"""Tests for CompletenessMetric."""

from __future__ import annotations

import pytest

from evalkit.metrics.completeness import CompletenessMetric
from evalkit.models import MetricScore
from evalkit.models import TestCase as EvalTestCase


class TestCompletenessMetric:
    def test_name(self) -> None:
        assert CompletenessMetric.name == "completeness"

    def test_default_threshold(self) -> None:
        metric = CompletenessMetric()
        assert metric.threshold == 0.5

    def test_custom_threshold(self) -> None:
        metric = CompletenessMetric(threshold=0.8)
        assert metric.threshold == 0.8

    # ------------------------------------------------------------------
    # Early return when expected_answer is None
    # ------------------------------------------------------------------

    def test_score_returns_zero_when_no_expected_answer(self, mock_judge) -> None:
        test_case = EvalTestCase(
            question="What is 2+2?",
            context=["Math context"],
            answer="Four.",
            expected_answer=None,
        )
        metric = CompletenessMetric()
        result = metric.score(test_case, mock_judge)
        assert result.score == pytest.approx(0.0)
        assert result.passed is False

    def test_score_reason_explains_missing_expected_answer(self, mock_judge) -> None:
        test_case = EvalTestCase(
            question="What is 2+2?",
            context=["Math context"],
            answer="Four.",
            expected_answer=None,
        )
        metric = CompletenessMetric()
        result = metric.score(test_case, mock_judge)
        assert "expected_answer" in result.reason

    def test_score_does_not_call_judge_when_no_expected_answer(self, mock_judge) -> None:
        test_case = EvalTestCase(
            question="What is 2+2?",
            context=["Math context"],
            answer="Four.",
            expected_answer=None,
        )
        metric = CompletenessMetric()
        metric.score(test_case, mock_judge)
        assert mock_judge.call_count == 0

    def test_score_async_returns_zero_when_no_expected_answer(self, mock_judge) -> None:
        import asyncio

        test_case = EvalTestCase(
            question="What is 2+2?",
            context=["Math context"],
            answer="Four.",
            expected_answer=None,
        )
        metric = CompletenessMetric()
        result = asyncio.run(metric.score_async(test_case, mock_judge))
        assert result.score == pytest.approx(0.0)
        assert mock_judge.call_count == 0

    # ------------------------------------------------------------------
    # Prompt construction
    # ------------------------------------------------------------------

    def test_prompt_contains_question(self, sample_test_case: EvalTestCase) -> None:
        metric = CompletenessMetric()
        prompt = metric._build_prompt(sample_test_case)
        assert sample_test_case.question in prompt

    def test_prompt_contains_expected_answer(self, sample_test_case: EvalTestCase) -> None:
        metric = CompletenessMetric()
        prompt = metric._build_prompt(sample_test_case)
        assert sample_test_case.expected_answer in prompt

    def test_prompt_contains_actual_answer(self, sample_test_case: EvalTestCase) -> None:
        metric = CompletenessMetric()
        prompt = metric._build_prompt(sample_test_case)
        assert sample_test_case.answer in prompt

    # ------------------------------------------------------------------
    # score() result with expected_answer set
    # ------------------------------------------------------------------

    def test_score_returns_metric_score(self, mock_judge, sample_test_case: EvalTestCase) -> None:
        metric = CompletenessMetric()
        result = metric.score(sample_test_case, mock_judge)
        assert isinstance(result, MetricScore)

    def test_score_metric_name(self, mock_judge, sample_test_case: EvalTestCase) -> None:
        metric = CompletenessMetric()
        result = metric.score(sample_test_case, mock_judge)
        assert result.metric_name == "completeness"

    def test_score_with_high_judge_response(self, sample_test_case: EvalTestCase) -> None:
        from tests.conftest import MockJudge

        judge = MockJudge(score=0.9, reason="Covers all key points")
        metric = CompletenessMetric()
        result = metric.score(sample_test_case, judge)
        assert result.score == pytest.approx(0.9)
        assert result.passed is True

    def test_score_with_low_judge_response(self, sample_test_case: EvalTestCase) -> None:
        from tests.conftest import MockJudge

        judge = MockJudge(score=0.2, reason="Missing important details")
        metric = CompletenessMetric()
        result = metric.score(sample_test_case, judge)
        assert result.score == pytest.approx(0.2)
        assert result.passed is False

    def test_score_calls_judge_when_expected_answer_set(self, mock_judge, sample_test_case: EvalTestCase) -> None:
        metric = CompletenessMetric()
        metric.score(sample_test_case, mock_judge)
        assert mock_judge.call_count == 1
