"""Tests for contradiction detection."""

from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Dict, Optional
import numpy as np

from hieromem_core.reasoning.contradiction import (
    ContradictionType,
    ResolutionStrategy,
    Contradiction,
    ContradictionResult,
    ContradictionDetector,
    ContradictionResolver,
)


@dataclass
class MockEpisode:
    """Mock episode for testing."""
    id: str
    e: Optional[np.ndarray] = None
    G: Any = None
    trust: float = 1.0
    tocc: datetime = field(default_factory=datetime.utcnow)
    meta: Dict[str, Any] = field(default_factory=dict)


class TestContradictionType:
    def test_enum_values(self) -> None:
        assert ContradictionType.SEMANTIC.value == "semantic"
        assert ContradictionType.TEMPORAL.value == "temporal"
        assert ContradictionType.GRAPH_CONFLICT.value == "graph"
        assert ContradictionType.NEGATION.value == "negation"


class TestContradiction:
    def test_to_dict(self) -> None:
        c = Contradiction(
            contradiction_id="c-1",
            type=ContradictionType.SEMANTIC,
            episode_a_id="ep-1",
            episode_b_id="ep-2",
            confidence=0.9,
            explanation="Test explanation",
        )
        d = c.to_dict()

        assert d["contradiction_id"] == "c-1"
        assert d["type"] == "semantic"
        assert d["episode_a_id"] == "ep-1"
        assert d["confidence"] == 0.9


class TestContradictionResult:
    def test_has_contradictions(self) -> None:
        result = ContradictionResult(episode_id="ep-1")
        assert result.has_contradictions is False

        result.contradictions.append(Contradiction(
            contradiction_id="c-1",
            type=ContradictionType.SEMANTIC,
            episode_a_id="ep-1",
            episode_b_id="ep-2",
            confidence=0.9,
            explanation="Test",
        ))
        assert result.has_contradictions is True

    def test_highest_confidence(self) -> None:
        result = ContradictionResult(episode_id="ep-1")
        assert result.highest_confidence() is None

        result.contradictions = [
            Contradiction("c-1", ContradictionType.SEMANTIC, "a", "b", 0.5, "low"),
            Contradiction("c-2", ContradictionType.SEMANTIC, "a", "c", 0.9, "high"),
            Contradiction("c-3", ContradictionType.SEMANTIC, "a", "d", 0.7, "med"),
        ]
        highest = result.highest_confidence()
        assert highest is not None
        assert highest.contradiction_id == "c-2"
        assert highest.confidence == 0.9


class TestContradictionDetector:
    def test_no_contradictions_different_content(self) -> None:
        detector = ContradictionDetector()

        # Different embeddings, different content
        ep_new = MockEpisode(
            id="new",
            e=np.array([1, 0, 0]),
            meta={"text": "Alice is a CEO"},
        )
        ep_existing = MockEpisode(
            id="existing",
            e=np.array([0, 1, 0]),
            meta={"text": "Bob likes pizza"},
        )

        result = detector.detect_contradictions(ep_new, [ep_existing])
        assert not result.has_contradictions

    def test_detect_negation(self) -> None:
        detector = ContradictionDetector(semantic_threshold=0.8)

        # Similar embeddings but negated content
        ep_new = MockEpisode(
            id="new",
            e=np.array([1, 0, 0]),
            meta={"text": "The project is completed"},
        )
        ep_existing = MockEpisode(
            id="existing",
            e=np.array([0.95, 0.05, 0]),  # Very similar
            meta={"text": "The project is not completed"},
        )

        result = detector.detect_contradictions(ep_new, [ep_existing])
        # Should detect negation
        negations = [c for c in result.contradictions if c.type == ContradictionType.NEGATION]
        assert len(negations) > 0

    def test_detect_antonym(self) -> None:
        detector = ContradictionDetector()

        ep_new = MockEpisode(
            id="new",
            e=np.array([1, 0, 0]),
            meta={"text": "The test result is true"},
        )
        ep_existing = MockEpisode(
            id="existing",
            e=np.array([0.9, 0.1, 0]),  # Similar
            meta={"text": "The test result is false"},
        )

        result = detector.detect_contradictions(ep_new, [ep_existing])
        semantics = [c for c in result.contradictions if c.type == ContradictionType.SEMANTIC]
        assert len(semantics) > 0

    def test_trust_conflict(self) -> None:
        detector = ContradictionDetector(
            semantic_threshold=0.8,
            trust_conflict_threshold=0.3,
        )

        ep_new = MockEpisode(
            id="new",
            e=np.array([1, 0, 0]),
            trust=0.9,
            meta={"text": "Important fact"},
        )
        ep_existing = MockEpisode(
            id="existing",
            e=np.array([0.95, 0.05, 0]),  # Very similar
            trust=0.3,  # Low trust - big difference
            meta={"text": "Important fact"},
        )

        result = detector.detect_contradictions(ep_new, [ep_existing])
        trust_conflicts = [c for c in result.contradictions if c.type == ContradictionType.TRUST_CONFLICT]
        assert len(trust_conflicts) > 0

    def test_find_all_contradictions(self) -> None:
        detector = ContradictionDetector()

        episodes = [
            MockEpisode("ep-1", np.array([1, 0, 0]), meta={"text": "Statement A is true"}),
            MockEpisode("ep-2", np.array([0.9, 0.1, 0]), meta={"text": "Statement A is false"}),
            MockEpisode("ep-3", np.array([0, 1, 0]), meta={"text": "Unrelated content"}),
        ]

        contradictions = detector.find_contradictions(episodes)
        # Should find contradiction between ep-1 and ep-2
        assert len(contradictions) >= 1


class TestContradictionResolver:
    def test_newest_wins(self) -> None:
        resolver = ContradictionResolver(ResolutionStrategy.NEWEST_WINS)

        ep_old = MockEpisode("old", tocc=datetime(2025, 1, 1))
        ep_new = MockEpisode("new", tocc=datetime(2025, 6, 1))

        contradiction = Contradiction(
            "c-1", ContradictionType.SEMANTIC,
            "old", "new", 0.9, "test"
        )

        result = resolver.resolve(contradiction, ep_old, ep_new)
        assert result["action"] == "supersede"
        assert result["winner_id"] == "new"
        assert result["loser_id"] == "old"

    def test_highest_trust_wins(self) -> None:
        resolver = ContradictionResolver(ResolutionStrategy.HIGHEST_TRUST_WINS)

        ep_low = MockEpisode("low", trust=0.3)
        ep_high = MockEpisode("high", trust=0.95)

        contradiction = Contradiction(
            "c-1", ContradictionType.SEMANTIC,
            "low", "high", 0.9, "test"
        )

        result = resolver.resolve(contradiction, ep_low, ep_high)
        assert result["action"] == "supersede"
        assert result["winner_id"] == "high"

    def test_flag_for_review(self) -> None:
        resolver = ContradictionResolver(ResolutionStrategy.FLAG_FOR_REVIEW)

        ep_a = MockEpisode("a")
        ep_b = MockEpisode("b")

        contradiction = Contradiction(
            "c-1", ContradictionType.SEMANTIC,
            "a", "b", 0.9, "test"
        )

        result = resolver.resolve(contradiction, ep_a, ep_b)
        assert result["action"] == "flag"

    def test_keep_both(self) -> None:
        resolver = ContradictionResolver(ResolutionStrategy.KEEP_BOTH)

        ep_a = MockEpisode("a")
        ep_b = MockEpisode("b")

        contradiction = Contradiction(
            "c-1", ContradictionType.SEMANTIC,
            "a", "b", 0.9, "test"
        )

        result = resolver.resolve(contradiction, ep_a, ep_b)
        assert result["action"] == "link"
        assert result["edge_type"] == "contradicts"

    def test_strategy_per_type(self) -> None:
        resolver = ContradictionResolver(ResolutionStrategy.FLAG_FOR_REVIEW)
        resolver.set_strategy(ContradictionType.TRUST_CONFLICT, ResolutionStrategy.HIGHEST_TRUST_WINS)

        ep_low = MockEpisode("low", trust=0.3)
        ep_high = MockEpisode("high", trust=0.95)

        # Trust conflict should use HIGHEST_TRUST_WINS
        trust_contradiction = Contradiction(
            "c-1", ContradictionType.TRUST_CONFLICT,
            "low", "high", 0.9, "test"
        )

        result = resolver.resolve(trust_contradiction, ep_low, ep_high)
        assert result["action"] == "supersede"

        # Semantic should still use FLAG_FOR_REVIEW (default)
        semantic_contradiction = Contradiction(
            "c-2", ContradictionType.SEMANTIC,
            "low", "high", 0.9, "test"
        )

        result = resolver.resolve(semantic_contradiction, ep_low, ep_high)
        assert result["action"] == "flag"

    def test_merge_strategy_prefers_higher_trust(self) -> None:
        """Test that merge strategy uses higher trust episode as primary."""
        resolver = ContradictionResolver(ResolutionStrategy.MERGE)

        ep_low = MockEpisode(
            "low",
            trust=0.3,
            meta={"source": "unreliable", "info": "old_value"},
        )
        ep_high = MockEpisode(
            "high",
            trust=0.95,
            meta={"source": "official", "extra": "data"},
        )

        contradiction = Contradiction(
            "c-1", ContradictionType.SEMANTIC,
            "low", "high", 0.9, "test"
        )

        result = resolver.resolve(contradiction, ep_low, ep_high)

        assert result["action"] == "merge"
        assert result["strategy"] == "merge"
        assert result["primary_id"] == "high"
        assert result["secondary_id"] == "low"

        # Merged meta should have data from both, primary overwrites
        merged = result["merged_meta"]
        assert merged["source"] == "official"  # From primary
        assert merged["extra"] == "data"  # From primary
        assert merged["info"] == "old_value"  # From secondary (not overwritten)
        assert merged["_merged_from"] == ["low", "high"]
        assert merged["_merge_primary"] == "high"
        assert merged["_contradiction_id"] == "c-1"

    def test_merge_strategy_prefers_newer_on_equal_trust(self) -> None:
        """Test that merge strategy uses newer episode when trust is equal."""
        resolver = ContradictionResolver(ResolutionStrategy.MERGE)

        ep_old = MockEpisode(
            "old",
            trust=0.8,
            tocc=datetime(2025, 1, 1),
            meta={"version": "v1"},
        )
        ep_new = MockEpisode(
            "new",
            trust=0.8,  # Same trust
            tocc=datetime(2025, 6, 1),  # Newer
            meta={"version": "v2"},
        )

        contradiction = Contradiction(
            "c-1", ContradictionType.TEMPORAL,
            "old", "new", 0.85, "test"
        )

        result = resolver.resolve(contradiction, ep_old, ep_new)

        assert result["action"] == "merge"
        assert result["primary_id"] == "new"
        assert result["secondary_id"] == "old"
        assert result["merged_meta"]["version"] == "v2"

    def test_merge_tracks_provenance(self) -> None:
        """Test that merge properly tracks provenance metadata."""
        resolver = ContradictionResolver(ResolutionStrategy.MERGE)

        ep_a = MockEpisode("ep-a", trust=0.7, meta={})
        ep_b = MockEpisode("ep-b", trust=0.7, meta={})

        contradiction = Contradiction(
            "contradiction-123", ContradictionType.GRAPH_CONFLICT,
            "ep-a", "ep-b", 0.9, "Graph conflict"
        )

        result = resolver.resolve(contradiction, ep_a, ep_b)

        merged = result["merged_meta"]
        assert "_merged_from" in merged
        assert set(merged["_merged_from"]) == {"ep-a", "ep-b"}
        assert "_merge_primary" in merged
        assert "_contradiction_id" in merged
        assert merged["_contradiction_id"] == "contradiction-123"
