"""
High-value pure logic tests for PrecisionExtractionHandler.

Focuses on:
- _identify_question_type() keyword matching edge cases and priority ordering
- _apply_precision_extraction() routing logic for all question types
- answer_patterns regex correctness and boundary matching
- Edge cases: empty strings, overlapping keywords, boundary values
"""

import re

from unittest.mock import MagicMock, patch


def _make_handler():
    """Create a PrecisionExtractionHandler with __init__ bypassed."""
    from local_deep_research.citation_handlers.precision_extraction_handler import (
        PrecisionExtractionHandler,
    )

    with patch.object(
        PrecisionExtractionHandler, "__init__", lambda self, *a, **kw: None
    ):
        handler = PrecisionExtractionHandler()
    # Manually set answer_patterns as __init__ was bypassed
    handler.answer_patterns = {
        "full_name": re.compile(r"\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+){1,4})\b"),
        "year": re.compile(r"\b(19\d{2}|20\d{2})\b"),
        "number": re.compile(r"\b(\d+(?:\.\d+)?)\b"),
        "dimension": re.compile(
            r"(\d+(?:\.\d+)?)\s*(meters?|feet|inches|cm|km|miles?|m|ft|kg|pounds?|lbs?)",
            re.I,
        ),
        "score": re.compile(r"(\d+)\s*[-\u2013]\s*(\d+)"),
        "percentage": re.compile(r"(\d+(?:\.\d+)?)\s*%"),
        "location": re.compile(r"\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)\b"),
    }
    handler.llm = MagicMock()
    return handler


class TestIdentifyQuestionTypePriority:
    """Tests for keyword matching priority in _identify_question_type."""

    def setup_method(self):
        self.handler = _make_handler()

    def test_full_name_takes_priority_over_plain_name(self):
        """'full name' should return 'full_name', not 'name'."""
        result = self.handler._identify_question_type(
            "What is the full name of the president?"
        )
        assert result == "full_name"

    def test_name_of_returns_name_not_full_name(self):
        """'name of' without 'full' should return 'name'."""
        result = self.handler._identify_question_type(
            "What is the name of the CEO?"
        )
        assert result == "name"

    def test_name_check_before_location(self):
        """'who is' should match name even if 'where' appears later in query."""
        result = self.handler._identify_question_type(
            "Who is the person who lives where the river flows?"
        )
        assert result == "name"

    def test_location_before_temporal(self):
        """Location keywords should be checked before temporal keywords."""
        result = self.handler._identify_question_type(
            "Where and when did the event happen?"
        )
        assert result == "location"

    def test_temporal_before_number(self):
        """Temporal keywords should be checked before number keywords."""
        result = self.handler._identify_question_type(
            "When and how many times did this occur?"
        )
        assert result == "temporal"

    def test_number_before_score(self):
        """Number keywords take priority over score keywords."""
        result = self.handler._identify_question_type(
            "How many final results were counted?"
        )
        assert result == "number"

    def test_score_before_dimension(self):
        """Score keywords checked before dimension keywords."""
        result = self.handler._identify_question_type(
            "What was the final outcome of the height contest?"
        )
        assert result == "score"

    def test_empty_query_returns_general(self):
        """Empty string should return 'general'."""
        result = self.handler._identify_question_type("")
        assert result == "general"

    def test_whitespace_only_query_returns_general(self):
        """Whitespace-only query should return 'general'."""
        result = self.handler._identify_question_type("   \t\n  ")
        assert result == "general"

    def test_single_choice_requires_which_prefix_and_one(self):
        """'single_choice' requires query starting with 'which' AND containing 'one'."""
        result = self.handler._identify_question_type(
            "Which one of these is the winner?"
        )
        assert result == "single_choice"

    def test_which_without_one_returns_general(self):
        """'which' without 'one' should not return 'single_choice'."""
        result = self.handler._identify_question_type("Which color is the car?")
        assert result == "general"

    def test_one_without_which_prefix_not_single_choice(self):
        """'one' without starting 'which' should not return 'single_choice'."""
        result = self.handler._identify_question_type("Tell me the one answer")
        assert result == "general"

    def test_dimension_with_long_keyword(self):
        """'long' should trigger dimension type."""
        result = self.handler._identify_question_type("How long is the bridge?")
        assert result == "dimension"


class TestApplyPrecisionExtractionRouting:
    """Tests for _apply_precision_extraction routing to correct sub-method."""

    def setup_method(self):
        self.handler = _make_handler()

    def test_routes_full_name_type(self):
        """full_name type should call _extract_full_name."""
        self.handler._extract_full_name = MagicMock(return_value="routed")
        result = self.handler._apply_precision_extraction(
            "content", "q", "full_name", "src"
        )
        self.handler._extract_full_name.assert_called_once_with(
            "content", "q", "src"
        )
        assert result == "routed"

    def test_routes_name_type(self):
        """name type should call _extract_best_name."""
        self.handler._extract_best_name = MagicMock(return_value="routed")
        result = self.handler._apply_precision_extraction(
            "content", "q", "name", "src"
        )
        self.handler._extract_best_name.assert_called_once_with(
            "content", "q", "src"
        )
        assert result == "routed"

    def test_routes_single_choice_type(self):
        """single_choice type should call _extract_single_answer."""
        self.handler._extract_single_answer = MagicMock(return_value="routed")
        result = self.handler._apply_precision_extraction(
            "content", "q", "single_choice", "src"
        )
        self.handler._extract_single_answer.assert_called_once_with(
            "content", "q", "src"
        )
        assert result == "routed"

    def test_routes_dimension_type(self):
        """dimension type should call _extract_dimension."""
        self.handler._extract_dimension = MagicMock(return_value="routed")
        result = self.handler._apply_precision_extraction(
            "content", "q", "dimension", "src"
        )
        self.handler._extract_dimension.assert_called_once_with(
            "content", "q", "src"
        )
        assert result == "routed"

    def test_routes_score_type(self):
        """score type should call _extract_score."""
        self.handler._extract_score = MagicMock(return_value="routed")
        result = self.handler._apply_precision_extraction(
            "content", "q", "score", "src"
        )
        self.handler._extract_score.assert_called_once_with(
            "content", "q", "src"
        )
        assert result == "routed"

    def test_routes_temporal_type(self):
        """temporal type should call _extract_temporal."""
        self.handler._extract_temporal = MagicMock(return_value="routed")
        result = self.handler._apply_precision_extraction(
            "content", "q", "temporal", "src"
        )
        self.handler._extract_temporal.assert_called_once_with(
            "content", "q", "src"
        )
        assert result == "routed"

    def test_routes_number_type(self):
        """number type should call _extract_number."""
        self.handler._extract_number = MagicMock(return_value="routed")
        result = self.handler._apply_precision_extraction(
            "content", "q", "number", "src"
        )
        self.handler._extract_number.assert_called_once_with(
            "content", "q", "src"
        )
        assert result == "routed"

    def test_general_type_returns_content_unchanged(self):
        """general type should return content without calling any extractor."""
        result = self.handler._apply_precision_extraction(
            "original text", "q", "general", "src"
        )
        assert result == "original text"

    def test_unknown_type_returns_content_unchanged(self):
        """An unrecognized question type should return content as-is."""
        result = self.handler._apply_precision_extraction(
            "original text", "q", "nonsense_type", "src"
        )
        assert result == "original text"

    def test_location_type_returns_content_unchanged(self):
        """location type has no dedicated extractor, so content passes through."""
        result = self.handler._apply_precision_extraction(
            "original text", "q", "location", "src"
        )
        assert result == "original text"


class TestAnswerPatternsRegex:
    """Tests for answer_patterns regex definitions and edge cases."""

    def setup_method(self):
        self.handler = _make_handler()

    def test_year_pattern_rejects_1800s(self):
        """Year pattern should not match years before 1900."""
        matches = self.handler.answer_patterns["year"].findall(
            "It happened in 1895."
        )
        assert "1895" not in matches

    def test_year_pattern_rejects_2100s(self):
        """Year pattern should not match years 2100 and beyond."""
        matches = self.handler.answer_patterns["year"].findall("Set in 2100.")
        assert "2100" not in matches

    def test_year_pattern_boundary_1900(self):
        """Year pattern should match 1900 exactly."""
        matches = self.handler.answer_patterns["year"].findall("Built in 1900.")
        assert "1900" in matches

    def test_year_pattern_boundary_2099(self):
        """Year pattern should match 2099 exactly."""
        matches = self.handler.answer_patterns["year"].findall(
            "Predicted for 2099."
        )
        assert "2099" in matches

    def test_full_name_rejects_single_word(self):
        """Full name pattern requires at least two capitalized words."""
        matches = self.handler.answer_patterns["full_name"].findall(
            "John went home."
        )
        assert "John" not in matches

    def test_full_name_max_five_words(self):
        """Full name pattern allows up to five capitalized words."""
        text = "John Michael William Robert Smith was present."
        matches = self.handler.answer_patterns["full_name"].findall(text)
        assert any(name.count(" ") == 4 for name in matches)

    def test_dimension_pattern_case_insensitive(self):
        """Dimension pattern should match uppercase units like 'Meters'."""
        matches = self.handler.answer_patterns["dimension"].findall(
            "The tower is 324 Meters tall."
        )
        assert len(matches) >= 1
        assert matches[0][0] == "324"

    def test_score_pattern_with_en_dash(self):
        """Score pattern should match scores separated by en-dash."""
        matches = self.handler.answer_patterns["score"].findall(
            "Score was 21\u201314."
        )
        assert ("21", "14") in matches

    def test_percentage_pattern_with_decimal(self):
        """Percentage pattern should match decimal percentages."""
        matches = self.handler.answer_patterns["percentage"].findall(
            "Growth of 12.5% last year."
        )
        assert "12.5" in matches

    def test_number_pattern_does_not_match_empty(self):
        """Number pattern should not match in text without digits."""
        matches = self.handler.answer_patterns["number"].findall(
            "No numbers here at all."
        )
        assert matches == []

    def test_dimension_pattern_matches_decimal_values(self):
        """Dimension pattern should capture decimal measurements."""
        matches = self.handler.answer_patterns["dimension"].findall(
            "Height is 3.14 meters."
        )
        assert ("3.14", "meters") in matches

    def test_location_pattern_matches_multi_word_place(self):
        """Location pattern should match multi-word place names."""
        matches = self.handler.answer_patterns["location"].findall(
            "Located in New York City."
        )
        assert any("New York" in m for m in matches)
