"""Evaluator 测试。"""

from promptgrad.evaluator import Evaluator
from promptgrad.models import ExecutionResult, TestCase

RUBRIC = {
    "dimensions": [
        {
            "name": "format",
            "weight": 0.5,
            "description": "Correct format",
            "eval_method": "rule",
            "rules": [
                {"type": "max_length", "value": 50},
                {"type": "no_prefix", "pattern": "^(Here is|The answer)"},
            ],
        },
        {
            "name": "accuracy",
            "weight": 0.5,
            "description": "Match expected",
            "eval_method": "reference",
            "metric": "fuzzy",
        },
    ],
    "pass_threshold": 0.7,
}


class TestRuleEval:
    def test_max_length_pass(self):
        ev = Evaluator(rubric=RUBRIC)
        score = ev._eval_rule("short text", [{"type": "max_length", "value": 50}])
        assert score == 1.0

    def test_max_length_fail(self):
        ev = Evaluator(rubric=RUBRIC)
        score = ev._eval_rule("x" * 60, [{"type": "max_length", "value": 50}])
        assert score == 0.0

    def test_min_length_pass(self):
        ev = Evaluator(rubric=RUBRIC)
        score = ev._eval_rule("enough text", [{"type": "min_length", "value": 5}])
        assert score == 1.0

    def test_min_length_fail(self):
        ev = Evaluator(rubric=RUBRIC)
        score = ev._eval_rule("hi", [{"type": "min_length", "value": 5}])
        assert score == 0.0

    def test_regex_match_pass(self):
        ev = Evaluator(rubric=RUBRIC)
        score = ev._eval_rule("error code: 404", [{"type": "regex_match", "pattern": r"\d{3}"}])
        assert score == 1.0

    def test_regex_match_fail(self):
        ev = Evaluator(rubric=RUBRIC)
        score = ev._eval_rule("no numbers here", [{"type": "regex_match", "pattern": r"\d{3}"}])
        assert score == 0.0

    def test_no_prefix_pass(self):
        ev = Evaluator(rubric=RUBRIC)
        score = ev._eval_rule("Good answer", [{"type": "no_prefix", "pattern": "^(Here is|The answer)"}])
        assert score == 1.0

    def test_no_prefix_fail(self):
        ev = Evaluator(rubric=RUBRIC)
        score = ev._eval_rule("Here is the result", [{"type": "no_prefix", "pattern": "^(Here is|The answer)"}])
        assert score == 0.0

    def test_multiple_rules_all_pass(self):
        ev = Evaluator(rubric=RUBRIC)
        rules = [{"type": "max_length", "value": 100}, {"type": "min_length", "value": 3}]
        score = ev._eval_rule("hello world", rules)
        assert score == 1.0

    def test_multiple_rules_one_fails(self):
        ev = Evaluator(rubric=RUBRIC)
        rules = [{"type": "max_length", "value": 5}, {"type": "min_length", "value": 3}]
        score = ev._eval_rule("hello world", rules)
        assert score == 0.0


class TestReferenceEval:
    def test_exact_match_pass(self):
        ev = Evaluator(rubric=RUBRIC)
        assert ev._eval_reference("hello world", "hello world", "exact_match") == 1.0

    def test_exact_match_strip(self):
        ev = Evaluator(rubric=RUBRIC)
        assert ev._eval_reference("  hello  ", "hello", "exact_match") == 1.0

    def test_exact_match_fail(self):
        ev = Evaluator(rubric=RUBRIC)
        assert ev._eval_reference("hello", "world", "exact_match") == 0.0

    def test_fuzzy_identical(self):
        ev = Evaluator(rubric=RUBRIC)
        assert ev._eval_reference("hello world", "hello world", "fuzzy") == 1.0

    def test_fuzzy_similar(self):
        ev = Evaluator(rubric=RUBRIC)
        score = ev._eval_reference("hello world", "hello worlds", "fuzzy")
        assert 0.8 < score < 1.0

    def test_fuzzy_different(self):
        ev = Evaluator(rubric=RUBRIC)
        score = ev._eval_reference("abc", "xyz", "fuzzy")
        assert score < 0.3

    def test_semantic_returns_negative(self):
        ev = Evaluator(rubric=RUBRIC)
        assert ev._eval_reference("a", "b", "semantic") == -1.0


class TestEvaluateIntegration:
    def test_evaluate_with_rule_and_reference(self):
        ev = Evaluator(rubric=RUBRIC)
        results = [
            ExecutionResult(case_id="c1", input={"q": "test"}, output="short match", latency_ms=100, token_usage={}),
            ExecutionResult(case_id="c2", input={"q": "test"}, output="x" * 60, latency_ms=100, token_usage={}),
        ]
        cases = [
            TestCase(id="c1", input={"q": "test"}, golden="short match", tags=[]),
            TestCase(id="c2", input={"q": "test"}, golden="different", tags=[]),
        ]
        eval_result = ev.evaluate(results, cases)
        assert len(eval_result.case_scores) == 2
        assert eval_result.case_scores[0].scores["format"] == 1.0  # c1 short, passes max_length
        assert eval_result.case_scores[1].scores["format"] == 0.0  # c2 too long
        assert eval_result.avg_score > 0
        assert eval_result.worst_dimension in ("format", "accuracy")

    def test_evaluate_empty(self):
        ev = Evaluator(rubric=RUBRIC)
        eval_result = ev.evaluate([], [])
        assert eval_result.avg_score == 0.0
        assert eval_result.pass_rate == 0.0
