"""Tests for eval contracts and golden comparison helpers."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from abi_control_core.sdk.evals import (
    compare_exact,
    compare_numeric_tolerance,
    compare_structural,
    load_eval_suite,
    write_eval_result,
)
from abi_control_core.sdk.validate import SchemaValidationError, validate_json

# ── Exact comparison ───────────────────────────────────────────────────────

class TestCompareExact:
    def test_equal_dicts(self) -> None:
        assert compare_exact({"a": 1}, {"a": 1})

    def test_unequal_dicts(self) -> None:
        assert not compare_exact({"a": 1}, {"a": 2})

    def test_equal_lists(self) -> None:
        assert compare_exact([1, 2, 3], [1, 2, 3])

    def test_equal_strings(self) -> None:
        assert compare_exact("hello", "hello")

    def test_unequal_strings(self) -> None:
        assert not compare_exact("hello", "world")

    def test_nested(self) -> None:
        a = {"x": [1, {"y": 2}]}
        b = {"x": [1, {"y": 2}]}
        assert compare_exact(a, b)

    def test_nested_unequal(self) -> None:
        a = {"x": [1, {"y": 2}]}
        b = {"x": [1, {"y": 3}]}
        assert not compare_exact(a, b)


# ── Numeric tolerance comparison ──────────────────────────────────────────

class TestCompareNumericTolerance:
    def test_exact_match(self) -> None:
        assert compare_numeric_tolerance(1.0, 1.0)

    def test_within_tolerance(self) -> None:
        assert compare_numeric_tolerance(1.0000001, 1.0, tolerance=1e-6)

    def test_outside_tolerance(self) -> None:
        assert not compare_numeric_tolerance(1.01, 1.0, tolerance=1e-6)

    def test_nested_dict(self) -> None:
        actual = {"x": 1.0000001, "y": 2.0}
        expected = {"x": 1.0, "y": 2.0}
        assert compare_numeric_tolerance(actual, expected, tolerance=1e-6)

    def test_nested_list(self) -> None:
        actual = [1.0000001, 2.0]
        expected = [1.0, 2.0]
        assert compare_numeric_tolerance(actual, expected, tolerance=1e-6)

    def test_mixed_types(self) -> None:
        actual = {"n": 1.0, "s": "hello"}
        expected = {"n": 1.0, "s": "hello"}
        assert compare_numeric_tolerance(actual, expected)

    def test_string_mismatch(self) -> None:
        assert not compare_numeric_tolerance("a", "b")

    def test_different_keys(self) -> None:
        assert not compare_numeric_tolerance({"a": 1}, {"b": 1})

    def test_different_lengths(self) -> None:
        assert not compare_numeric_tolerance([1, 2], [1])


# ── Structural comparison ─────────────────────────────────────────────────

class TestCompareStructural:
    def test_matching_structure(self) -> None:
        expected = {"name": "x", "value": 1}
        actual = {"name": "y", "value": 2, "extra": True}
        assert compare_structural(actual, expected)

    def test_missing_key(self) -> None:
        expected = {"name": "x", "value": 1}
        actual = {"name": "y"}
        assert not compare_structural(actual, expected)

    def test_type_mismatch(self) -> None:
        expected = {"value": 1}
        actual = {"value": "string"}
        assert not compare_structural(actual, expected)

    def test_nested_structure(self) -> None:
        expected = {"nested": {"a": 1}}
        actual = {"nested": {"a": 2, "b": 3}}
        assert compare_structural(actual, expected)

    def test_list_structure(self) -> None:
        expected = {"items": [{"id": 1}]}
        actual = {"items": [{"id": 2, "extra": True}]}
        assert compare_structural(actual, expected)

    def test_empty_list(self) -> None:
        expected = {"items": []}
        actual = {"items": [1, 2, 3]}
        assert compare_structural(actual, expected)

    def test_actual_not_dict_when_expected_dict(self) -> None:
        assert not compare_structural("string", {"key": "val"})


# ── Eval suite load/write ─────────────────────────────────────────────────

class TestEvalSuiteRoundtrip:
    def test_load_valid_suite(self, tmp_path: Path) -> None:
        suite = {
            "schema_version": "eval-suite/1.0",
            "suite_id": "roundtrip-suite",
            "cases": [
                {"case_id": "tc-1", "input": {"x": 1}, "expected": {"y": 2}},
            ],
        }
        suite_path = tmp_path / "suite.json"
        suite_path.write_text(json.dumps(suite), encoding="utf-8")
        loaded = load_eval_suite(suite_path)
        assert loaded["suite_id"] == "roundtrip-suite"

    def test_load_invalid_suite(self, tmp_path: Path) -> None:
        invalid = {"schema_version": "eval-suite/1.0", "suite_id": "bad", "cases": []}
        suite_path = tmp_path / "bad_suite.json"
        suite_path.write_text(json.dumps(invalid), encoding="utf-8")
        with pytest.raises(SchemaValidationError):
            load_eval_suite(suite_path)

    def test_write_eval_result(self, tmp_path: Path) -> None:
        result_path = tmp_path / "result.json"
        results = [
            {"case_id": "tc-1", "status": "pass"},
            {"case_id": "tc-2", "status": "fail", "message": "mismatch"},
        ]
        result = write_eval_result(result_path, "roundtrip-suite", "aabb0011", results)

        assert result["summary"]["total"] == 2
        assert result["summary"]["passed"] == 1
        assert result["summary"]["failed"] == 1

        # Verify written file validates
        loaded = json.loads(result_path.read_text(encoding="utf-8"))
        validate_json(loaded, "eval_result.schema.json")

    def test_write_eval_result_all_pass(self, tmp_path: Path) -> None:
        result_path = tmp_path / "result.json"
        results = [{"case_id": "tc-1", "status": "pass"}]
        result = write_eval_result(result_path, "s1", "aabb0011", results)
        assert result["summary"]["passed"] == 1
        assert result["summary"]["failed"] == 0
