"""Unit tests for evalkit.suites."""

from __future__ import annotations

from evalkit.models import Suite
from evalkit.suites import (
    BUILTIN_SUITES,
    AgentToolCallSuite,
    AnswerSimilaritySuite,
    ConversationalSuite,
    DocumentSearchSuite,
    EvaluationSuite,
    RAGQASuite,
)

# ---------------------------------------------------------------------------
# EvaluationSuite base class
# ---------------------------------------------------------------------------


class TestEvaluationSuite:
    def test_basic_instantiation(self):
        suite = EvaluationSuite(name="test_suite", description="A test suite")
        assert suite.name == "test_suite"
        assert suite.description == "A test suite"
        assert suite.metrics == []

    def test_metric_names_empty(self):
        suite = EvaluationSuite(name="empty", description="No metrics")
        assert suite.metric_names() == []

    def test_metric_names_returns_correct_list(self):
        from evalkit.metrics import AnswerRelevancyMetric, FaithfulnessMetric

        suite = EvaluationSuite(
            name="test",
            description="Two metrics",
            metrics=[FaithfulnessMetric(), AnswerRelevancyMetric()],
        )
        assert suite.metric_names() == ["faithfulness", "answer_relevancy"]

    def test_metrics_default_factory_not_shared(self):
        """Each EvaluationSuite instance must have its own metrics list."""
        s1 = EvaluationSuite(name="s1", description="first")
        s2 = EvaluationSuite(name="s2", description="second")
        from evalkit.metrics import FaithfulnessMetric

        s1.metrics.append(FaithfulnessMetric())
        assert s2.metrics == []


# ---------------------------------------------------------------------------
# RAGQASuite
# ---------------------------------------------------------------------------


class TestRAGQASuite:
    def test_returns_evaluation_suite(self):
        suite = RAGQASuite()
        assert isinstance(suite, EvaluationSuite)

    def test_suite_name(self):
        suite = RAGQASuite()
        assert suite.name == "rag_qa"

    def test_has_five_metrics(self):
        suite = RAGQASuite()
        assert len(suite.metrics) == 5

    def test_metric_names(self):
        suite = RAGQASuite()
        names = suite.metric_names()
        assert "faithfulness" in names
        assert "answer_relevancy" in names
        assert "contextual_relevancy" in names
        assert "hallucination" in names
        assert "completeness" in names

    def test_metric_order(self):
        suite = RAGQASuite()
        assert suite.metric_names() == [
            "faithfulness",
            "answer_relevancy",
            "contextual_relevancy",
            "hallucination",
            "completeness",
        ]

    def test_each_call_creates_independent_instances(self):
        """Suite factory must return new metric instances on every call."""
        suite1 = RAGQASuite()
        suite2 = RAGQASuite()
        for m1, m2 in zip(suite1.metrics, suite2.metrics):
            assert m1 is not m2

    def test_metrics_list_is_independent_between_calls(self):
        suite1 = RAGQASuite()
        suite2 = RAGQASuite()
        assert suite1.metrics is not suite2.metrics


# ---------------------------------------------------------------------------
# DocumentSearchSuite
# ---------------------------------------------------------------------------


class TestDocumentSearchSuite:
    def test_returns_evaluation_suite(self):
        suite = DocumentSearchSuite()
        assert isinstance(suite, EvaluationSuite)

    def test_suite_name(self):
        suite = DocumentSearchSuite()
        assert suite.name == "document_search"

    def test_has_two_metrics(self):
        suite = DocumentSearchSuite()
        assert len(suite.metrics) == 2

    def test_metric_names(self):
        suite = DocumentSearchSuite()
        assert suite.metric_names() == ["contextual_relevancy", "faithfulness"]

    def test_each_call_creates_independent_instances(self):
        suite1 = DocumentSearchSuite()
        suite2 = DocumentSearchSuite()
        for m1, m2 in zip(suite1.metrics, suite2.metrics):
            assert m1 is not m2


# ---------------------------------------------------------------------------
# ConversationalSuite
# ---------------------------------------------------------------------------


class TestConversationalSuite:
    def test_returns_evaluation_suite(self):
        suite = ConversationalSuite()
        assert isinstance(suite, EvaluationSuite)

    def test_suite_name(self):
        suite = ConversationalSuite()
        assert suite.name == "conversational"

    def test_has_three_metrics(self):
        suite = ConversationalSuite()
        assert len(suite.metrics) == 3

    def test_metric_names(self):
        suite = ConversationalSuite()
        assert suite.metric_names() == [
            "answer_relevancy",
            "faithfulness",
            "hallucination",
        ]

    def test_each_call_creates_independent_instances(self):
        suite1 = ConversationalSuite()
        suite2 = ConversationalSuite()
        for m1, m2 in zip(suite1.metrics, suite2.metrics):
            assert m1 is not m2


# ---------------------------------------------------------------------------
# AgentToolCallSuite
# ---------------------------------------------------------------------------


class TestAgentToolCallSuite:
    def test_returns_evaluation_suite(self):
        suite = AgentToolCallSuite()
        assert isinstance(suite, EvaluationSuite)

    def test_suite_name(self):
        suite = AgentToolCallSuite()
        assert suite.name == "agent_tool_call"

    def test_has_three_metrics(self):
        suite = AgentToolCallSuite()
        assert len(suite.metrics) == 3

    def test_metric_names(self):
        suite = AgentToolCallSuite()
        assert suite.metric_names() == ["tool_selection", "tool_parameter", "tool_parameter_similarity"]

    def test_each_call_creates_independent_instances(self):
        suite1 = AgentToolCallSuite()
        suite2 = AgentToolCallSuite()
        for m1, m2 in zip(suite1.metrics, suite2.metrics):
            assert m1 is not m2


class TestAnswerSimilaritySuite:
    def test_returns_evaluation_suite(self):
        suite = AnswerSimilaritySuite()
        assert isinstance(suite, EvaluationSuite)

    def test_suite_name(self):
        suite = AnswerSimilaritySuite()
        assert suite.name == "answer_similarity"

    def test_has_three_metrics(self):
        suite = AnswerSimilaritySuite()
        assert len(suite.metrics) == 3

    def test_metric_names(self):
        suite = AnswerSimilaritySuite()
        assert suite.metric_names() == ["exact_match", "string_containment", "string_similarity"]

    def test_does_not_require_judge(self):
        suite = AnswerSimilaritySuite()
        assert suite.requires_judge is False

    def test_each_call_creates_independent_instances(self):
        suite1 = AnswerSimilaritySuite()
        suite2 = AnswerSimilaritySuite()
        for m1, m2 in zip(suite1.metrics, suite2.metrics):
            assert m1 is not m2


class TestSuiteEnum:
    def test_has_all_members(self):
        assert set(Suite) == {
            Suite.RAG_QA,
            Suite.DOCUMENT_SEARCH,
            Suite.CONVERSATIONAL,
            Suite.AGENT_TOOL_CALL,
            Suite.LIVE_QA,
            Suite.ANSWER_SIMILARITY,
        }

    def test_values_are_strings(self):
        for member in Suite:
            assert isinstance(member, str)
            assert member.value == str(member)

    def test_string_comparison(self):
        assert Suite.RAG_QA == "rag_qa"
        assert Suite.LIVE_QA == "live_qa"

    def test_lookup_in_builtin_suites(self):
        for member in Suite:
            assert member in BUILTIN_SUITES

    def test_importable_from_evalkit(self):
        from evalkit import Suite as TopLevelSuite

        assert TopLevelSuite is Suite


class TestBuiltinSuites:
    def test_contains_all_keys(self):
        for member in Suite:
            assert member in BUILTIN_SUITES

    def test_all_values_are_callable(self):
        for key, factory in BUILTIN_SUITES.items():
            assert callable(factory), f"BUILTIN_SUITES['{key}'] is not callable"

    def test_calling_each_factory_returns_evaluation_suite(self):
        for key, factory in BUILTIN_SUITES.items():
            suite = factory()
            assert isinstance(suite, EvaluationSuite), f"BUILTIN_SUITES['{key}']() did not return an EvaluationSuite"

    def test_factory_keys_match_suite_names(self):
        for key, factory in BUILTIN_SUITES.items():
            suite = factory()
            assert suite.name == key, f"Key '{key}' does not match suite.name '{suite.name}'"

    def test_each_factory_call_creates_new_suite(self):
        """Factories must not return singletons."""
        for key, factory in BUILTIN_SUITES.items():
            s1 = factory()
            s2 = factory()
            assert s1 is not s2, f"BUILTIN_SUITES['{key}'] returned the same instance twice"
