"""
Extended tests for EvidenceAnalyzer - Dual confidence evidence analysis.

Tests cover:
- ConstraintEvidence dataclass
- EvidenceAnalyzer initialization
- analyze_evidence_dual_confidence method
- Score extraction
- evaluate_evidence_list method
- Normalization
- Edge cases

These tests import and test the ACTUAL EvidenceAnalyzer class with mocked LLM.
"""

from unittest.mock import MagicMock


from local_deep_research.advanced_search_system.constraint_checking.evidence_analyzer import (
    ConstraintEvidence,
    EvidenceAnalyzer,
)
from local_deep_research.advanced_search_system.constraints.base_constraint import (
    Constraint,
    ConstraintType,
)


class TestConstraintEvidenceDataclass:
    """Tests for ConstraintEvidence dataclass."""

    def test_has_positive_confidence(self):
        """ConstraintEvidence should have positive_confidence field."""
        evidence = ConstraintEvidence(
            positive_confidence=0.7,
            negative_confidence=0.2,
            uncertainty=0.1,
            evidence_text="text",
            source="search",
        )
        assert evidence.positive_confidence == 0.7

    def test_has_negative_confidence(self):
        """ConstraintEvidence should have negative_confidence field."""
        evidence = ConstraintEvidence(
            positive_confidence=0.3,
            negative_confidence=0.5,
            uncertainty=0.2,
            evidence_text="text",
            source="search",
        )
        assert evidence.negative_confidence == 0.5

    def test_has_uncertainty(self):
        """ConstraintEvidence should have uncertainty field."""
        evidence = ConstraintEvidence(
            positive_confidence=0.4,
            negative_confidence=0.1,
            uncertainty=0.5,
            evidence_text="text",
            source="search",
        )
        assert evidence.uncertainty == 0.5

    def test_has_evidence_text(self):
        """ConstraintEvidence should have evidence_text field."""
        evidence = ConstraintEvidence(
            positive_confidence=0.5,
            negative_confidence=0.3,
            uncertainty=0.2,
            evidence_text="This is evidence text",
            source="search",
        )
        assert evidence.evidence_text == "This is evidence text"

    def test_has_source(self):
        """ConstraintEvidence should have source field."""
        evidence = ConstraintEvidence(
            positive_confidence=0.5,
            negative_confidence=0.3,
            uncertainty=0.2,
            evidence_text="text",
            source="web_search",
        )
        assert evidence.source == "web_search"

    def test_scores_sum_to_one(self):
        """Confidence scores should approximately sum to 1.0."""
        evidence = ConstraintEvidence(
            positive_confidence=0.5,
            negative_confidence=0.3,
            uncertainty=0.2,
            evidence_text="text",
            source="search",
        )

        total = (
            evidence.positive_confidence
            + evidence.negative_confidence
            + evidence.uncertainty
        )
        assert abs(total - 1.0) < 0.001


class TestEvidenceAnalyzerInitialization:
    """Tests for EvidenceAnalyzer initialization."""

    def test_model_assignment(self):
        """Should assign model on initialization."""
        mock_model = MagicMock()
        analyzer = EvidenceAnalyzer(model=mock_model)
        assert analyzer.model == mock_model

    def test_accepts_langchain_model(self):
        """Should accept LangChain BaseChatModel."""
        mock_model = MagicMock()
        mock_model.invoke = MagicMock(return_value=MagicMock(content="test"))

        analyzer = EvidenceAnalyzer(model=mock_model)
        assert analyzer.model is not None


class TestAnalyzeEvidenceDualConfidence:
    """Tests for analyze_evidence_dual_confidence method."""

    def test_extracts_text_from_evidence(self):
        """Should extract text from evidence dict."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.6\nNEGATIVE: 0.2\nUNCERTAINTY: 0.2"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test constraint",
            value="test value",
        )
        evidence = {"text": "This is evidence text", "source": "search"}

        result = analyzer.analyze_evidence_dual_confidence(evidence, constraint)

        assert isinstance(result, ConstraintEvidence)
        assert result.evidence_text == "This is evidence text"

    def test_handles_missing_text(self):
        """Should handle missing text field."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.5\nNEGATIVE: 0.3\nUNCERTAINTY: 0.2"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence = {"source": "search"}  # No text field

        result = analyzer.analyze_evidence_dual_confidence(evidence, constraint)

        assert result.evidence_text == ""

    def test_truncates_long_text_in_evidence_result(self):
        """Should truncate evidence_text to 500 chars in result."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.5\nNEGATIVE: 0.3\nUNCERTAINTY: 0.2"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        long_text = "x" * 1000
        evidence = {"text": long_text, "source": "search"}

        result = analyzer.analyze_evidence_dual_confidence(evidence, constraint)

        assert len(result.evidence_text) == 500

    def test_normalizes_scores(self):
        """Should normalize scores to sum to 1.0."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        # Non-normalized scores
        mock_response.content = "POSITIVE: 0.6\nNEGATIVE: 0.3\nUNCERTAINTY: 0.3"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence = {"text": "test", "source": "search"}

        result = analyzer.analyze_evidence_dual_confidence(evidence, constraint)

        total = (
            result.positive_confidence
            + result.negative_confidence
            + result.uncertainty
        )
        assert abs(total - 1.0) < 0.001

    def test_default_high_uncertainty_on_zero_total(self):
        """Should default to high uncertainty when parsing returns zeros."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        # Return valid format but low confidence values - these get normalized
        mock_response.content = "POSITIVE: 0.1\nNEGATIVE: 0.1\nUNCERTAINTY: 0.1"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence = {"text": "test", "source": "search"}

        result = analyzer.analyze_evidence_dual_confidence(evidence, constraint)

        # Scores get normalized to sum to 1.0
        total = (
            result.positive_confidence
            + result.negative_confidence
            + result.uncertainty
        )
        assert abs(total - 1.0) < 0.001

    def test_uses_default_source(self):
        """Should use default source when not provided."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.5\nNEGATIVE: 0.3\nUNCERTAINTY: 0.2"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence = {"text": "test"}  # No source

        result = analyzer.analyze_evidence_dual_confidence(evidence, constraint)

        assert result.source == "search"

    def test_handles_exception_with_defaults(self):
        """Should return default values on exception."""
        mock_model = MagicMock()
        mock_model.invoke = MagicMock(side_effect=Exception("API Error"))

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence = {"text": "test", "source": "search"}

        result = analyzer.analyze_evidence_dual_confidence(evidence, constraint)

        assert result.positive_confidence == 0.1
        assert result.negative_confidence == 0.1
        assert result.uncertainty == 0.8


class TestExtractScore:
    """Tests for _extract_score method."""

    def test_extracts_positive_score(self):
        """Should extract POSITIVE score."""
        mock_model = MagicMock()
        analyzer = EvidenceAnalyzer(model=mock_model)

        text = "POSITIVE: 0.7\nNEGATIVE: 0.2\nUNCERTAINTY: 0.1"
        score = analyzer._extract_score(text, "POSITIVE")

        assert score == 0.7

    def test_extracts_negative_score(self):
        """Should extract NEGATIVE score."""
        mock_model = MagicMock()
        analyzer = EvidenceAnalyzer(model=mock_model)

        text = "POSITIVE: 0.7\nNEGATIVE: 0.2\nUNCERTAINTY: 0.1"
        score = analyzer._extract_score(text, "NEGATIVE")

        assert score == 0.2

    def test_extracts_uncertainty_score(self):
        """Should extract UNCERTAINTY score."""
        mock_model = MagicMock()
        analyzer = EvidenceAnalyzer(model=mock_model)

        text = "POSITIVE: 0.7\nNEGATIVE: 0.2\nUNCERTAINTY: 0.1"
        score = analyzer._extract_score(text, "UNCERTAINTY")

        assert score == 0.1

    def test_handles_bracketed_score(self):
        """Should handle score in brackets."""
        mock_model = MagicMock()
        analyzer = EvidenceAnalyzer(model=mock_model)

        text = "POSITIVE: [0.8]"
        score = analyzer._extract_score(text, "POSITIVE")

        assert score == 0.8

    def test_case_insensitive_matching(self):
        """Should match labels case-insensitively."""
        mock_model = MagicMock()
        analyzer = EvidenceAnalyzer(model=mock_model)

        text = "positive: 0.6"
        score = analyzer._extract_score(text, "POSITIVE")

        assert score == 0.6

    def test_returns_default_on_no_match(self):
        """Should return default score on no match."""
        mock_model = MagicMock()
        analyzer = EvidenceAnalyzer(model=mock_model)

        text = "No scores here"
        score = analyzer._extract_score(text, "POSITIVE")

        assert score == 0.1

    def test_handles_integer_score(self):
        """Should handle integer score."""
        mock_model = MagicMock()
        analyzer = EvidenceAnalyzer(model=mock_model)

        text = "POSITIVE: 1"
        score = analyzer._extract_score(text, "POSITIVE")

        assert score == 1.0

    def test_returns_default_for_value_error(self):
        """Should return default when float() raises ValueError (PR #2022).

        PR #2022 changed bare `except:` to `except ValueError:` to
        specifically catch float conversion errors.
        """
        mock_model = MagicMock()
        analyzer = EvidenceAnalyzer(model=mock_model)

        # The regex (\d*\.?\d+) normally matches valid float strings,
        # but test that the ValueError path works by using a label
        # that won't match, verifying the default return.
        text = "POSITIVE: not_a_number"
        score = analyzer._extract_score(text, "POSITIVE")
        assert score == 0.1  # Default low score

    def test_handles_empty_string_input(self):
        """Should return default for empty string input."""
        mock_model = MagicMock()
        analyzer = EvidenceAnalyzer(model=mock_model)

        score = analyzer._extract_score("", "POSITIVE")
        assert score == 0.1


class TestEvaluateEvidenceList:
    """Tests for evaluate_evidence_list method."""

    def test_returns_penalty_for_empty_list(self):
        """Should return 0.5 - penalty for empty list."""
        mock_model = MagicMock()
        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )

        score = analyzer.evaluate_evidence_list([], constraint)

        assert score == 0.3  # 0.5 - 0.2 (default penalty)

    def test_calculates_average_scores(self):
        """Should calculate average scores correctly."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.7\nNEGATIVE: 0.2\nUNCERTAINTY: 0.1"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence_list = [
            {"text": "Evidence 1", "source": "s1"},
            {"text": "Evidence 2", "source": "s2"},
        ]

        score = analyzer.evaluate_evidence_list(evidence_list, constraint)

        # Score is calculated from the evidence
        assert isinstance(score, float)
        assert 0.0 <= score <= 1.0

    def test_applies_uncertainty_penalty(self):
        """Should apply uncertainty penalty."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.3\nNEGATIVE: 0.1\nUNCERTAINTY: 0.6"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence_list = [{"text": "High uncertainty", "source": "s1"}]

        score = analyzer.evaluate_evidence_list(evidence_list, constraint)

        # High uncertainty should result in lower score
        assert score < 0.5

    def test_applies_negative_weight(self):
        """Should apply negative weight in calculation."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.5\nNEGATIVE: 0.4\nUNCERTAINTY: 0.1"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence_list = [{"text": "Some negative evidence", "source": "s1"}]

        score = analyzer.evaluate_evidence_list(evidence_list, constraint)

        # Negative evidence should reduce score
        assert isinstance(score, float)
        assert 0.0 <= score <= 1.0

    def test_clamps_score_to_zero(self):
        """Should clamp score to minimum 0.0."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        # Very negative result
        mock_response.content = "POSITIVE: 0.1\nNEGATIVE: 0.8\nUNCERTAINTY: 0.1"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence_list = [{"text": "Very negative", "source": "s1"}]

        score = analyzer.evaluate_evidence_list(evidence_list, constraint)

        assert score >= 0.0

    def test_clamps_score_to_one(self):
        """Should clamp score to maximum 1.0."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = (
            "POSITIVE: 0.95\nNEGATIVE: 0.03\nUNCERTAINTY: 0.02"
        )
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence_list = [{"text": "Very positive", "source": "s1"}]

        score = analyzer.evaluate_evidence_list(evidence_list, constraint)

        assert score <= 1.0

    def test_custom_uncertainty_penalty(self):
        """Should use custom uncertainty penalty."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.5\nNEGATIVE: 0.1\nUNCERTAINTY: 0.4"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence_list = [{"text": "test", "source": "s1"}]

        # Compare with different penalties
        score_default = analyzer.evaluate_evidence_list(
            evidence_list, constraint, uncertainty_penalty=0.2
        )
        score_high = analyzer.evaluate_evidence_list(
            evidence_list, constraint, uncertainty_penalty=0.5
        )

        # Higher penalty should result in lower score
        assert score_high < score_default

    def test_custom_negative_weight(self):
        """Should use custom negative weight."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.5\nNEGATIVE: 0.3\nUNCERTAINTY: 0.2"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence_list = [{"text": "test", "source": "s1"}]

        # Compare with different weights
        score_default = analyzer.evaluate_evidence_list(
            evidence_list, constraint, negative_weight=0.5
        )
        score_high = analyzer.evaluate_evidence_list(
            evidence_list, constraint, negative_weight=1.0
        )

        # Higher negative weight should result in lower score
        assert score_high < score_default


class TestScoreNormalization:
    """Tests for score normalization."""

    def test_normalizes_when_total_exceeds_one(self):
        """Should normalize when total > 1."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.6\nNEGATIVE: 0.5\nUNCERTAINTY: 0.4"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence = {"text": "test", "source": "search"}

        result = analyzer.analyze_evidence_dual_confidence(evidence, constraint)

        total = (
            result.positive_confidence
            + result.negative_confidence
            + result.uncertainty
        )
        assert abs(total - 1.0) < 0.001

    def test_preserves_ratios(self):
        """Should preserve score ratios during normalization."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.6\nNEGATIVE: 0.3\nUNCERTAINTY: 0.3"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence = {"text": "test", "source": "search"}

        result = analyzer.analyze_evidence_dual_confidence(evidence, constraint)

        # Original ratio 0.6 / 0.3 = 2.0
        # Should be preserved
        ratio = result.positive_confidence / result.negative_confidence
        assert abs(ratio - 2.0) < 0.001


class TestDefaultValues:
    """Tests for default value handling."""

    def test_default_uncertainty_penalty(self):
        """Default uncertainty_penalty should be 0.2."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.5\nNEGATIVE: 0.1\nUNCERTAINTY: 0.4"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence_list = [{"text": "test", "source": "s1"}]

        # Call with default parameters
        score = analyzer.evaluate_evidence_list(evidence_list, constraint)

        # Should use 0.2 penalty (default)
        assert isinstance(score, float)

    def test_default_negative_weight(self):
        """Default negative_weight should be 0.5."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.5\nNEGATIVE: 0.3\nUNCERTAINTY: 0.2"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence_list = [{"text": "test", "source": "s1"}]

        # Call with default parameters
        score = analyzer.evaluate_evidence_list(evidence_list, constraint)

        # Should use 0.5 negative weight (default)
        assert isinstance(score, float)


class TestEdgeCases:
    """Tests for edge cases."""

    def test_single_evidence_item(self):
        """Should handle single evidence item."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.8\nNEGATIVE: 0.1\nUNCERTAINTY: 0.1"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence_list = [{"text": "single item", "source": "s1"}]

        score = analyzer.evaluate_evidence_list(evidence_list, constraint)

        assert isinstance(score, float)
        assert 0.0 <= score <= 1.0

    def test_all_high_uncertainty(self):
        """Should handle all high uncertainty evidence."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.1\nNEGATIVE: 0.1\nUNCERTAINTY: 0.8"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence_list = [
            {"text": "uncertain 1", "source": "s1"},
            {"text": "uncertain 2", "source": "s2"},
        ]

        score = analyzer.evaluate_evidence_list(evidence_list, constraint)

        # High uncertainty should result in low score
        assert score < 0.5

    def test_all_positive_evidence(self):
        """Should handle all positive evidence."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = (
            "POSITIVE: 0.9\nNEGATIVE: 0.05\nUNCERTAINTY: 0.05"
        )
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence_list = [
            {"text": "positive 1", "source": "s1"},
            {"text": "positive 2", "source": "s2"},
        ]

        score = analyzer.evaluate_evidence_list(evidence_list, constraint)

        # High positive should result in high score
        assert score > 0.7

    def test_all_negative_evidence(self):
        """Should handle all negative evidence."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.1\nNEGATIVE: 0.8\nUNCERTAINTY: 0.1"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence_list = [
            {"text": "negative 1", "source": "s1"},
            {"text": "negative 2", "source": "s2"},
        ]

        score = analyzer.evaluate_evidence_list(evidence_list, constraint)

        # High negative should result in low score
        assert score < 0.3

    def test_very_long_evidence_text(self):
        """Should handle very long evidence text."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.5\nNEGATIVE: 0.3\nUNCERTAINTY: 0.2"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        long_text = "x" * 10000
        evidence = {"text": long_text, "source": "search"}

        result = analyzer.analyze_evidence_dual_confidence(evidence, constraint)

        assert len(result.evidence_text) == 500  # Truncated

    def test_unicode_evidence_text(self):
        """Should handle unicode evidence text."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.5\nNEGATIVE: 0.3\nUNCERTAINTY: 0.2"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence = {
            "text": "Evidence with unicode: \u65e5\u672c\u8a9e \u4e2d\u6587 \ud55c\uad6d\uc5b4",
            "source": "search",
        }

        result = analyzer.analyze_evidence_dual_confidence(evidence, constraint)

        assert "\u65e5\u672c\u8a9e" in result.evidence_text

    def test_empty_evidence_text(self):
        """Should handle empty evidence text."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.5\nNEGATIVE: 0.3\nUNCERTAINTY: 0.2"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence = {"text": "", "source": "search"}

        result = analyzer.analyze_evidence_dual_confidence(evidence, constraint)

        assert result.evidence_text == ""


class TestLLMIntegration:
    """Tests for LLM integration behavior."""

    def test_invokes_model_with_prompt(self):
        """Should invoke model with formatted prompt."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.5\nNEGATIVE: 0.3\nUNCERTAINTY: 0.2"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test constraint",
            value="test value",
        )
        evidence = {"text": "Test evidence text", "source": "search"}

        analyzer.analyze_evidence_dual_confidence(evidence, constraint)

        # Verify model was called
        mock_model.invoke.assert_called_once()
        # Verify prompt contains key elements
        call_args = mock_model.invoke.call_args[0][0]
        assert "test value" in call_args
        assert "Test evidence text" in call_args

    def test_handles_various_response_formats(self):
        """Should handle various LLM response formats."""
        mock_model = MagicMock()
        analyzer = EvidenceAnalyzer(model=mock_model)
        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="Test",
            value="value",
        )
        evidence = {"text": "test", "source": "search"}

        # Test different response formats
        formats = [
            "POSITIVE: 0.5\nNEGATIVE: 0.3\nUNCERTAINTY: 0.2",
            "POSITIVE: [0.5]\nNEGATIVE: [0.3]\nUNCERTAINTY: [0.2]",
            "positive: 0.5\nnegative: 0.3\nuncertainty: 0.2",
            "Positive: 0.5 Negative: 0.3 Uncertainty: 0.2",
        ]

        for fmt in formats:
            mock_response = MagicMock()
            mock_response.content = fmt
            mock_model.invoke = MagicMock(return_value=mock_response)

            result = analyzer.analyze_evidence_dual_confidence(
                evidence, constraint
            )
            assert isinstance(result, ConstraintEvidence)


class TestConstraintTypeHandling:
    """Tests for handling different constraint types."""

    def test_handles_all_constraint_types(self):
        """Should handle all constraint types."""
        mock_model = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "POSITIVE: 0.6\nNEGATIVE: 0.2\nUNCERTAINTY: 0.2"
        mock_model.invoke = MagicMock(return_value=mock_response)

        analyzer = EvidenceAnalyzer(model=mock_model)
        evidence = {"text": "test", "source": "search"}

        types = [
            ConstraintType.PROPERTY,
            ConstraintType.NAME_PATTERN,
            ConstraintType.EVENT,
            ConstraintType.STATISTIC,
            ConstraintType.TEMPORAL,
            ConstraintType.LOCATION,
            ConstraintType.COMPARISON,
            ConstraintType.EXISTENCE,
        ]

        for ctype in types:
            constraint = Constraint(
                id="c1",
                type=ctype,
                description="Test",
                value="value",
            )

            result = analyzer.analyze_evidence_dual_confidence(
                evidence, constraint
            )
            assert isinstance(result, ConstraintEvidence)
