"""
Pure-logic tests for BaseConstraintChecker helper methods.

Tests _calculate_weighted_score and _log_constraint_result — no LLM or
search calls.
"""

from unittest.mock import Mock, patch

from local_deep_research.advanced_search_system.candidates.base_candidate import (
    Candidate,
)
from local_deep_research.advanced_search_system.constraint_checking.base_constraint_checker import (
    BaseConstraintChecker,
)
from local_deep_research.advanced_search_system.constraints.base_constraint import (
    Constraint,
    ConstraintType,
)


def _make_checker():
    """Build a minimal BaseConstraintChecker with abstract methods stubbed."""

    # Subclass to implement abstract methods
    class ConcreteChecker(BaseConstraintChecker):
        def check_candidate(self, candidate, constraints):
            pass

        def should_reject_candidate(self, candidate, constraint, evidence_data):
            pass

    model = Mock()
    return ConcreteChecker(model=model)


def _constraint(value="test"):
    return Constraint(
        id="c1",
        type=ConstraintType.PROPERTY,
        description=value,
        value=value,
        weight=1.0,
    )


# ---------------------------------------------------------------------------
# _calculate_weighted_score
# ---------------------------------------------------------------------------


class TestCalculateWeightedScore:
    """Verify weighted average calculation."""

    def test_empty_scores_returns_zero(self):
        """Empty score list returns 0.0."""
        checker = _make_checker()
        assert checker._calculate_weighted_score([], []) == 0.0

    def test_empty_scores_nonempty_weights(self):
        """Empty scores with non-empty weights returns 0.0."""
        checker = _make_checker()
        assert checker._calculate_weighted_score([], [1.0, 2.0]) == 0.0

    def test_nonempty_scores_empty_weights(self):
        """Non-empty scores with empty weights returns 0.0."""
        checker = _make_checker()
        assert checker._calculate_weighted_score([0.5, 0.8], []) == 0.0

    def test_equal_weights(self):
        """Equal weights produce simple average."""
        checker = _make_checker()
        result = checker._calculate_weighted_score([0.8, 0.6], [1.0, 1.0])
        assert abs(result - 0.7) < 1e-9

    def test_different_weights(self):
        """Different weights produce weighted average."""
        checker = _make_checker()
        # (0.8 * 2.0 + 0.4 * 1.0) / (2.0 + 1.0) = 2.0 / 3.0 = 0.667
        result = checker._calculate_weighted_score([0.8, 0.4], [2.0, 1.0])
        assert abs(result - 2.0 / 3.0) < 1e-9

    def test_single_score(self):
        """Single score returns that score."""
        checker = _make_checker()
        result = checker._calculate_weighted_score([0.9], [1.0])
        assert abs(result - 0.9) < 1e-9

    def test_zero_scores(self):
        """All-zero scores return 0.0."""
        checker = _make_checker()
        result = checker._calculate_weighted_score([0.0, 0.0], [1.0, 1.0])
        assert result == 0.0

    def test_mismatched_lengths(self):
        """Shorter list determines pairing (zip behavior)."""
        checker = _make_checker()
        # zip stops at shorter: (0.8*1.0) / (1.0) = 0.8
        result = checker._calculate_weighted_score([0.8, 0.6], [1.0])
        # sum(weights) is 1.0 since zip truncates
        assert abs(result - 0.8) < 1e-9


# ---------------------------------------------------------------------------
# _log_constraint_result
# ---------------------------------------------------------------------------


class TestLogConstraintResult:
    """Verify logging symbols for different score levels."""

    def test_high_score_checkmark(self):
        """Score >= 0.8 logs checkmark symbol."""
        checker = _make_checker()
        candidate = Candidate(name="Test")
        constraint = _constraint("height")
        with patch(
            "local_deep_research.advanced_search_system.constraint_checking.base_constraint_checker.logger"
        ) as mock_logger:
            checker._log_constraint_result(candidate, constraint, 0.85, {})
            call_args = mock_logger.info.call_args[0][0]
            assert "✓" in call_args

    def test_medium_score_circle(self):
        """Score >= 0.5 and < 0.8 logs circle symbol."""
        checker = _make_checker()
        candidate = Candidate(name="Test")
        constraint = _constraint("weight")
        with patch(
            "local_deep_research.advanced_search_system.constraint_checking.base_constraint_checker.logger"
        ) as mock_logger:
            checker._log_constraint_result(candidate, constraint, 0.6, {})
            call_args = mock_logger.info.call_args[0][0]
            assert "○" in call_args

    def test_low_score_cross(self):
        """Score < 0.5 logs cross symbol."""
        checker = _make_checker()
        candidate = Candidate(name="Test")
        constraint = _constraint("color")
        with patch(
            "local_deep_research.advanced_search_system.constraint_checking.base_constraint_checker.logger"
        ) as mock_logger:
            checker._log_constraint_result(candidate, constraint, 0.3, {})
            call_args = mock_logger.info.call_args[0][0]
            assert "✗" in call_args

    def test_includes_candidate_name(self):
        """Log message includes candidate name."""
        checker = _make_checker()
        candidate = Candidate(name="Mount Rainier")
        constraint = _constraint("elevation")
        with patch(
            "local_deep_research.advanced_search_system.constraint_checking.base_constraint_checker.logger"
        ) as mock_logger:
            checker._log_constraint_result(candidate, constraint, 0.7, {})
            call_args = mock_logger.info.call_args[0][0]
            assert "Mount Rainier" in call_args

    def test_includes_constraint_value(self):
        """Log message includes constraint value."""
        checker = _make_checker()
        candidate = Candidate(name="Test")
        constraint = _constraint("over 14000 ft")
        with patch(
            "local_deep_research.advanced_search_system.constraint_checking.base_constraint_checker.logger"
        ) as mock_logger:
            checker._log_constraint_result(candidate, constraint, 0.9, {})
            call_args = mock_logger.info.call_args[0][0]
            assert "over 14000 ft" in call_args

    def test_includes_percentage(self):
        """Log message includes score as percentage."""
        checker = _make_checker()
        candidate = Candidate(name="Test")
        constraint = _constraint("test")
        with patch(
            "local_deep_research.advanced_search_system.constraint_checking.base_constraint_checker.logger"
        ) as mock_logger:
            checker._log_constraint_result(candidate, constraint, 0.75, {})
            call_args = mock_logger.info.call_args[0][0]
            assert "75%" in call_args

    def test_boundary_score_08(self):
        """Exactly 0.8 gets checkmark."""
        checker = _make_checker()
        candidate = Candidate(name="Test")
        constraint = _constraint("test")
        with patch(
            "local_deep_research.advanced_search_system.constraint_checking.base_constraint_checker.logger"
        ) as mock_logger:
            checker._log_constraint_result(candidate, constraint, 0.8, {})
            call_args = mock_logger.info.call_args[0][0]
            assert "✓" in call_args

    def test_boundary_score_05(self):
        """Exactly 0.5 gets circle."""
        checker = _make_checker()
        candidate = Candidate(name="Test")
        constraint = _constraint("test")
        with patch(
            "local_deep_research.advanced_search_system.constraint_checking.base_constraint_checker.logger"
        ) as mock_logger:
            checker._log_constraint_result(candidate, constraint, 0.5, {})
            call_args = mock_logger.info.call_args[0][0]
            assert "○" in call_args
