"""
Tests for the DualConfidenceChecker class.

Tests cover:
- Initialization with thresholds
- Dual confidence scoring
- Re-evaluation logic for uncertain results
- LLM pre-screening
- Rejection decisions based on positive/negative evidence
- Weighted score calculation
"""

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


@dataclass
class MockConstraintEvidence:
    """Mock constraint evidence for testing."""

    positive_confidence: float
    negative_confidence: float
    uncertainty: float
    evidence_text: str = ""
    source: str = "test"


class TestDualConfidenceCheckerInit:
    """Tests for DualConfidenceChecker initialization."""

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

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model)

        assert checker.negative_threshold == 0.25
        assert checker.positive_threshold == 0.4
        assert checker.uncertainty_penalty == 0.2
        assert checker.negative_weight == 0.5
        assert checker.uncertainty_threshold == 0.6
        assert checker.max_reevaluations == 2

    def test_init_with_custom_negative_threshold(self):
        """Initialize with custom negative threshold."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model, negative_threshold=0.3)

        assert checker.negative_threshold == 0.3

    def test_init_with_custom_positive_threshold(self):
        """Initialize with custom positive threshold."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model, positive_threshold=0.5)

        assert checker.positive_threshold == 0.5

    def test_init_with_custom_uncertainty_penalty(self):
        """Initialize with custom uncertainty penalty."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model, uncertainty_penalty=0.3)

        assert checker.uncertainty_penalty == 0.3

    def test_init_with_custom_max_reevaluations(self):
        """Initialize with custom max reevaluations."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model, max_reevaluations=5)

        assert checker.max_reevaluations == 5

    def test_init_creates_evidence_analyzer(self):
        """Initializes evidence analyzer."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model)

        assert checker.evidence_analyzer is not None

    def test_init_stores_model(self):
        """Stores model reference."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model)

        assert checker.model is mock_model


class TestShouldEarlyReject:
    """Tests for _should_early_reject method."""

    def test_rejects_high_negative(self):
        """Rejects when negative evidence exceeds threshold."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model, negative_threshold=0.25)

        result = checker._should_early_reject(0.5, 0.3)  # negative > 0.25

        assert result is True

    def test_rejects_low_positive(self):
        """Rejects when positive evidence below threshold."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model, positive_threshold=0.4)

        result = checker._should_early_reject(0.3, 0.1)  # positive < 0.4

        assert result is True

    def test_accepts_good_scores(self):
        """Accepts when scores meet thresholds."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(
            mock_model, negative_threshold=0.25, positive_threshold=0.4
        )

        result = checker._should_early_reject(0.6, 0.1)  # Good scores

        assert result is False


class TestShouldRejectCandidateFromAverages:
    """Tests for should_reject_candidate_from_averages method."""

    def test_rejects_high_negative_evidence(self):
        """Rejects when average negative evidence high."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model, negative_threshold=0.25)

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

        should_reject, reason = checker.should_reject_candidate_from_averages(
            candidate,
            constraint,
            0.5,
            0.3,  # negative > 0.25
        )

        assert should_reject is True
        assert "negative evidence" in reason.lower()

    def test_rejects_low_positive_evidence(self):
        """Rejects when average positive evidence low."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model, positive_threshold=0.4)

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

        should_reject, reason = checker.should_reject_candidate_from_averages(
            candidate,
            constraint,
            0.2,
            0.1,  # positive < 0.4
        )

        assert should_reject is True
        assert "positive evidence" in reason.lower()

    def test_accepts_good_averages(self):
        """Accepts when averages meet thresholds."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model)

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

        should_reject, reason = checker.should_reject_candidate_from_averages(
            candidate,
            constraint,
            0.7,
            0.1,  # Good averages
        )

        assert should_reject is False
        assert reason == ""


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

    def test_returns_false_for_empty_evidence(self):
        """Returns false when no evidence provided."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model)

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

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

        assert should_reject is False
        assert reason == ""

    def test_calculates_averages_from_evidence(self):
        """Calculates averages from dual evidence."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(
            mock_model, negative_threshold=0.25, positive_threshold=0.4
        )

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

        evidence = [
            MockConstraintEvidence(0.8, 0.1, 0.1),
            MockConstraintEvidence(0.6, 0.2, 0.2),
        ]

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

        # Avg positive = 0.7, avg negative = 0.15 - should pass
        assert should_reject is False


class TestLLMPrescreenCandidate:
    """Tests for _llm_prescreen_candidate method."""

    def test_returns_not_rejected_without_query(self):
        """Returns not rejected when no original query."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model)

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

        result = checker._llm_prescreen_candidate(candidate, constraints, None)

        assert result["should_reject"] is False

    def test_accepts_high_quality_answer(self):
        """Accepts answer with high quality score."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        mock_model.generate.return_value = "85"
        checker = DualConfidenceChecker(mock_model)

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

        result = checker._llm_prescreen_candidate(
            candidate, constraints, "What is the answer?"
        )

        assert result["should_reject"] is False

    def test_rejects_low_quality_answer(self):
        """Rejects answer with low quality score."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        mock_model.generate.return_value = "25"
        checker = DualConfidenceChecker(mock_model)

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

        result = checker._llm_prescreen_candidate(
            candidate, constraints, "What is the answer?"
        )

        assert result["should_reject"] is True
        assert "Poor answer quality" in result["reason"]

    def test_accepts_on_parse_failure(self):
        """Accepts by default when score parsing fails."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        mock_model.generate.return_value = "no score here"
        checker = DualConfidenceChecker(mock_model)

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

        result = checker._llm_prescreen_candidate(
            candidate, constraints, "question"
        )

        assert result["should_reject"] is False

    def test_accepts_on_exception(self):
        """Accepts by default when exception occurs."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        mock_model.generate.side_effect = Exception("LLM error")
        checker = DualConfidenceChecker(mock_model)

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

        result = checker._llm_prescreen_candidate(
            candidate, constraints, "question"
        )

        assert result["should_reject"] is False


class TestEvaluateConstraintWithReevaluation:
    """Tests for _evaluate_constraint_with_reevaluation method."""

    def test_returns_high_uncertainty_when_no_evidence(self):
        """Returns high uncertainty when no evidence found."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

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

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

        result = checker._evaluate_constraint_with_reevaluation(
            candidate, constraint
        )

        assert result["uncertainty"] == 1.0
        assert result["positive"] == 0.0
        assert result["negative"] == 0.0

    def test_does_not_reevaluate_when_certain(self):
        """Does not re-evaluate when uncertainty is low."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = (
            "POSITIVE: 0.8\nNEGATIVE: 0.1\nUNCERTAINTY: 0.1"
        )
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = DualConfidenceChecker(
            mock_model,
            evidence_gatherer=mock_gatherer,
            uncertainty_threshold=0.6,
        )

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

        result = checker._evaluate_constraint_with_reevaluation(
            candidate, constraint
        )

        assert result["reevaluation_count"] == 0

    def test_tracks_reevaluation_count(self):
        """Tracks number of re-evaluations."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        # First call: high uncertainty, second call: low uncertainty
        mock_model.invoke.return_value.content = (
            "POSITIVE: 0.7\nNEGATIVE: 0.1\nUNCERTAINTY: 0.2"
        )
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = DualConfidenceChecker(
            mock_model,
            evidence_gatherer=mock_gatherer,
            uncertainty_threshold=0.6,
            max_reevaluations=2,
        )

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

        result = checker._evaluate_constraint_with_reevaluation(
            candidate, constraint
        )

        assert "reevaluation_count" in result


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.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = (
            "POSITIVE: 0.8\nNEGATIVE: 0.1\nUNCERTAINTY: 0.1"
        )
        mock_model.generate.return_value = "75"  # Pre-screen passes
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = DualConfidenceChecker(
            mock_model, evidence_gatherer=mock_gatherer
        )

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

        result = checker.check_candidate(
            candidate, constraints, original_query="test query"
        )

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

    def test_prescreen_rejects_low_quality_answer(self):
        """Pre-screen rejects low quality answers via _llm_prescreen_candidate."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        mock_model.generate.return_value = "20"  # Low quality score
        checker = DualConfidenceChecker(mock_model)

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

        # Test the pre-screen method directly
        result = checker._llm_prescreen_candidate(
            candidate, constraints, "test query"
        )

        assert result["should_reject"] is True
        assert "Poor answer quality" in result["reason"]

    def test_stores_positive_negative_uncertainty(self):
        """Stores positive/negative/uncertainty in constraint scores."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = (
            "POSITIVE: 0.7\nNEGATIVE: 0.1\nUNCERTAINTY: 0.2"
        )
        mock_model.generate.return_value = "80"
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = DualConfidenceChecker(
            mock_model, evidence_gatherer=mock_gatherer
        )

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

        result = checker.check_candidate(
            candidate, constraints, original_query="test"
        )

        scores = result.constraint_scores["c1"]
        assert "positive" in scores
        assert "negative" in scores
        assert "uncertainty" in scores

    def test_calculates_weighted_total_score(self):
        """Calculates weighted total score from constraints."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = (
            "POSITIVE: 0.8\nNEGATIVE: 0.1\nUNCERTAINTY: 0.1"
        )
        mock_model.generate.return_value = "80"
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = DualConfidenceChecker(
            mock_model, evidence_gatherer=mock_gatherer
        )

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

        result = checker.check_candidate(
            candidate, constraints, original_query="test"
        )

        # Should have a weighted score
        assert result.total_score >= 0.0
        assert result.total_score <= 1.0

    def test_sets_score_zero_when_rejected(self):
        """Sets total score to 0 when candidate rejected."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        # High negative evidence
        mock_model.invoke.return_value.content = (
            "POSITIVE: 0.1\nNEGATIVE: 0.8\nUNCERTAINTY: 0.1"
        )
        mock_model.generate.return_value = "80"
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = DualConfidenceChecker(
            mock_model, evidence_gatherer=mock_gatherer
        )

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

        result = checker.check_candidate(
            candidate, constraints, original_query="test"
        )

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

    def test_handles_empty_constraints(self):
        """Handles empty constraints list."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        mock_model.generate.return_value = "80"
        checker = DualConfidenceChecker(mock_model)

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

        result = checker.check_candidate(
            candidate, constraints, original_query="test"
        )

        assert result.total_score == 0.0
        assert result.detailed_results == []

    def test_records_reevaluation_count_in_results(self):
        """Records reevaluation count in detailed results."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        mock_model.invoke.return_value.content = (
            "POSITIVE: 0.7\nNEGATIVE: 0.1\nUNCERTAINTY: 0.2"
        )
        mock_model.generate.return_value = "80"
        mock_gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = DualConfidenceChecker(
            mock_model, evidence_gatherer=mock_gatherer
        )

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

        result = checker.check_candidate(
            candidate, constraints, original_query="test"
        )

        assert "reevaluation_count" in result.detailed_results[0]
        assert "reevaluation_count" in result.constraint_scores["c1"]


class TestLogConstraintResultDetailed:
    """Tests for _log_constraint_result_detailed method."""

    def test_logs_high_score_with_checkmark(self):
        """Logs high scores with checkmark symbol."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model)

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

        # Should not raise
        checker._log_constraint_result_detailed(
            candidate, constraint, 0.9, 0.8, 0.1, 0.1, 0
        )

    def test_logs_medium_score_with_circle(self):
        """Logs medium scores with circle symbol."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model)

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

        # Should not raise
        checker._log_constraint_result_detailed(
            candidate, constraint, 0.6, 0.5, 0.2, 0.3, 1
        )

    def test_logs_low_score_with_x(self):
        """Logs low scores with X symbol."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model)

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

        # Should not raise
        checker._log_constraint_result_detailed(
            candidate, constraint, 0.3, 0.2, 0.5, 0.3, 2
        )


class TestCalculateWeightedScore:
    """Tests for _calculate_weighted_score method."""

    def test_calculates_weighted_average(self):
        """Calculates weighted average correctly."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model)

        scores = [0.8, 0.4]
        weights = [2.0, 1.0]

        result = checker._calculate_weighted_score(scores, weights)

        # (0.8 * 2.0 + 0.4 * 1.0) / 3.0 = 2.0 / 3.0 = 0.667
        assert abs(result - 0.667) < 0.01

    def test_handles_empty_scores(self):
        """Handles empty score list."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model)

        result = checker._calculate_weighted_score([], [])

        assert result == 0.0

    def test_handles_equal_weights(self):
        """Calculates simple average for equal weights."""
        from local_deep_research.advanced_search_system.constraint_checking.dual_confidence_checker import (
            DualConfidenceChecker,
        )

        mock_model = Mock()
        checker = DualConfidenceChecker(mock_model)

        scores = [0.9, 0.5, 0.1]
        weights = [1.0, 1.0, 1.0]

        result = checker._calculate_weighted_score(scores, weights)

        # Simple average: 0.5
        assert abs(result - 0.5) < 0.01
