"""Tests for counterfactual edges."""

import pytest

from hieromem_core.reasoning.counterfactual import (
    CounterfactualEdgeType,
    CounterfactualEdge,
    CounterfactualChain,
    CounterfactualGraph,
    CounterfactualLinker,
)


class TestCounterfactualEdge:
    def test_to_dict(self) -> None:
        edge = CounterfactualEdge(
            source_episode_id="new",
            target_episode_id="old",
            edge_type=CounterfactualEdgeType.SUPERSEDES,
            confidence=0.9,
            reason="Updated information",
        )

        d = edge.to_dict()
        assert d["source_episode_id"] == "new"
        assert d["target_episode_id"] == "old"
        assert d["edge_type"] == "supersedes"
        assert d["confidence"] == 0.9

    def test_from_dict(self) -> None:
        d = {
            "source_episode_id": "a",
            "target_episode_id": "b",
            "edge_type": "contradicts",
            "confidence": 0.8,
            "reason": "Test",
        }

        edge = CounterfactualEdge.from_dict(d)
        assert edge.source_episode_id == "a"
        assert edge.edge_type == CounterfactualEdgeType.CONTRADICTS

    def test_inverse_contradicts(self) -> None:
        edge = CounterfactualEdge(
            source_episode_id="a",
            target_episode_id="b",
            edge_type=CounterfactualEdgeType.CONTRADICTS,
        )

        inverse = edge.inverse()
        assert inverse.source_episode_id == "b"
        assert inverse.target_episode_id == "a"
        assert inverse.edge_type == CounterfactualEdgeType.CONTRADICTS

    def test_inverse_supersedes_fails(self) -> None:
        edge = CounterfactualEdge(
            source_episode_id="new",
            target_episode_id="old",
            edge_type=CounterfactualEdgeType.SUPERSEDES,
        )

        with pytest.raises(ValueError):
            edge.inverse()  # Supersedes is not invertible


class TestCounterfactualChain:
    def test_total_confidence(self) -> None:
        edges = [
            CounterfactualEdge("a", "b", CounterfactualEdgeType.SUPERSEDES, 0.9),
            CounterfactualEdge("b", "c", CounterfactualEdgeType.SUPERSEDES, 0.8),
        ]

        chain = CounterfactualChain(edges, "a", "c")
        assert chain.length == 2
        assert chain.total_confidence == pytest.approx(0.72)


class TestCounterfactualGraph:
    def test_add_and_get_edges(self) -> None:
        graph = CounterfactualGraph()

        edge = CounterfactualEdge("a", "b", CounterfactualEdgeType.SUPERSEDES)
        graph.add_edge(edge)

        outgoing = graph.get_outgoing("a")
        assert len(outgoing) == 1
        assert outgoing[0].target_episode_id == "b"

        incoming = graph.get_incoming("b")
        assert len(incoming) == 1
        assert incoming[0].source_episode_id == "a"

    def test_remove_edge(self) -> None:
        graph = CounterfactualGraph()

        graph.add_edge(CounterfactualEdge("a", "b", CounterfactualEdgeType.SUPERSEDES))
        graph.add_edge(CounterfactualEdge("a", "c", CounterfactualEdgeType.CONTRADICTS))

        result = graph.remove_edge("a", "b")
        assert result is True

        outgoing = graph.get_outgoing("a")
        assert len(outgoing) == 1
        assert outgoing[0].target_episode_id == "c"

    def test_remove_episode(self) -> None:
        graph = CounterfactualGraph()

        graph.add_edge(CounterfactualEdge("a", "b", CounterfactualEdgeType.SUPERSEDES))
        graph.add_edge(CounterfactualEdge("b", "c", CounterfactualEdgeType.SUPERSEDES))
        graph.add_edge(CounterfactualEdge("d", "b", CounterfactualEdgeType.CONTRADICTS))

        count = graph.remove_episode("b")
        assert count == 3  # 1 outgoing, 2 incoming

        assert graph.get_outgoing("b") == []
        assert graph.get_incoming("b") == []

    def test_get_contradictions(self) -> None:
        graph = CounterfactualGraph()

        graph.add_edge(CounterfactualEdge("a", "b", CounterfactualEdgeType.CONTRADICTS))
        graph.add_edge(CounterfactualEdge("c", "a", CounterfactualEdgeType.CONTRADICTS))

        contradictions = graph.get_contradictions("a")
        assert "b" in contradictions
        assert "c" in contradictions

    def test_supersedes_chain(self) -> None:
        graph = CounterfactualGraph()

        # v1 -> v2 -> v3 (v3 is latest)
        graph.add_edge(CounterfactualEdge("v2", "v1", CounterfactualEdgeType.SUPERSEDES))
        graph.add_edge(CounterfactualEdge("v3", "v2", CounterfactualEdgeType.SUPERSEDES))

        # From v1, latest should be v3
        latest = graph.get_latest_version("v1")
        assert latest == "v3"

        # From v3, original should be v1
        original = graph.get_original_version("v3")
        assert original == "v1"

    def test_find_chain(self) -> None:
        graph = CounterfactualGraph()

        graph.add_edge(CounterfactualEdge("a", "b", CounterfactualEdgeType.SUPERSEDES))
        graph.add_edge(CounterfactualEdge("b", "c", CounterfactualEdgeType.SUPERSEDES))
        graph.add_edge(CounterfactualEdge("c", "d", CounterfactualEdgeType.SUPERSEDES))

        chain = graph.find_chain("a", "d")
        assert chain is not None
        assert chain.length == 3
        assert chain.start_episode_id == "a"
        assert chain.end_episode_id == "d"

    def test_find_chain_no_path(self) -> None:
        graph = CounterfactualGraph()

        graph.add_edge(CounterfactualEdge("a", "b", CounterfactualEdgeType.SUPERSEDES))
        graph.add_edge(CounterfactualEdge("c", "d", CounterfactualEdgeType.SUPERSEDES))

        chain = graph.find_chain("a", "d")
        assert chain is None

    def test_find_chain_with_type_filter(self) -> None:
        graph = CounterfactualGraph()

        graph.add_edge(CounterfactualEdge("a", "b", CounterfactualEdgeType.SUPERSEDES))
        graph.add_edge(CounterfactualEdge("b", "c", CounterfactualEdgeType.CONTRADICTS))

        # Only following SUPERSEDES
        chain = graph.find_chain("a", "c", edge_types={CounterfactualEdgeType.SUPERSEDES})
        assert chain is None

        # Following any type
        chain = graph.find_chain("a", "c")
        assert chain is not None

    def test_serialization(self) -> None:
        graph = CounterfactualGraph()
        graph.add_edge(CounterfactualEdge("a", "b", CounterfactualEdgeType.SUPERSEDES, 0.9))
        graph.add_edge(CounterfactualEdge("b", "c", CounterfactualEdgeType.CONTRADICTS, 0.8))

        # Serialize
        d = graph.to_dict()
        assert len(d["edges"]) == 2

        # Deserialize
        graph2 = CounterfactualGraph.from_dict(d)
        assert len(graph2.get_all_edges()) == 2


class TestCounterfactualLinker:
    def test_link_contradiction(self) -> None:
        linker = CounterfactualLinker()

        edge = linker.link_contradiction("a", "b", confidence=0.9, reason="Test")

        assert edge.edge_type == CounterfactualEdgeType.CONTRADICTS
        assert linker.graph.get_contradictions("a") == ["b"]

    def test_link_supersedes(self) -> None:
        linker = CounterfactualLinker()

        edge = linker.link_supersedes("new", "old", reason="Updated")

        assert edge.edge_type == CounterfactualEdgeType.SUPERSEDES
        assert linker.graph.get_supersedes("new") == ["old"]
        assert linker.graph.get_superseded_by("old") == ["new"]

    def test_link_obsoletes(self) -> None:
        linker = CounterfactualLinker()

        edge = linker.link_obsoletes("new", "obsolete")

        assert edge.edge_type == CounterfactualEdgeType.OBSOLETES

    def test_link_supports(self) -> None:
        linker = CounterfactualLinker()

        edge = linker.link_supports("evidence", "claim")

        assert edge.edge_type == CounterfactualEdgeType.SUPPORTS
