"""Tests for obliteratus.capability_check."""

from __future__ import annotations

import json
from unittest.mock import MagicMock, patch

import pytest


class TestCapabilityCheckImport:
    def test_import(self):
        from obliteratus.capability_check import capability_check  # noqa: F401

    def test_quick_subjects_defined(self):
        from obliteratus.capability_check import QUICK_SUBJECTS

        assert len(QUICK_SUBJECTS) >= 3
        assert all(s.startswith("mmlu_") for s in QUICK_SUBJECTS)


class TestRunLmEval:
    @patch("obliteratus.capability_check.subprocess.run")
    def test_calls_lm_eval(self, mock_run, tmp_path):
        from obliteratus.capability_check import _run_lm_eval

        # Create fake results
        results_dir = tmp_path / "results" / "fake_model"
        results_dir.mkdir(parents=True)
        results_file = results_dir / "results_2026.json"
        results_file.write_text(json.dumps({
            "results": {"mmlu": {"acc,none": 0.85}},
        }))

        mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")

        result = _run_lm_eval("fake/model", "mmlu", 5, "cpu", str(tmp_path / "results"))
        assert result["results"]["mmlu"]["acc,none"] == 0.85
        mock_run.assert_called_once()

    @patch("obliteratus.capability_check.subprocess.run")
    def test_raises_on_failure(self, mock_run, tmp_path):
        from obliteratus.capability_check import _run_lm_eval

        mock_run.return_value = MagicMock(returncode=1, stdout="", stderr="error")

        with pytest.raises(RuntimeError, match="lm-eval exited"):
            _run_lm_eval("fake/model", "mmlu", 5, "cpu", str(tmp_path))


class TestCapabilityCheck:
    @patch("obliteratus.capability_check._run_lm_eval")
    def test_computes_delta(self, mock_lm_eval, tmp_path):
        from obliteratus.capability_check import capability_check

        mock_lm_eval.side_effect = [
            {"results": {"mmlu": {"acc,none": 0.81}}},  # abliterated
            {"results": {"mmlu": {"acc,none": 0.87}}},  # stock
        ]

        result = capability_check(
            "fake/abliterated", "fake/stock",
            device="cpu", output_dir=str(tmp_path),
        )

        assert result["abliterated_acc"] == 0.81
        assert result["stock_acc"] == 0.87
        assert result["delta_pp"] == -6.0

    @patch("obliteratus.capability_check._run_lm_eval")
    def test_quick_mode(self, mock_lm_eval, tmp_path):
        from obliteratus.capability_check import QUICK_SUBJECTS, capability_check

        mock_lm_eval.side_effect = [
            {"results": {s: {"acc,none": 0.8} for s in QUICK_SUBJECTS}},
            {"results": {s: {"acc,none": 0.9} for s in QUICK_SUBJECTS}},
        ]

        result = capability_check(
            "fake/abliterated", "fake/stock",
            device="cpu", quick=True, output_dir=str(tmp_path),
        )

        assert result["tasks"] == ",".join(QUICK_SUBJECTS)
        assert abs(result["abliterated_acc"] - 0.8) < 0.01

    @patch("obliteratus.capability_check._run_lm_eval")
    def test_saves_summary(self, mock_lm_eval, tmp_path):
        from obliteratus.capability_check import capability_check

        mock_lm_eval.side_effect = [
            {"results": {"mmlu": {"acc,none": 0.85}}},
            {"results": {"mmlu": {"acc,none": 0.87}}},
        ]

        capability_check(
            "fake/abliterated", "fake/stock",
            device="cpu", output_dir=str(tmp_path),
        )

        summary_path = tmp_path / "capability_summary.json"
        assert summary_path.exists()
        summary = json.loads(summary_path.read_text())
        assert "delta_pp" in summary
        assert summary["method"] == "lm-eval-harness 0-shot log-likelihood"

    @patch("obliteratus.capability_check._run_lm_eval")
    def test_custom_subjects(self, mock_lm_eval, tmp_path):
        from obliteratus.capability_check import capability_check

        mock_lm_eval.side_effect = [
            {"results": {"mmlu_physics": {"acc,none": 0.75}}},
            {"results": {"mmlu_physics": {"acc,none": 0.80}}},
        ]

        result = capability_check(
            "fake/abliterated", "fake/stock",
            device="cpu", subjects=["mmlu_physics"],
            output_dir=str(tmp_path),
        )

        assert result["tasks"] == "mmlu_physics"
        assert result["abliterated_acc"] == 0.75

    @patch("obliteratus.capability_check._run_lm_eval")
    def test_individual_subjects_mean(self, mock_lm_eval, tmp_path):
        """Test mean computation when no aggregate mmlu key exists."""
        from obliteratus.capability_check import capability_check

        # Results don't have "mmlu" aggregate key or match tasks.split(",")[0]
        mock_lm_eval.side_effect = [
            {"results": {"sub_a": {"acc,none": 0.6}, "sub_b": {"acc,none": 0.8}}},
            {"results": {"sub_a": {"acc,none": 0.7}, "sub_b": {"acc,none": 0.9}}},
        ]

        result = capability_check(
            "fake/abliterated", "fake/stock",
            device="cpu", subjects=["mmlu_a", "mmlu_b"],
            output_dir=str(tmp_path),
        )

        assert abs(result["abliterated_acc"] - 0.7) < 0.01
        assert abs(result["stock_acc"] - 0.8) < 0.01


class TestMainCLI:
    @patch("obliteratus.capability_check.capability_check")
    def test_main_runs(self, mock_check):
        from obliteratus.capability_check import main

        mock_check.return_value = {
            "stock_acc": 0.87, "abliterated_acc": 0.81,
            "delta_pp": -6.0, "tasks": "mmlu", "method": "test",
        }

        import sys
        old_argv = sys.argv
        sys.argv = ["prog", "--abliterated", "fake/abl", "--stock", "fake/stock", "--device", "cpu"]
        try:
            main()
        finally:
            sys.argv = old_argv

        mock_check.assert_called_once()

    @patch("obliteratus.capability_check._run_lm_eval")
    def test_cli_dispatch(self, mock_lm_eval, tmp_path):
        """Test that 'obliteratus capability-check' dispatches correctly."""
        from obliteratus.cli import main as cli_main

        mock_lm_eval.side_effect = [
            {"results": {"mmlu": {"acc,none": 0.81}}},
            {"results": {"mmlu": {"acc,none": 0.87}}},
        ]

        cli_main(["capability-check",
                   "--abliterated", "fake/abl",
                   "--stock", "fake/stock",
                   "--device", "cpu",
                   "--output-dir", str(tmp_path)])
