"""
Tests for the BaseConstraintChecker class and ConstraintCheckResult.

Tests cover:
- ConstraintCheckResult dataclass
- BaseConstraintChecker initialization
- Evidence gathering
- Weighted score calculation
"""

from unittest.mock import Mock
from dataclasses import dataclass


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

    name: str
    value: str = ""


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

    value: str
    weight: float = 1.0


class ConcreteConstraintChecker:
    """Concrete implementation for testing the base pattern."""

    def __init__(self, model, evidence_gatherer=None, **kwargs):
        self.model = model
        self.evidence_gatherer = evidence_gatherer

    def check_candidate(self, candidate, constraints):
        return {
            "candidate": candidate,
            "total_score": 0.8,
            "constraint_scores": {},
            "should_reject": False,
            "rejection_reason": None,
            "detailed_results": [],
        }

    def should_reject_candidate(self, candidate, constraint, evidence_data):
        return False, ""

    def _gather_evidence_for_constraint(self, candidate, constraint):
        if self.evidence_gatherer:
            return self.evidence_gatherer(candidate, constraint)
        return []

    def _calculate_weighted_score(self, constraint_scores, weights):
        if not constraint_scores or not weights:
            return 0.0
        return sum(
            s * w for s, w in zip(constraint_scores, weights, strict=False)
        ) / sum(weights)


class TestConstraintCheckResult:
    """Tests for ConstraintCheckResult dataclass."""

    def test_result_dataclass_creation(self):
        """ConstraintCheckResult can be created with all fields."""
        from local_deep_research.advanced_search_system.constraint_checking.base_constraint_checker import (
            ConstraintCheckResult,
        )

        candidate = MockCandidate(name="Test")
        result = ConstraintCheckResult(
            candidate=candidate,
            total_score=0.85,
            constraint_scores={"constraint1": {"score": 0.9}},
            should_reject=False,
            rejection_reason=None,
            detailed_results=[],
        )

        assert result.candidate == candidate
        assert result.total_score == 0.85
        assert result.should_reject is False

    def test_result_with_rejection(self):
        """ConstraintCheckResult can store rejection info."""
        from local_deep_research.advanced_search_system.constraint_checking.base_constraint_checker import (
            ConstraintCheckResult,
        )

        candidate = MockCandidate(name="Rejected")
        result = ConstraintCheckResult(
            candidate=candidate,
            total_score=0.3,
            constraint_scores={},
            should_reject=True,
            rejection_reason="Failed minimum threshold",
            detailed_results=[],
        )

        assert result.should_reject is True
        assert "threshold" in result.rejection_reason.lower()


class TestBaseConstraintCheckerInit:
    """Tests for BaseConstraintChecker initialization."""

    def test_init_stores_model(self):
        """Checker stores the model reference."""
        mock_model = Mock()
        checker = ConcreteConstraintChecker(mock_model)

        assert checker.model is mock_model

    def test_init_stores_evidence_gatherer(self):
        """Checker stores the evidence gatherer function."""
        mock_model = Mock()
        mock_gatherer = Mock()
        checker = ConcreteConstraintChecker(
            mock_model, evidence_gatherer=mock_gatherer
        )

        assert checker.evidence_gatherer is mock_gatherer

    def test_init_without_evidence_gatherer(self):
        """Checker can be initialized without evidence gatherer."""
        mock_model = Mock()
        checker = ConcreteConstraintChecker(mock_model)

        assert checker.evidence_gatherer is None


class TestGatherEvidenceForConstraint:
    """Tests for _gather_evidence_for_constraint method."""

    def test_gather_evidence_calls_gatherer(self):
        """Method calls evidence gatherer with correct args."""
        mock_model = Mock()
        mock_gatherer = Mock(return_value=[{"evidence": "data"}])

        checker = ConcreteConstraintChecker(
            mock_model, evidence_gatherer=mock_gatherer
        )

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

        result = checker._gather_evidence_for_constraint(candidate, constraint)

        mock_gatherer.assert_called_once_with(candidate, constraint)
        assert result == [{"evidence": "data"}]

    def test_gather_evidence_without_gatherer(self):
        """Method returns empty list when no gatherer."""
        mock_model = Mock()
        checker = ConcreteConstraintChecker(mock_model)

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

        result = checker._gather_evidence_for_constraint(candidate, constraint)

        assert result == []


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

    def test_calculate_weighted_score_equal_weights(self):
        """Calculates weighted average with equal weights."""
        mock_model = Mock()
        checker = ConcreteConstraintChecker(mock_model)

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

        result = checker._calculate_weighted_score(scores, weights)

        assert abs(result - 0.6) < 0.01  # Average of 0.8, 0.6, 0.4

    def test_calculate_weighted_score_different_weights(self):
        """Calculates weighted average with different weights."""
        mock_model = Mock()
        checker = ConcreteConstraintChecker(mock_model)

        scores = [1.0, 0.0]
        weights = [2.0, 1.0]

        result = checker._calculate_weighted_score(scores, weights)

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

    def test_calculate_weighted_score_empty_lists(self):
        """Returns 0.0 for empty lists."""
        mock_model = Mock()
        checker = ConcreteConstraintChecker(mock_model)

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

        assert result == 0.0

    def test_calculate_weighted_score_empty_scores(self):
        """Returns 0.0 for empty scores."""
        mock_model = Mock()
        checker = ConcreteConstraintChecker(mock_model)

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

        assert result == 0.0


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

    def test_check_candidate_returns_result(self):
        """check_candidate returns a result dict."""
        mock_model = Mock()
        checker = ConcreteConstraintChecker(mock_model)

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

        result = checker.check_candidate(candidate, constraints)

        assert "candidate" in result
        assert "total_score" in result
        assert "should_reject" in result


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

    def test_should_reject_returns_tuple(self):
        """should_reject_candidate returns (bool, str) tuple."""
        mock_model = Mock()
        checker = ConcreteConstraintChecker(mock_model)

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

        result = checker.should_reject_candidate(candidate, constraint, {})

        assert isinstance(result, tuple)
        assert len(result) == 2
        assert isinstance(result[0], bool)
        assert isinstance(result[1], str)
