"""
High-value pure logic tests for graders.py.

Tests extract_answer_from_response(), DEFAULT_EVALUATION_CONFIG,
regex extraction patterns, and grade_single_result response parsing.
"""

import re
from unittest.mock import MagicMock, patch


from local_deep_research.benchmarks.graders import (
    DEFAULT_EVALUATION_CONFIG,
    extract_answer_from_response,
    get_evaluation_llm,
    grade_single_result,
)


class TestDefaultEvaluationConfig:
    """Tests for DEFAULT_EVALUATION_CONFIG dict values."""

    def test_model_name_is_set(self):
        assert "model_name" in DEFAULT_EVALUATION_CONFIG
        assert (
            DEFAULT_EVALUATION_CONFIG["model_name"]
            == "anthropic/claude-3.7-sonnet"
        )

    def test_provider_is_openai_endpoint(self):
        assert DEFAULT_EVALUATION_CONFIG["provider"] == "openai_endpoint"

    def test_openai_endpoint_url(self):
        assert (
            DEFAULT_EVALUATION_CONFIG["openai_endpoint_url"]
            == "https://openrouter.ai/api/v1"
        )

    def test_temperature_is_zero(self):
        assert DEFAULT_EVALUATION_CONFIG["temperature"] == 0

    def test_no_max_tokens_key(self):
        """max_tokens was intentionally removed per code comments."""
        assert "max_tokens" not in DEFAULT_EVALUATION_CONFIG

    def test_config_has_exactly_four_keys(self):
        assert len(DEFAULT_EVALUATION_CONFIG) == 4


class TestExtractAnswerSimpleQA:
    """Tests for extract_answer_from_response with simpleqa dataset type."""

    def test_basic_response_returned_as_is(self):
        result = extract_answer_from_response("Paris is the capital of France.")
        assert result["extracted_answer"] == "Paris is the capital of France."
        assert result["confidence"] == "100"

    def test_empty_response(self):
        result = extract_answer_from_response("")
        assert result["extracted_answer"] == ""
        assert result["confidence"] == "100"

    def test_citations_are_stripped(self):
        result = extract_answer_from_response(
            "The answer is 42[1] according to sources[23]."
        )
        assert "[1]" not in result["extracted_answer"]
        assert "[23]" not in result["extracted_answer"]
        assert "42" in result["extracted_answer"]

    def test_multiple_citations_stripped(self):
        result = extract_answer_from_response("[1][2][3] Some text [45]")
        assert result["extracted_answer"] == " Some text "

    def test_default_dataset_type_is_simpleqa(self):
        result = extract_answer_from_response("Hello world")
        assert result["extracted_answer"] == "Hello world"

    def test_simpleqa_explicit_type(self):
        result = extract_answer_from_response(
            "Hello world", dataset_type="simpleqa"
        )
        assert result["extracted_answer"] == "Hello world"

    def test_simpleqa_case_insensitive_type(self):
        result = extract_answer_from_response("Hello", dataset_type="SimpleQA")
        assert result["extracted_answer"] == "Hello"

    def test_multiline_response(self):
        text = "Line one\nLine two\nLine three"
        result = extract_answer_from_response(text, dataset_type="simpleqa")
        assert "Line one" in result["extracted_answer"]
        assert "Line three" in result["extracted_answer"]


class TestExtractAnswerBrowseComp:
    """Tests for extract_answer_from_response with browsecomp dataset type."""

    def test_exact_answer_extracted(self):
        response = "Exact Answer: John Smith\nConfidence: 85%"
        result = extract_answer_from_response(
            response, dataset_type="browsecomp"
        )
        assert result["extracted_answer"] == "John Smith"
        assert result["confidence"] == "85"

    def test_exact_answer_at_end_of_string(self):
        response = "Exact Answer: The Eiffel Tower"
        result = extract_answer_from_response(
            response, dataset_type="browsecomp"
        )
        assert result["extracted_answer"] == "The Eiffel Tower"

    def test_no_exact_answer_returns_none_string(self):
        response = "I don't know the answer."
        result = extract_answer_from_response(
            response, dataset_type="browsecomp"
        )
        assert result["extracted_answer"] == "None"

    def test_no_confidence_defaults_to_100(self):
        response = "Exact Answer: Something"
        result = extract_answer_from_response(
            response, dataset_type="browsecomp"
        )
        assert result["confidence"] == "100"

    def test_confidence_extraction(self):
        response = "Exact Answer: X\nConfidence: 42%"
        result = extract_answer_from_response(
            response, dataset_type="browsecomp"
        )
        assert result["confidence"] == "42"

    def test_browsecomp_case_insensitive_type(self):
        response = "Exact Answer: Yes\nConfidence: 90%"
        result = extract_answer_from_response(
            response, dataset_type="BrowseComp"
        )
        assert result["extracted_answer"] == "Yes"

    def test_citations_stripped_before_extraction(self):
        response = "Exact Answer: Paris[1]\nConfidence: 75%"
        result = extract_answer_from_response(
            response, dataset_type="browsecomp"
        )
        assert result["extracted_answer"] == "Paris"

    def test_empty_response_browsecomp(self):
        result = extract_answer_from_response("", dataset_type="browsecomp")
        assert result["extracted_answer"] == "None"
        assert result["confidence"] == "100"

    def test_exact_answer_with_extra_whitespace(self):
        response = "Exact Answer:   spaced out   \nConfidence: 50%"
        result = extract_answer_from_response(
            response, dataset_type="browsecomp"
        )
        assert result["extracted_answer"] == "spaced out"

    def test_exact_answer_followed_by_more_text(self):
        response = "Exact Answer: Alpha\nSome other line\nConfidence: 60%"
        result = extract_answer_from_response(
            response, dataset_type="browsecomp"
        )
        assert result["extracted_answer"] == "Alpha"
        assert result["confidence"] == "60"


class TestGradeSingleResultBrowseComp:
    """Tests for grade_single_result browsecomp regex parsing of grader LLM response."""

    @patch("local_deep_research.benchmarks.graders.get_evaluation_llm")
    def test_browsecomp_correct_yes(self, mock_get_llm):
        mock_llm = MagicMock()
        mock_llm.invoke.return_value = MagicMock(
            content="extracted_final_answer: Paris\nreasoning: Matches reference\ncorrect: yes\nconfidence: 95"
        )
        mock_llm.chat_messages = True
        mock_get_llm.return_value = mock_llm

        result = grade_single_result(
            {
                "problem": "Capital of France?",
                "correct_answer": "Paris",
                "response": "Paris",
            },
            dataset_type="browsecomp",
        )
        assert result["is_correct"] is True
        assert result["extracted_by_grader"] == "Paris"
        assert result["graded_confidence"] == "95"

    @patch("local_deep_research.benchmarks.graders.get_evaluation_llm")
    def test_browsecomp_correct_no(self, mock_get_llm):
        mock_llm = MagicMock()
        mock_llm.invoke.return_value = MagicMock(
            content="extracted_final_answer: London\nreasoning: Wrong city\ncorrect: no\nconfidence: 10"
        )
        mock_llm.chat_messages = True
        mock_get_llm.return_value = mock_llm

        result = grade_single_result(
            {"problem": "Q", "correct_answer": "Paris", "response": "London"},
            dataset_type="browsecomp",
        )
        assert result["is_correct"] is False

    @patch("local_deep_research.benchmarks.graders.get_evaluation_llm")
    def test_browsecomp_no_correct_field_defaults_false(self, mock_get_llm):
        mock_llm = MagicMock()
        mock_llm.invoke.return_value = MagicMock(
            content="extracted_final_answer: Something\nreasoning: Unclear"
        )
        mock_llm.chat_messages = True
        mock_get_llm.return_value = mock_llm

        result = grade_single_result(
            {"problem": "Q", "correct_answer": "A", "response": "R"},
            dataset_type="browsecomp",
        )
        assert result["is_correct"] is False
        assert result["graded_confidence"] == "100"


class TestGradeSingleResultSimpleQA:
    """Tests for grade_single_result simpleqa regex parsing of grader LLM response."""

    @patch("local_deep_research.benchmarks.graders.get_evaluation_llm")
    def test_simpleqa_correct_yes(self, mock_get_llm):
        mock_llm = MagicMock()
        mock_llm.invoke.return_value = MagicMock(
            content="Extracted Answer: 42\nReasoning: Matches exactly\nCorrect: Yes"
        )
        mock_llm.chat_messages = True
        mock_get_llm.return_value = mock_llm

        result = grade_single_result(
            {"problem": "Q", "correct_answer": "42", "response": "42"},
            dataset_type="simpleqa",
        )
        assert result["is_correct"] is True
        assert result["extracted_by_grader"] == "42"
        assert result["graded_confidence"] == "100"

    @patch("local_deep_research.benchmarks.graders.get_evaluation_llm")
    def test_simpleqa_correct_no(self, mock_get_llm):
        mock_llm = MagicMock()
        mock_llm.invoke.return_value = MagicMock(
            content="Extracted Answer: 43\nReasoning: Off by one\nCorrect: No"
        )
        mock_llm.chat_messages = True
        mock_get_llm.return_value = mock_llm

        result = grade_single_result(
            {"problem": "Q", "correct_answer": "42", "response": "43"},
            dataset_type="simpleqa",
        )
        assert result["is_correct"] is False

    @patch("local_deep_research.benchmarks.graders.get_evaluation_llm")
    def test_simpleqa_no_extracted_answer_defaults_none(self, mock_get_llm):
        mock_llm = MagicMock()
        mock_llm.invoke.return_value = MagicMock(
            content="Reasoning: Cannot determine\nCorrect: no"
        )
        mock_llm.chat_messages = True
        mock_get_llm.return_value = mock_llm

        result = grade_single_result(
            {"problem": "Q", "correct_answer": "A", "response": "R"},
            dataset_type="simpleqa",
        )
        assert result["extracted_by_grader"] == "None"

    @patch("local_deep_research.benchmarks.graders.get_evaluation_llm")
    def test_grade_single_result_invoke_exception_returns_error(
        self, mock_get_llm
    ):
        """When the LLM invoke raises, the try/except inside grade_single_result catches it."""
        mock_llm = MagicMock()
        mock_llm.invoke.side_effect = RuntimeError("LLM call failed")
        mock_llm.chat_messages = True
        mock_get_llm.return_value = mock_llm

        result = grade_single_result(
            {"problem": "Q", "correct_answer": "A", "response": "R"},
        )
        assert result["is_correct"] is False
        assert "grading_error" in result
        assert "LLM call failed" in result["grading_error"]
        assert result["graded_confidence"] == "0"


class TestCitationStrippingRegex:
    """Directly test the citation-stripping regex used in extract_answer_from_response."""

    def test_single_digit_citation(self):
        assert re.sub(r"\[\d+\]", "", "text[5]") == "text"

    def test_multi_digit_citation(self):
        assert re.sub(r"\[\d+\]", "", "text[123]more") == "textmore"

    def test_no_citation(self):
        assert (
            re.sub(r"\[\d+\]", "", "no citations here") == "no citations here"
        )

    def test_non_numeric_brackets_preserved(self):
        assert re.sub(r"\[\d+\]", "", "text[abc]") == "text[abc]"


class TestGetEvaluationLlm:
    """Tests for get_evaluation_llm configuration merging logic."""

    @patch("local_deep_research.benchmarks.graders.get_llm")
    def test_default_config_used_when_no_custom(self, mock_get_llm):
        mock_get_llm.return_value = MagicMock()
        get_evaluation_llm()
        mock_get_llm.assert_called_once()
        call_kwargs = mock_get_llm.call_args[1]
        assert call_kwargs["model_name"] == "anthropic/claude-3.7-sonnet"
        assert call_kwargs["temperature"] == 0

    @patch("local_deep_research.benchmarks.graders.get_llm")
    def test_custom_config_overrides_defaults(self, mock_get_llm):
        mock_get_llm.return_value = MagicMock()
        get_evaluation_llm(
            custom_config={"model_name": "gpt-4", "temperature": 0.5}
        )
        call_kwargs = mock_get_llm.call_args[1]
        assert call_kwargs["model_name"] == "gpt-4"
        assert call_kwargs["temperature"] == 0.5

    @patch("local_deep_research.benchmarks.graders.get_llm")
    def test_unsupported_params_filtered_out(self, mock_get_llm):
        mock_get_llm.return_value = MagicMock()
        get_evaluation_llm(custom_config={"max_tokens": 1000, "top_p": 0.9})
        call_kwargs = mock_get_llm.call_args[1]
        assert "max_tokens" not in call_kwargs
        assert "top_p" not in call_kwargs
