"""Integration contracts between checkpoint helpers and the model pipeline."""

from __future__ import annotations

import json
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import MagicMock

import pytest
import torch

import obliteratus.abliterate as abliterate
from obliteratus.abliterate import AbliterationPipeline


pytestmark = pytest.mark.cpu


def _pipeline(tmp_path: Path) -> AbliterationPipeline:
    pipeline = AbliterationPipeline(
        model_name="test-model",
        output_dir=str(tmp_path / "checkpoint"),
        method="basic",
    )
    pipeline._on_log = lambda _message: None
    pipeline._on_stage = lambda _event: None
    pipeline.handle = MagicMock()
    return pipeline


def _write_valid_checkpoint(
    checkpoint_dir: Path,
    metadata_json: str,
    payload: bytes = b"saved",
) -> None:
    (checkpoint_dir / "model.safetensors").write_bytes(payload)
    (checkpoint_dir / "config.json").write_text("{}", encoding="utf-8")
    (checkpoint_dir / "tokenizer_config.json").write_text("{}", encoding="utf-8")
    (checkpoint_dir / "abliteration_metadata.json").write_text(
        metadata_json,
        encoding="utf-8",
    )


def test_rebirth_rejects_invalid_metadata_before_gathering_state(tmp_path):
    pipeline = _pipeline(tmp_path)
    destination = pipeline.output_dir
    destination.mkdir()
    sentinel = destination / "sentinel"
    sentinel.write_text("old", encoding="utf-8")
    pipeline._build_metadata = MagicMock(return_value={"invalid": object()})
    pipeline._gather_state_dict = MagicMock()

    with pytest.raises(TypeError):
        pipeline._rebirth()

    pipeline._gather_state_dict.assert_not_called()
    assert sentinel.read_text(encoding="utf-8") == "old"


def test_rebirth_insufficient_capacity_preserves_checkpoint_and_runtime_state(
    tmp_path,
    monkeypatch,
):
    pipeline = _pipeline(tmp_path)
    destination = pipeline.output_dir
    destination.mkdir()
    sentinel = destination / "sentinel"
    sentinel.write_text("old", encoding="utf-8")
    pipeline._build_metadata = MagicMock(return_value={"schema": 1})
    pipeline._gather_state_dict = MagicMock(
        return_value={"weight": torch.ones(4, dtype=torch.float32)},
    )
    pipeline._write_local_checkpoint = MagicMock()
    pipeline._free_gpu_memory = MagicMock()
    pipeline._cleanup_offload_dir = MagicMock()
    monkeypatch.setattr(
        abliterate.shutil,
        "disk_usage",
        lambda _path: SimpleNamespace(free=16),
    )

    with pytest.raises(OSError, match="Insufficient disk space"):
        pipeline._rebirth()

    pipeline._write_local_checkpoint.assert_not_called()
    pipeline._free_gpu_memory.assert_not_called()
    pipeline._cleanup_offload_dir.assert_not_called()
    assert sentinel.read_text(encoding="utf-8") == "old"
    assert list(tmp_path.glob(".checkpoint.staging-*")) == []


def test_rebirth_ignores_non_os_disk_probe_failure_and_promotes_checkpoint(
    tmp_path,
    monkeypatch,
):
    pipeline = _pipeline(tmp_path)
    pipeline._build_metadata = MagicMock(return_value={"schema": 1})
    state_dict = {"weight": torch.ones(1)}
    pipeline._gather_state_dict = MagicMock(return_value=state_dict)

    def write_checkpoint(checkpoint_dir, metadata_json, received_state_dict):
        assert json.loads(metadata_json) == {"schema": 1}
        assert received_state_dict is state_dict
        _write_valid_checkpoint(checkpoint_dir, metadata_json)

    pipeline._write_local_checkpoint = MagicMock(side_effect=write_checkpoint)
    pipeline._free_gpu_memory = MagicMock()
    pipeline._cleanup_offload_dir = MagicMock()

    def fail_probe(_path):
        raise RuntimeError("filesystem probe unavailable")

    monkeypatch.setattr(abliterate.shutil, "disk_usage", fail_probe)

    assert pipeline._rebirth() == pipeline.output_dir
    assert (pipeline.output_dir / "model.safetensors").read_bytes() == b"saved"
    pipeline._free_gpu_memory.assert_called_once_with()
    pipeline._cleanup_offload_dir.assert_called_once_with()


def test_rebirth_rejects_truncated_checkpoint_then_retries_idempotently(tmp_path):
    pipeline = _pipeline(tmp_path)
    destination = pipeline.output_dir
    destination.mkdir()
    sentinel = destination / "sentinel"
    sentinel.write_text("old", encoding="utf-8")
    pipeline._build_metadata = MagicMock(return_value={"schema": 1})
    pipeline._gather_state_dict = MagicMock(return_value={"weight": torch.ones(1)})
    pipeline._free_gpu_memory = MagicMock()
    pipeline._cleanup_offload_dir = MagicMock()
    attempts = 0

    def write_checkpoint(checkpoint_dir, metadata_json, _state_dict):
        nonlocal attempts
        attempts += 1
        _write_valid_checkpoint(checkpoint_dir, metadata_json)
        if attempts == 1:
            (checkpoint_dir / "abliteration_metadata.json").write_text(
                "{",
                encoding="utf-8",
            )

    pipeline._write_local_checkpoint = MagicMock(side_effect=write_checkpoint)

    with pytest.raises(ValueError, match="metadata is corrupt"):
        pipeline._rebirth()

    assert sentinel.read_text(encoding="utf-8") == "old"
    pipeline._free_gpu_memory.assert_not_called()
    pipeline._cleanup_offload_dir.assert_not_called()
    assert list(tmp_path.glob(".checkpoint.staging-*")) == []

    assert pipeline._rebirth() == destination
    assert (destination / "model.safetensors").read_bytes() == b"saved"
    assert not sentinel.exists()
    pipeline._free_gpu_memory.assert_called_once_with()
    pipeline._cleanup_offload_dir.assert_called_once_with()


def test_rebirth_pushes_only_after_local_checkpoint_promotion(tmp_path, monkeypatch):
    import huggingface_hub

    pipeline = _pipeline(tmp_path)
    pipeline.push_to_hub = "auto"
    pipeline.hub_token = "test-token"
    pipeline.hub_community_org = "test-org"
    pipeline._build_metadata = MagicMock(return_value={"schema": 1})
    pipeline._gather_state_dict = MagicMock(return_value={"weight": torch.ones(1)})
    pipeline._free_gpu_memory = MagicMock()
    pipeline._cleanup_offload_dir = MagicMock()

    def write_checkpoint(checkpoint_dir, metadata_json, _state_dict):
        _write_valid_checkpoint(checkpoint_dir, metadata_json)

    pipeline._write_local_checkpoint = MagicMock(side_effect=write_checkpoint)
    api = MagicMock()
    api_factory = MagicMock(return_value=api)
    monkeypatch.setattr(huggingface_hub, "HfApi", api_factory)
    auto_name = MagicMock(return_value="test-org/test-model-OBLITERATED")
    monkeypatch.setattr(abliterate, "auto_hub_repo_id", auto_name)

    assert pipeline._rebirth() == pipeline.output_dir

    assert (pipeline.output_dir / "model.safetensors").read_bytes() == b"saved"
    api_factory.assert_called_once_with(token="test-token")
    auto_name.assert_called_once_with(
        "test-model",
        api=api,
        org="test-org",
    )
    api.create_repo.assert_called_once_with(
        "test-org/test-model-OBLITERATED",
        exist_ok=True,
    )
    api.upload_folder.assert_called_once_with(
        folder_path=str(pipeline.output_dir),
        repo_id="test-org/test-model-OBLITERATED",
        commit_message="OBLITERATUS: abliterated test-model (basic)",
    )


def test_rebirth_uses_fallback_token_for_explicit_hub_destination(
    tmp_path,
    monkeypatch,
):
    import huggingface_hub

    pipeline = _pipeline(tmp_path)
    pipeline.push_to_hub = "test-org/explicit-repo"
    pipeline._build_metadata = MagicMock(return_value={"schema": 1})
    pipeline._gather_state_dict = MagicMock(return_value={"weight": torch.ones(1)})
    pipeline._write_local_checkpoint = MagicMock(
        side_effect=lambda path, metadata_json, _state_dict: _write_valid_checkpoint(
            path,
            metadata_json,
        ),
    )
    pipeline._free_gpu_memory = MagicMock()
    pipeline._cleanup_offload_dir = MagicMock()
    api = MagicMock()
    api_factory = MagicMock(return_value=api)
    monkeypatch.setattr(huggingface_hub, "HfApi", api_factory)
    monkeypatch.setenv("HF_TOKEN", "fallback-token")

    pipeline._rebirth()

    api_factory.assert_called_once_with(token="fallback-token")
    api.create_repo.assert_called_once_with(
        "test-org/explicit-repo",
        exist_ok=True,
    )


def test_write_local_checkpoint_strips_runtime_only_state_and_writes_metadata(
    tmp_path,
    monkeypatch,
):
    import obliteratus.lora_ablation as lora_ablation

    class Quantizer:
        def __init__(self):
            self.models = []

        def remove_quantization_config(self, model):
            self.models.append(model)

    class Model:
        def __init__(self):
            self.hf_quantizer = Quantizer()
            self._weight_conversions = {"legacy": "conversion"}
            self.saved = None

        def save_pretrained(self, path, **kwargs):
            self.saved = (path, kwargs)

    pipeline = _pipeline(tmp_path)
    model = Model()
    tokenizer = MagicMock()
    pipeline.handle = SimpleNamespace(model=model, tokenizer=tokenizer)
    pipeline._lora_adapters = {"layer": (torch.ones(1), torch.ones(1))}
    checkpoint_dir = tmp_path / "staging"
    adapter_path = checkpoint_dir / "lora"
    save_adapters = MagicMock(return_value=adapter_path)
    monkeypatch.setattr(lora_ablation, "save_lora_adapters", save_adapters)
    state_dict = {"weight": torch.ones(1)}
    metadata_json = '{"schema": 1}'
    checkpoint_dir.mkdir()

    pipeline._write_local_checkpoint(checkpoint_dir, metadata_json, state_dict)

    assert model.hf_quantizer.models == [model]
    assert not hasattr(model, "_weight_conversions")
    assert model.saved == (
        checkpoint_dir,
        {
            "state_dict": state_dict,
            "max_shard_size": "2GB",
            "save_original_format": False,
        },
    )
    tokenizer.save_pretrained.assert_called_once_with(checkpoint_dir)
    save_adapters.assert_called_once_with(pipeline._lora_adapters, checkpoint_dir)
    assert (checkpoint_dir / "abliteration_metadata.json").read_text(
        encoding="utf-8",
    ) == metadata_json
