"""
Tests for the ThresholdChecker class.

Tests cover:
- Initialization with custom thresholds
- Constraint satisfaction checking
- Candidate rejection decisions
- Evidence handling
- Score calculation
- LLM response parsing
"""

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"


@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 TestThresholdCheckerInit:
    """Tests for ThresholdChecker initialization."""

    def test_init_with_defaults(self):
        """Initialize with default thresholds."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        checker = ThresholdChecker(mock_model)

        assert checker.satisfaction_threshold == 0.7
        assert checker.required_satisfaction_rate == 0.8

    def test_init_with_custom_satisfaction_threshold(self):
        """Initialize with custom satisfaction threshold."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        checker = ThresholdChecker(mock_model, satisfaction_threshold=0.5)

        assert checker.satisfaction_threshold == 0.5

    def test_init_with_custom_required_satisfaction_rate(self):
        """Initialize with custom required satisfaction rate."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        checker = ThresholdChecker(mock_model, required_satisfaction_rate=0.6)

        assert checker.required_satisfaction_rate == 0.6

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

        mock_model = Mock()
        checker = ThresholdChecker(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.threshold_checker import (
            ThresholdChecker,
        )

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

        assert checker.evidence_gatherer is mock_gatherer


class TestCheckConstraintSatisfaction:
    """Tests for _check_constraint_satisfaction method."""

    def test_extracts_score_from_llm_response(self):
        """Extracts score from LLM response."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "Score: 0.85"
        checker = ThresholdChecker(mock_model)

        candidate = MockCandidate(name="Test Candidate")
        constraint = MockConstraint(value="must be valid")
        evidence = [{"text": "Evidence text"}]

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

        assert score == 0.85

    def test_extracts_integer_score(self):
        """Extracts integer score from LLM response."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "1"
        checker = ThresholdChecker(mock_model)

        candidate = MockCandidate(name="Test")
        constraint = MockConstraint(value="constraint")
        evidence = [{"text": "text"}]

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

        assert score == 1.0

    def test_clamps_score_to_max_1(self):
        """Clamps score to maximum 1.0."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "Score: 1.5"
        checker = ThresholdChecker(mock_model)

        candidate = MockCandidate(name="Test")
        constraint = MockConstraint(value="constraint")
        evidence = [{"text": "text"}]

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

        assert score == 1.0

    def test_negative_score_returns_default(self):
        """Negative scores in LLM response are not matched, returns default 0.5."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        # Regex only matches positive numbers, so "-0.5" doesn't match
        mock_model.invoke.return_value.content = "Score: -0.5"
        checker = ThresholdChecker(mock_model)

        candidate = MockCandidate(name="Test")
        constraint = MockConstraint(value="constraint")
        evidence = [{"text": "text"}]

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

        # Returns default 0.5 since regex doesn't match negative numbers
        assert score == 0.5

    def test_returns_default_on_parse_failure(self):
        """Returns 0.5 default when parsing fails."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "no score here"
        checker = ThresholdChecker(mock_model)

        candidate = MockCandidate(name="Test")
        constraint = MockConstraint(value="constraint")
        evidence = [{"text": "text"}]

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

        assert score == 0.5

    def test_returns_default_on_exception(self):
        """Returns 0.5 default on exception."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

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

        candidate = MockCandidate(name="Test")
        constraint = MockConstraint(value="constraint")
        evidence = [{"text": "text"}]

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

        assert score == 0.5

    def test_combines_evidence_texts(self):
        """Combines multiple evidence texts in prompt."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

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

        candidate = MockCandidate(name="Test")
        constraint = MockConstraint(value="constraint")
        evidence = [
            {"text": "Evidence 1"},
            {"text": "Evidence 2"},
            {"text": "Evidence 3"},
        ]

        checker._check_constraint_satisfaction(candidate, constraint, evidence)

        # Verify prompt contains evidence
        call_args = mock_model.invoke.call_args[0][0]
        assert "Evidence 1" in call_args
        assert "Evidence 2" in call_args
        assert "Evidence 3" in call_args

    def test_truncates_evidence_text(self):
        """Truncates long evidence texts."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.7"
        checker = ThresholdChecker(mock_model)

        candidate = MockCandidate(name="Test")
        constraint = MockConstraint(value="constraint")
        evidence = [{"text": "A" * 500}]  # Long text

        checker._check_constraint_satisfaction(candidate, constraint, evidence)

        call_args = mock_model.invoke.call_args[0][0]
        # Evidence should be truncated to 200 chars
        assert len(call_args) < 500 + 200  # Some buffer for prompt


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.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        checker = ThresholdChecker(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_when_score_below_threshold(self):
        """Rejects when satisfaction score below threshold."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.3"  # Below 0.7 default
        checker = ThresholdChecker(mock_model)

        candidate = MockCandidate(name="Test")
        constraint = MockConstraint(value="constraint")
        evidence = [{"text": "some evidence"}]

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

        assert should_reject is True
        assert "not satisfied" in reason

    def test_accepts_when_score_above_threshold(self):
        """Accepts when satisfaction score above threshold."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.85"
        checker = ThresholdChecker(mock_model)

        candidate = MockCandidate(name="Test")
        constraint = MockConstraint(value="constraint")
        evidence = [{"text": "good evidence"}]

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

        assert should_reject is False
        assert reason == ""

    def test_uses_custom_threshold(self):
        """Uses custom satisfaction threshold."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.5"
        checker = ThresholdChecker(mock_model, satisfaction_threshold=0.4)

        candidate = MockCandidate(name="Test")
        constraint = MockConstraint(value="constraint")
        evidence = [{"text": "evidence"}]

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

        assert should_reject is False  # 0.5 >= 0.4


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.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.9"
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = ThresholdChecker(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")
        assert hasattr(result, "detailed_results")

    def test_checks_all_constraints(self):
        """Checks all provided constraints."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

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

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

        result = checker.check_candidate(candidate, constraints)

        assert len(result.detailed_results) == 3
        assert "c1" in result.constraint_scores
        assert "c2" in result.constraint_scores
        assert "c3" in result.constraint_scores

    def test_rejects_when_satisfaction_rate_too_low(self):
        """Rejects when satisfaction rate below threshold."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        # First constraint passes (0.9), others fail (0.3)
        mock_model.invoke.return_value.content = "0.3"
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = ThresholdChecker(
            mock_model,
            evidence_gatherer=mock_gatherer,
            satisfaction_threshold=0.7,
            required_satisfaction_rate=0.8,
        )

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

        result = checker.check_candidate(candidate, constraints)

        # 0/3 constraints satisfied = 0% < 80%
        assert result.should_reject is True
        assert "0/3" in result.rejection_reason

    def test_accepts_when_satisfaction_rate_sufficient(self):
        """Accepts when satisfaction rate meets threshold."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.85"
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = ThresholdChecker(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 False
        assert result.rejection_reason is None

    def test_handles_no_evidence_for_constraint(self):
        """Handles missing evidence for constraint."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        mock_gatherer = Mock(return_value=[])  # No evidence
        checker = ThresholdChecker(mock_model, evidence_gatherer=mock_gatherer)

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

        result = checker.check_candidate(candidate, constraints)

        assert result.constraint_scores["c1"]["total"] == 0.0
        assert result.constraint_scores["c1"]["satisfied"] is False

    def test_calculates_total_score_as_satisfaction_rate(self):
        """Total score equals satisfaction rate when not rejected."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.9"
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = ThresholdChecker(
            mock_model,
            evidence_gatherer=mock_gatherer,
            required_satisfaction_rate=0.5,
        )

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

        result = checker.check_candidate(candidate, constraints)

        # Both satisfied, so 100% satisfaction rate
        assert result.total_score == 1.0

    def test_sets_total_score_zero_when_rejected(self):
        """Total score is zero when rejected."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.3"  # Below threshold
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = ThresholdChecker(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_weight_in_constraint_scores(self):
        """Stores constraint weight in scores."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

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

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

        result = checker.check_candidate(candidate, constraints)

        assert result.constraint_scores["c1"]["weight"] == 2.5

    def test_handles_empty_constraints_list(self):
        """Handles empty constraints list."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        checker = ThresholdChecker(mock_model)

        candidate = MockCandidate(name="Test")
        constraints = []

        result = checker.check_candidate(candidate, constraints)

        assert result.total_score == 0  # 0/0 division handled
        assert result.should_reject is True  # 0 < 0.8


class TestDetailedResults:
    """Tests for detailed_results structure."""

    def test_detailed_results_contains_constraint_info(self):
        """Detailed results contain constraint information."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

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

        candidate = MockCandidate(name="Test")
        constraints = [MockConstraint(value="test constraint", weight=1.5)]

        result = checker.check_candidate(candidate, constraints)

        detail = result.detailed_results[0]
        assert detail["constraint"] == "test constraint"
        assert detail["weight"] == 1.5
        assert detail["type"] == "property"
        assert "score" in detail
        assert "satisfied" in detail

    def test_detailed_results_score_matches_satisfaction_check(self):
        """Score in detailed results matches satisfaction check."""
        from local_deep_research.advanced_search_system.constraint_checking.threshold_checker import (
            ThresholdChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = "0.82"
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = ThresholdChecker(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]["score"] == 0.82
        assert result.detailed_results[0]["satisfied"] is True  # 0.82 >= 0.7
