"""
Tests for uncovered code paths in graders.py.

Targets:
- extract_answer_from_response: browsecomp mode, missing matches
- grade_single_result: various LLM interfaces (chat_messages, content attr, callable)
- grade_results / _grade_results_inner: browsecomp extraction, progress callbacks, error handling
- human_evaluation: non-interactive mode
"""

import json
from unittest.mock import Mock, patch


class TestExtractAnswerFromResponse:
    """Tests for extract_answer_from_response."""

    def test_simpleqa_returns_full_response(self):
        """SimpleQA mode returns full response as extracted answer."""
        from local_deep_research.benchmarks.graders import (
            extract_answer_from_response,
        )

        result = extract_answer_from_response(
            "The answer is 42 [1] with citations [2]", "simpleqa"
        )

        assert "42" in result["extracted_answer"]
        assert result["confidence"] == "100"
        # Citations should be stripped
        assert "[1]" not in result["extracted_answer"]
        assert "[2]" not in result["extracted_answer"]

    def test_browsecomp_extracts_answer_and_confidence(self):
        """BrowseComp mode extracts answer and confidence from structured response."""
        from local_deep_research.benchmarks.graders import (
            extract_answer_from_response,
        )

        response = "Exact Answer: The Great Wall of China\nConfidence: 85%"
        result = extract_answer_from_response(response, "browsecomp")

        assert result["extracted_answer"] == "The Great Wall of China"
        assert result["confidence"] == "85"

    def test_browsecomp_missing_answer_field(self):
        """BrowseComp mode returns 'None' when answer field is missing."""
        from local_deep_research.benchmarks.graders import (
            extract_answer_from_response,
        )

        result = extract_answer_from_response(
            "Some unstructured response", "browsecomp"
        )

        assert result["extracted_answer"] == "None"

    def test_browsecomp_missing_confidence(self):
        """BrowseComp mode defaults to 100 when confidence is missing."""
        from local_deep_research.benchmarks.graders import (
            extract_answer_from_response,
        )

        result = extract_answer_from_response(
            "Exact Answer: Something\nNo confidence here", "browsecomp"
        )

        assert result["confidence"] == "100"

    def test_citations_stripped_from_response(self):
        """Citation markers like [1], [23] are removed."""
        from local_deep_research.benchmarks.graders import (
            extract_answer_from_response,
        )

        result = extract_answer_from_response(
            "The answer [1] is 42 [23] exactly.", "simpleqa"
        )

        assert "[1]" not in result["extracted_answer"]
        assert "[23]" not in result["extracted_answer"]
        assert "42" in result["extracted_answer"]


class TestGradeSingleResult:
    """Tests for grade_single_result."""

    @patch("local_deep_research.benchmarks.graders.get_evaluation_llm")
    @patch("local_deep_research.utilities.resource_utils.safe_close")
    def test_grade_single_result_with_content_attr(
        self, mock_close, mock_get_llm
    ):
        """grade_single_result handles LLM response with .content attribute."""
        from local_deep_research.benchmarks.graders import grade_single_result

        mock_llm = Mock()
        mock_response = Mock()
        mock_response.content = (
            "Extracted Answer: 42\nReasoning: Matches exactly\nCorrect: yes"
        )
        mock_llm.invoke.return_value = mock_response
        mock_llm.chat_messages = None  # Has invoke but not chat_messages
        # Remove chat_messages to trigger the else branch
        del mock_llm.chat_messages
        mock_get_llm.return_value = mock_llm

        result = grade_single_result(
            {
                "problem": "What is 6 * 7?",
                "correct_answer": "42",
                "response": "The answer is 42",
            },
            dataset_type="simpleqa",
        )

        assert result["is_correct"] is True
        assert result["extracted_by_grader"] == "42"

    @patch("local_deep_research.benchmarks.graders.get_evaluation_llm")
    @patch("local_deep_research.utilities.resource_utils.safe_close")
    def test_grade_single_result_browsecomp(self, mock_close, mock_get_llm):
        """grade_single_result handles browsecomp extraction format."""
        from local_deep_research.benchmarks.graders import grade_single_result

        mock_llm = Mock()
        mock_response = Mock()
        mock_response.content = (
            "extracted_final_answer: The Great Wall\n"
            "reasoning: The response correctly identifies the structure\n\n"
            "correct: yes\n"
            "confidence: 90"
        )
        mock_llm.invoke.return_value = mock_response
        del mock_llm.chat_messages
        mock_get_llm.return_value = mock_llm

        result = grade_single_result(
            {
                "problem": "What is the longest wall?",
                "correct_answer": "The Great Wall",
                "response": "The Great Wall of China",
            },
            dataset_type="browsecomp",
        )

        assert result["is_correct"] is True
        assert result["extracted_by_grader"] == "The Great Wall"
        assert result["graded_confidence"] == "90"

    @patch("local_deep_research.benchmarks.graders.get_evaluation_llm")
    @patch("local_deep_research.utilities.resource_utils.safe_close")
    def test_grade_single_result_callable_fallback(
        self, mock_close, mock_get_llm
    ):
        """grade_single_result falls back to calling LLM as callable."""
        from local_deep_research.benchmarks.graders import grade_single_result

        mock_llm = Mock()
        # Remove invoke to trigger callable fallback
        del mock_llm.invoke
        mock_llm.return_value = (
            "Extracted Answer: 42\nReasoning: ok\nCorrect: yes"
        )
        mock_get_llm.return_value = mock_llm

        result = grade_single_result(
            {
                "problem": "What?",
                "correct_answer": "42",
                "response": "42",
            },
        )

        assert result["is_correct"] is True

    @patch("local_deep_research.benchmarks.graders.get_evaluation_llm")
    @patch("local_deep_research.utilities.resource_utils.safe_close")
    def test_grade_single_result_exception(self, mock_close, mock_get_llm):
        """grade_single_result handles exceptions gracefully."""
        from local_deep_research.benchmarks.graders import grade_single_result

        mock_llm = Mock()
        mock_llm.invoke.side_effect = Exception("LLM failed")
        del mock_llm.chat_messages
        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 failed" in result["grading_error"]

    @patch("local_deep_research.benchmarks.graders.get_evaluation_llm")
    @patch("local_deep_research.utilities.resource_utils.safe_close")
    def test_grade_single_result_no_correct_match(
        self, mock_close, mock_get_llm
    ):
        """grade_single_result defaults to incorrect when no 'Correct:' match."""
        from local_deep_research.benchmarks.graders import grade_single_result

        mock_llm = Mock()
        mock_response = Mock()
        mock_response.content = "Some response without the expected format"
        mock_llm.invoke.return_value = mock_response
        del mock_llm.chat_messages
        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 result["extracted_by_grader"] == "None"


class TestHumanEvaluation:
    """Tests for human_evaluation non-interactive mode."""

    def test_non_interactive_mode(self, tmp_path):
        """human_evaluation in non-interactive mode marks all as incorrect."""
        from local_deep_research.benchmarks.graders import human_evaluation

        # Create input file
        input_file = tmp_path / "results.jsonl"
        results = [
            {
                "problem": "Q1",
                "correct_answer": "A1",
                "response": "R1",
                "extracted_answer": "E1",
            },
            {
                "problem": "Q2",
                "correct_answer": "A2",
                "response": "R2",
                "extracted_answer": "E2",
            },
        ]
        input_file.write_text("\n".join(json.dumps(r) for r in results))

        output_file = tmp_path / "graded.jsonl"

        graded = human_evaluation(
            str(input_file), str(output_file), interactive=False
        )

        assert len(graded) == 2
        assert all(r["is_correct"] is False for r in graded)
        assert all(r["human_evaluation"] is True for r in graded)
        assert all(
            r["reasoning"] == "Non-interactive evaluation" for r in graded
        )

        # Check output file was written
        assert output_file.exists()
        lines = output_file.read_text().strip().split("\n")
        assert len(lines) == 2

    def test_non_interactive_empty_results(self, tmp_path):
        """human_evaluation handles empty results file."""
        from local_deep_research.benchmarks.graders import human_evaluation

        input_file = tmp_path / "empty.jsonl"
        input_file.write_text("")

        output_file = tmp_path / "graded.jsonl"

        graded = human_evaluation(
            str(input_file), str(output_file), interactive=False
        )

        assert len(graded) == 0


class TestGradeResultsInner:
    """Tests for _grade_results_inner."""

    def test_grade_results_with_progress_callback(self, tmp_path):
        """_grade_results_inner calls progress callback correctly."""
        from local_deep_research.benchmarks.graders import _grade_results_inner

        # Create input file
        input_file = tmp_path / "results.jsonl"
        input_file.write_text(
            json.dumps(
                {
                    "problem": "Q1",
                    "correct_answer": "42",
                    "response": "42",
                }
            )
        )
        output_file = tmp_path / "graded.jsonl"

        mock_llm = Mock()
        mock_response = Mock()
        mock_response.content = (
            "Extracted Answer: 42\nReasoning: ok\nCorrect: yes"
        )
        mock_llm.invoke.return_value = mock_response
        del mock_llm.chat_messages

        callback_calls = []

        def progress_callback(idx, total, data):
            callback_calls.append((idx, total, data))

        result = _grade_results_inner(
            mock_llm,
            str(input_file),
            str(output_file),
            "simpleqa",
            progress_callback,
        )

        assert len(result) == 1
        assert result[0]["is_correct"] is True
        # Should have at least 2 callbacks: grading + graded
        assert len(callback_calls) >= 2

    def test_grade_results_error_handling(self, tmp_path):
        """_grade_results_inner handles grading errors per result."""
        from local_deep_research.benchmarks.graders import _grade_results_inner

        input_file = tmp_path / "results.jsonl"
        input_file.write_text(
            json.dumps({"problem": "Q", "correct_answer": "A", "response": "R"})
        )
        output_file = tmp_path / "graded.jsonl"

        mock_llm = Mock()
        mock_llm.invoke.side_effect = Exception("LLM error")
        del mock_llm.chat_messages

        result = _grade_results_inner(
            mock_llm, str(input_file), str(output_file), "simpleqa", None
        )

        assert len(result) == 1
        assert "grading_error" in result[0]

    def test_grade_results_browsecomp_extraction(self, tmp_path):
        """_grade_results_inner uses browsecomp extraction format."""
        from local_deep_research.benchmarks.graders import _grade_results_inner

        input_file = tmp_path / "results.jsonl"
        input_file.write_text(
            json.dumps(
                {
                    "problem": "What is X?",
                    "correct_answer": "Y",
                    "response": "Y is correct",
                }
            )
        )
        output_file = tmp_path / "graded.jsonl"

        mock_llm = Mock()
        mock_response = Mock()
        mock_response.content = (
            "extracted_final_answer: Y\n"
            "reasoning: Exact match\n\n"
            "correct: yes\n"
            "confidence: 95"
        )
        mock_llm.invoke.return_value = mock_response
        del mock_llm.chat_messages

        result = _grade_results_inner(
            mock_llm, str(input_file), str(output_file), "browsecomp", None
        )

        assert len(result) == 1
        assert result[0]["is_correct"] is True
        assert result[0]["graded_confidence"] == "95"

    def test_grade_results_removes_existing_output(self, tmp_path):
        """_grade_results_inner removes existing output file before writing."""
        from local_deep_research.benchmarks.graders import _grade_results_inner

        input_file = tmp_path / "results.jsonl"
        input_file.write_text(
            json.dumps({"problem": "Q", "correct_answer": "A", "response": "R"})
        )
        output_file = tmp_path / "graded.jsonl"
        output_file.write_text("old data\n")

        mock_llm = Mock()
        mock_response = Mock()
        mock_response.content = (
            "Extracted Answer: A\nReasoning: ok\nCorrect: yes"
        )
        mock_llm.invoke.return_value = mock_response
        del mock_llm.chat_messages

        _grade_results_inner(
            mock_llm, str(input_file), str(output_file), "simpleqa", None
        )

        # Output should not contain old data
        content = output_file.read_text()
        assert "old data" not in content
