"""Tests for BaseMetric."""

from __future__ import annotations

import asyncio

import pytest

from evalkit.metrics.base import BaseMetric
from evalkit.models import MetricScore
from evalkit.models import TestCase as EvalTestCase


class ConcreteMetric(BaseMetric):
    name = "concrete_test"

    def _build_prompt(self, test_case: EvalTestCase) -> str:
        return f"Evaluate: {test_case.question}"


class TestScore:
    def test_score_calls_judge_and_returns_metric_score(self, mock_judge, sample_test_case) -> None:
        metric = ConcreteMetric()
        result = metric.score(sample_test_case, mock_judge)
        assert isinstance(result, MetricScore)
        assert result.metric_name == "concrete_test"

    def test_score_builds_prompt_with_question(self, mock_judge, sample_test_case) -> None:
        metric = ConcreteMetric()
        metric.score(sample_test_case, mock_judge)
        assert sample_test_case.question in mock_judge.last_prompt

    def test_score_calls_judge_once(self, mock_judge, sample_test_case) -> None:
        metric = ConcreteMetric()
        metric.score(sample_test_case, mock_judge)
        assert mock_judge.call_count == 1

    def test_score_passed_true_when_above_threshold(self, mock_judge, sample_test_case) -> None:
        metric = ConcreteMetric()
        result = metric.score(sample_test_case, mock_judge)
        assert result.passed is True

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

        judge = MockJudge(score=0.3, reason="Poor")
        metric = ConcreteMetric()
        result = metric.score(sample_test_case, judge)
        assert result.passed is False

    def test_score_values_match_judge_response(self, mock_judge, sample_test_case) -> None:
        metric = ConcreteMetric()
        result = metric.score(sample_test_case, mock_judge)
        assert result.score == pytest.approx(0.8)
        assert result.reason == "Good answer"

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

        judge = MockJudge(score=0.6, reason="Okay")
        metric = ConcreteMetric()
        metric.threshold = 0.7
        result = metric.score(sample_test_case, judge)
        assert result.passed is False

        metric.threshold = 0.5
        judge2 = MockJudge(score=0.6, reason="Okay")
        result2 = metric.score(sample_test_case, judge2)
        assert result2.passed is True


class TestScoreAsync:
    def test_score_async_returns_metric_score(self, mock_judge, sample_test_case) -> None:
        metric = ConcreteMetric()
        result = asyncio.run(metric.score_async(sample_test_case, mock_judge))
        assert isinstance(result, MetricScore)
        assert result.metric_name == "concrete_test"

    def test_score_async_calls_judge(self, mock_judge, sample_test_case) -> None:
        metric = ConcreteMetric()
        asyncio.run(metric.score_async(sample_test_case, mock_judge))
        assert mock_judge.call_count == 1

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

        judge = MockJudge(score=0.7, reason="Decent")
        metric = ConcreteMetric()
        sync_result = metric.score(sample_test_case, judge)
        judge2 = MockJudge(score=0.7, reason="Decent")
        async_result = asyncio.run(metric.score_async(sample_test_case, judge2))
        assert sync_result.score == async_result.score
        assert sync_result.reason == async_result.reason
        assert sync_result.passed == async_result.passed
