"""
Tests for the StrictChecker class.

Tests cover:
- Initialization with strict thresholds
- NAME_PATTERN constraint handling
- Body part name pattern checking
- Strict evaluation criteria
- LLM prompt generation
- Rejection decisions
"""

from dataclasses import dataclass
from enum import Enum
from unittest.mock import Mock


class MockConstraintType(Enum):
    """Mock constraint type for testing."""

    PROPERTY = "property"
    NAME_PATTERN = "name_pattern"
    EVENT = "event"


@dataclass
class MockCandidate:
    """Mock candidate for testing."""

    name: str
    value: str = ""


@dataclass
class MockConstraint:
    """Mock constraint for testing."""

    value: str
    weight: float = 1.0
    type: MockConstraintType = MockConstraintType.PROPERTY


class TestStrictCheckerInit:
    """Tests for StrictChecker initialization."""

    def test_init_with_defaults(self):
        """Initialize with default strict thresholds."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        checker = StrictChecker(mock_model)

        assert checker.strict_threshold == 0.9
        assert checker.name_pattern_required is True

    def test_init_with_custom_strict_threshold(self):
        """Initialize with custom strict threshold."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        checker = StrictChecker(mock_model, strict_threshold=0.95)

        assert checker.strict_threshold == 0.95

    def test_init_with_name_pattern_not_required(self):
        """Initialize with name_pattern_required False."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        checker = StrictChecker(mock_model, name_pattern_required=False)

        assert checker.name_pattern_required is False

    def test_init_stores_model(self):
        """Initialization stores model reference."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        checker = StrictChecker(mock_model)

        assert checker.model is mock_model

    def test_init_stores_evidence_gatherer(self):
        """Initialization stores evidence gatherer."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        mock_gatherer = Mock()
        checker = StrictChecker(mock_model, evidence_gatherer=mock_gatherer)

        assert checker.evidence_gatherer is mock_gatherer


class TestCheckNamePatternStrictly:
    """Tests for _check_name_pattern_strictly method."""

    def test_finds_body_part_arm(self):
        """Finds 'arm' body part in name."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        checker = StrictChecker(mock_model)

        score = checker._check_name_pattern_strictly(
            "Strong Arm Mountain", "contains body part"
        )

        assert score == 1.0

    def test_finds_body_part_foot(self):
        """Finds 'foot' body part in name."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        checker = StrictChecker(mock_model)

        score = checker._check_name_pattern_strictly(
            "Bigfoot Trail", "body part in name"
        )

        assert score == 1.0

    def test_finds_body_part_heart(self):
        """Finds 'heart' body part in name."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        checker = StrictChecker(mock_model)

        score = checker._check_name_pattern_strictly(
            "Heart Lake", "must contain body part"
        )

        assert score == 1.0

    def test_finds_body_part_knee(self):
        """Finds 'knee' body part in name."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        checker = StrictChecker(mock_model)

        score = checker._check_name_pattern_strictly(
            "Wounded Knee Creek", "body part"
        )

        assert score == 1.0

    def test_no_body_part_returns_zero(self):
        """Returns 0.0 when no body part found."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        checker = StrictChecker(mock_model)

        score = checker._check_name_pattern_strictly(
            "Pine Mountain Trail", "contains body part"
        )

        assert score == 0.0

    def test_body_part_case_insensitive(self):
        """Body part check is case insensitive."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        checker = StrictChecker(mock_model)

        score = checker._check_name_pattern_strictly(
            "HAND CREEK TRAIL", "body part"
        )

        assert score == 1.0

    def test_uses_llm_for_non_body_part_patterns(self):
        """Uses LLM for non-body-part patterns."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "Score: 0.95"
        checker = StrictChecker(mock_model)

        score = checker._check_name_pattern_strictly(
            "Crystal Lake", "name must start with C"
        )

        assert score == 0.95
        mock_model.invoke.assert_called_once()

    def test_llm_pattern_returns_uncertain_on_exception(self):
        """Returns 0.5 (uncertain) when LLM fails for pattern check."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        mock_model.invoke.side_effect = Exception("LLM error")
        checker = StrictChecker(mock_model)

        score = checker._check_name_pattern_strictly(
            "Test Name", "starts with T"
        )

        assert score == 0.5


class TestEvaluateConstraintStrictly:
    """Tests for _evaluate_constraint_strictly method."""

    def test_returns_zero_for_empty_evidence(self):
        """Returns 0.0 when no evidence provided."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        checker = StrictChecker(mock_model)

        candidate = MockCandidate(name="Test")
        constraint = MockConstraint(value="must be valid")

        score = checker._evaluate_constraint_strictly(candidate, constraint, [])

        assert score == 0.0

    def test_uses_name_pattern_check_for_name_pattern_type(self):
        """Uses name pattern check for NAME_PATTERN constraints."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            ConstraintType,
            Constraint,
        )

        mock_model = Mock()
        checker = StrictChecker(mock_model)

        candidate = MockCandidate(name="Heart Mountain")
        constraint = Constraint(
            id="c1",
            type=ConstraintType.NAME_PATTERN,
            description="Contains body part",
            value="body part in name",
        )
        evidence = [{"text": "some evidence"}]

        score = checker._evaluate_constraint_strictly(
            candidate, constraint, evidence
        )

        assert score == 1.0  # Heart is a body part

    def test_uses_llm_for_property_constraints(self):
        """Uses LLM evaluation for PROPERTY constraints."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            ConstraintType,
            Constraint,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.92"
        checker = StrictChecker(mock_model)

        candidate = MockCandidate(name="Test Candidate")
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Must be old",
            value="formed during ice age",
        )
        evidence = [{"text": "Formed during the ice age"}]

        score = checker._evaluate_constraint_strictly(
            candidate, constraint, evidence
        )

        assert score == 0.92

    def test_strict_prompt_mentions_strict(self):
        """LLM prompt emphasizes strict evaluation."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            ConstraintType,
            Constraint,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.8"
        checker = StrictChecker(mock_model)

        candidate = MockCandidate(name="Test")
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="test",
            value="test",
        )
        evidence = [{"text": "evidence"}]

        checker._evaluate_constraint_strictly(candidate, constraint, evidence)

        call_args = mock_model.invoke.call_args[0][0]
        assert "STRICT" in call_args or "strict" in call_args

    def test_truncates_evidence_text(self):
        """Truncates long evidence text in prompt."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            ConstraintType,
            Constraint,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.9"
        checker = StrictChecker(mock_model)

        candidate = MockCandidate(name="Test")
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="test",
            value="test",
        )
        evidence = [{"text": "A" * 1000}]  # Very long text

        checker._evaluate_constraint_strictly(candidate, constraint, evidence)

        # Verify evidence is truncated (uses first 300 chars per evidence, 2 evidences max)
        call_args = mock_model.invoke.call_args[0][0]
        # The code truncates to 300 chars, but may include up to 2 evidence items
        assert call_args.count("A") <= 600  # Max 2 evidence * 300 chars each


class TestShouldRejectCandidate:
    """Tests for should_reject_candidate method."""

    def test_rejects_when_no_evidence(self):
        """Rejects candidate when no evidence available."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        checker = StrictChecker(mock_model)

        candidate = MockCandidate(name="Test")
        constraint = MockConstraint(value="constraint")

        should_reject, reason = checker.should_reject_candidate(
            candidate, constraint, []
        )

        assert should_reject is True
        assert "No evidence" in reason

    def test_rejects_name_pattern_below_095(self):
        """Rejects NAME_PATTERN constraints below 0.95."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            ConstraintType,
            Constraint,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.9"
        checker = StrictChecker(mock_model)

        candidate = MockCandidate(name="Pine Trail")
        constraint = Constraint(
            id="c1",
            type=ConstraintType.NAME_PATTERN,
            description="body part",
            value="contains body part",
        )
        evidence = [{"text": "evidence"}]

        should_reject, reason = checker.should_reject_candidate(
            candidate, constraint, evidence
        )

        assert should_reject is True
        assert "NAME_PATTERN" in reason

    def test_accepts_name_pattern_at_095_or_above(self):
        """Accepts NAME_PATTERN constraints at 0.95 or above."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            ConstraintType,
            Constraint,
        )

        mock_model = Mock()
        checker = StrictChecker(mock_model)

        candidate = MockCandidate(name="Heart Mountain")
        constraint = Constraint(
            id="c1",
            type=ConstraintType.NAME_PATTERN,
            description="body part",
            value="body part",
        )
        evidence = [{"text": "evidence"}]

        should_reject, _ = checker.should_reject_candidate(
            candidate, constraint, evidence
        )

        assert should_reject is False

    def test_rejects_below_strict_threshold(self):
        """Rejects when score below strict threshold."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            ConstraintType,
            Constraint,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.85"  # Below 0.9
        checker = StrictChecker(mock_model)

        candidate = MockCandidate(name="Test")
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="test",
            value="test",
        )
        evidence = [{"text": "evidence"}]

        should_reject, reason = checker.should_reject_candidate(
            candidate, constraint, evidence
        )

        assert should_reject is True
        assert "strict threshold" in reason.lower()

    def test_name_pattern_not_required_uses_standard_threshold(self):
        """When name_pattern_required=False, uses standard 0.9 threshold instead of 0.95."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            ConstraintType,
            Constraint,
        )

        mock_model = Mock()
        # For NAME_PATTERN with body part, it uses _check_name_pattern_strictly
        # which returns 1.0 for "Heart Trail" (has body part)
        checker = StrictChecker(mock_model, name_pattern_required=False)

        # Use a name that has a body part so the pattern check passes
        candidate = MockCandidate(name="Heart Trail")
        constraint = Constraint(
            id="c1",
            type=ConstraintType.NAME_PATTERN,
            description="body part",
            value="body part",
        )
        evidence = [{"text": "evidence"}]

        should_reject, _ = checker.should_reject_candidate(
            candidate, constraint, evidence
        )

        # Should pass - body part "heart" found, returns 1.0 which is >= 0.9
        assert should_reject is False


class TestCheckCandidate:
    """Tests for check_candidate method."""

    def test_returns_constraint_check_result(self):
        """Returns ConstraintCheckResult with all fields."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.95"
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = StrictChecker(mock_model, evidence_gatherer=mock_gatherer)

        candidate = MockCandidate(name="Test")
        constraints = [MockConstraint(value="c1")]

        result = checker.check_candidate(candidate, constraints)

        assert hasattr(result, "total_score")
        assert hasattr(result, "should_reject")
        assert hasattr(result, "constraint_scores")

    def test_total_score_1_when_all_pass_strict(self):
        """Total score is 1.0 when all constraints pass strict threshold."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.95"
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = StrictChecker(mock_model, evidence_gatherer=mock_gatherer)

        candidate = MockCandidate(name="Test")
        constraints = [MockConstraint(value="c1"), MockConstraint(value="c2")]

        result = checker.check_candidate(candidate, constraints)

        assert result.should_reject is False
        assert result.total_score == 1.0

    def test_total_score_0_when_any_fail_strict(self):
        """Total score is 0.0 when any constraint fails strict threshold."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.85"  # Below 0.9
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = StrictChecker(mock_model, evidence_gatherer=mock_gatherer)

        candidate = MockCandidate(name="Test")
        constraints = [MockConstraint(value="c1")]

        result = checker.check_candidate(candidate, constraints)

        assert result.should_reject is True
        assert result.total_score == 0.0

    def test_stores_strict_pass_in_constraint_scores(self):
        """Stores strict_pass boolean in constraint scores."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.92"
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = StrictChecker(mock_model, evidence_gatherer=mock_gatherer)

        candidate = MockCandidate(name="Test")
        constraints = [MockConstraint(value="c1")]

        result = checker.check_candidate(candidate, constraints)

        assert "strict_pass" in result.constraint_scores["c1"]
        assert (
            result.constraint_scores["c1"]["strict_pass"] is True
        )  # 0.92 >= 0.9

    def test_detailed_results_contain_strict_pass(self):
        """Detailed results contain strict_pass field."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.88"
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = StrictChecker(mock_model, evidence_gatherer=mock_gatherer)

        candidate = MockCandidate(name="Test")
        constraints = [MockConstraint(value="c1")]

        result = checker.check_candidate(candidate, constraints)

        assert result.detailed_results[0]["strict_pass"] is False  # 0.88 < 0.9

    def test_records_first_rejection_reason(self):
        """Records first rejection reason when multiple constraints fail."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.5"  # Below threshold
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = StrictChecker(mock_model, evidence_gatherer=mock_gatherer)

        candidate = MockCandidate(name="Test")
        constraints = [MockConstraint(value="c1"), MockConstraint(value="c2")]

        result = checker.check_candidate(candidate, constraints)

        assert result.should_reject is True
        assert result.rejection_reason is not None
        assert "c1" in result.rejection_reason  # First constraint

    def test_uses_custom_strict_threshold(self):
        """Uses custom strict threshold for evaluation."""
        from local_deep_research.advanced_search_system.constraint_checking.strict_checker import (
            StrictChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.8"
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = StrictChecker(
            mock_model, evidence_gatherer=mock_gatherer, strict_threshold=0.75
        )

        candidate = MockCandidate(name="Test")
        constraints = [MockConstraint(value="c1")]

        result = checker.check_candidate(candidate, constraints)

        # 0.8 >= 0.75, so should pass
        assert result.should_reject is False
        assert result.constraint_scores["c1"]["strict_pass"] is True
