"""Tests for contradictions(), provenance(), and audit_trail offset.

Covers:
- contradictions(): querying contradiction relationships
- provenance(): querying derived_from links
- audit_trail(): offset pagination
- SDK layer for all three
"""
import pytest

from tests.conftest import MockAIClient


# ── Fixtures ─────────────────────────────────────────────────

@pytest.fixture
def handler():
    from trustmem.handler.persistent import PersistentHandler

    h = PersistentHandler(database_url=":memory:", ai_client=MockAIClient())
    h.reset_stores()
    return h


@pytest.fixture
def sdk(handler):
    from trustmem import TrustMem

    return TrustMem._from_handler(handler)


def _add(handler, content="some content", user_id="default", **kwargs):
    return handler.add_memory(content=content, user_id=user_id, **kwargs)


def _make_contradict(handler, old_content, new_content, user_id="default"):
    """Create two memories with a contradicts provenance edge."""
    import uuid
    from datetime import datetime, timezone

    old = _add(handler, content=old_content, user_id=user_id)
    new = _add(handler, content=new_content, user_id=user_id)
    handler._db.put_provenance({
        "provenance_id": str(uuid.uuid4()),
        "child_memory_id": new["memory_id"],
        "parent_memory_id": old["memory_id"],

        "user_id": user_id,
        "relationship": "contradicts",
        "created_at": datetime.now(timezone.utc).isoformat(),
    })
    return old, new


# ── contradictions() ────────────────────────────────────────

class TestContradictions:
    def test_no_contradictions(self, handler):
        mem = _add(handler)
        result = handler.get_contradictions(mem["memory_id"])
        assert result == []

    def test_new_memory_sees_parent_as_opponent(self, handler):
        old, new = _make_contradict(handler, "budget is 3M", "budget is 5M")
        result = handler.get_contradictions(new["memory_id"])
        assert len(result) == 1
        assert result[0]["opponent_memory_id"] == old["memory_id"]
        assert result[0]["role"] == "loser"

    def test_old_memory_sees_child_as_opponent(self, handler):
        old, new = _make_contradict(handler, "budget is 3M", "budget is 5M")
        result = handler.get_contradictions(old["memory_id"])
        assert len(result) == 1
        assert result[0]["opponent_memory_id"] == new["memory_id"]
        assert result[0]["role"] == "winner"

    def test_opponent_confidence_included(self, handler):
        old, new = _make_contradict(handler, "budget is 3M", "budget is 5M")
        result = handler.get_contradictions(new["memory_id"])
        # opponent is old memory, its confidence is whatever default is
        assert "opponent_confidence" in result[0]

    def test_multiple_contradictions(self, handler):
        import uuid
        from datetime import datetime, timezone

        base = _add(handler, content="base fact")
        for i in range(3):
            contra = _add(handler, content=f"contra {i}")
            handler._db.put_provenance({
                "provenance_id": str(uuid.uuid4()),
                "child_memory_id": contra["memory_id"],
                "parent_memory_id": base["memory_id"],
        
                "user_id": "default",
                "relationship": "contradicts",
                "created_at": datetime.now(timezone.utc).isoformat(),
            })

        result = handler.get_contradictions(base["memory_id"])
        assert len(result) == 3

    def test_limit(self, handler):
        import uuid
        from datetime import datetime, timezone

        base = _add(handler, content="base fact")
        for i in range(5):
            contra = _add(handler, content=f"contra {i}")
            handler._db.put_provenance({
                "provenance_id": str(uuid.uuid4()),
                "child_memory_id": contra["memory_id"],
                "parent_memory_id": base["memory_id"],
        
                "user_id": "default",
                "relationship": "contradicts",
                "created_at": datetime.now(timezone.utc).isoformat(),
            })

        result = handler.get_contradictions(base["memory_id"], limit=2)
        assert len(result) == 2

    def test_offset(self, handler):
        import uuid
        from datetime import datetime, timezone

        base = _add(handler, content="base fact")
        for i in range(5):
            contra = _add(handler, content=f"contra {i}")
            handler._db.put_provenance({
                "provenance_id": str(uuid.uuid4()),
                "child_memory_id": contra["memory_id"],
                "parent_memory_id": base["memory_id"],
        
                "user_id": "default",
                "relationship": "contradicts",
                "created_at": datetime.now(timezone.utc).isoformat(),
            })

        all_results = handler.get_contradictions(base["memory_id"], limit=20)
        offset_results = handler.get_contradictions(base["memory_id"], limit=20, offset=2)
        assert len(offset_results) == len(all_results) - 2

    def test_does_not_include_derived_from(self, handler):
        """contradictions() should only return contradicts, not derived_from."""
        old = _add(handler)
        result = handler.update_memory(old["memory_id"], "updated content")

        # Old memory should have no contradictions (only derived_from)
        contras = handler.get_contradictions(old["memory_id"])
        assert contras == []


# ── provenance() ────────────────────────────────────────────

class TestProvenance:
    def test_no_provenance(self, handler):
        mem = _add(handler)
        result = handler.get_provenance(mem["memory_id"])
        assert result == []

    def test_derived_from_as_child(self, handler):
        old = _add(handler, content="v1")
        result = handler.update_memory(old["memory_id"], "v2")

        provs = handler.get_provenance(result["new_memory_id"])
        assert len(provs) == 1
        assert provs[0]["relationship"] == "derived_from"
        assert provs[0]["parent_memory_id"] == old["memory_id"]
        assert provs[0]["direction"] == "parent"

    def test_derived_from_as_parent(self, handler):
        old = _add(handler, content="v1")
        result = handler.update_memory(old["memory_id"], "v2")

        provs = handler.get_provenance(old["memory_id"])
        assert len(provs) == 1
        assert provs[0]["relationship"] == "derived_from"
        assert provs[0]["child_memory_id"] == result["new_memory_id"]
        assert provs[0]["direction"] == "child"

    def test_version_chain(self, handler):
        v1 = _add(handler, content="v1")
        r2 = handler.update_memory(v1["memory_id"], "v2")
        r3 = handler.update_memory(r2["new_memory_id"], "v3")

        # v2 has both parent (v1) and child (v3) links
        provs = handler.get_provenance(r2["new_memory_id"])
        assert len(provs) == 2

    def test_limit(self, handler):
        v1 = _add(handler, content="v1")
        r2 = handler.update_memory(v1["memory_id"], "v2")
        handler.update_memory(r2["new_memory_id"], "v3")

        # v2 has 2 links, limit to 1
        provs = handler.get_provenance(r2["new_memory_id"], limit=1)
        assert len(provs) == 1

    def test_offset(self, handler):
        v1 = _add(handler, content="v1")
        r2 = handler.update_memory(v1["memory_id"], "v2")
        handler.update_memory(r2["new_memory_id"], "v3")

        # v2 has 2 links, offset 1
        provs = handler.get_provenance(r2["new_memory_id"], limit=20, offset=1)
        assert len(provs) == 1

    def test_does_not_include_contradicts(self, handler):
        """provenance() should only return non-contradicts relationships."""
        old, new = _make_contradict(handler, "fact A", "fact B")

        # Neither should show contradicts in provenance
        assert handler.get_provenance(old["memory_id"]) == []
        assert handler.get_provenance(new["memory_id"]) == []


# ── audit_trail offset ──────────────────────────────────────

class TestAuditTrailOffset:
    def test_offset_skips_records(self, handler):
        mem = _add(handler)
        # add_memory creates 1 audit event; add more
        for i in range(4):
            handler.record_audit_trail(
                memory_id=mem["memory_id"],
                event_type=f"test_{i}",
                triggered_by="test",
            )

        all_trail = handler.get_audit_trail(mem["memory_id"], limit=50)
        offset_trail = handler.get_audit_trail(mem["memory_id"], limit=50, offset=2)
        assert len(offset_trail) == len(all_trail) - 2

    def test_offset_with_limit(self, handler):
        mem = _add(handler)
        for i in range(10):
            handler.record_audit_trail(
                memory_id=mem["memory_id"],
                event_type=f"test_{i}",
                triggered_by="test",
            )

        result = handler.get_audit_trail(mem["memory_id"], limit=3, offset=2)
        assert len(result) == 3


# ── SDK Layer ───────────────────────────────────────────────

class TestSDKContradictions:
    def test_basic(self, sdk, handler):
        old, new = _make_contradict(handler, "fact A", "fact B")
        result = sdk.contradictions(new["memory_id"])
        assert len(result) == 1
        assert result[0]["opponent_memory_id"] == old["memory_id"]

    def test_limit_offset(self, sdk, handler):
        import uuid
        from datetime import datetime, timezone

        base = handler.add_memory(content="base", user_id="default")
        for i in range(5):
            contra = handler.add_memory(content=f"c{i}", user_id="default")
            handler._db.put_provenance({
                "provenance_id": str(uuid.uuid4()),
                "child_memory_id": contra["memory_id"],
                "parent_memory_id": base["memory_id"],
        
                "user_id": "default",
                "relationship": "contradicts",
                "created_at": datetime.now(timezone.utc).isoformat(),
            })

        result = sdk.contradictions(base["memory_id"], limit=2, offset=1)
        assert len(result) == 2


class TestSDKProvenance:
    def test_basic(self, sdk):
        added = sdk.add_raw(content="v1", user_id="default")
        result = sdk.update_raw(added["memory_id"], "v2")

        provs = sdk.provenance(result["new_memory_id"])
        assert len(provs) == 1
        assert provs[0]["relationship"] == "derived_from"

    def test_limit_offset(self, sdk):
        v1 = sdk.add_raw(content="v1", user_id="default")
        r2 = sdk.update_raw(v1["memory_id"], "v2")
        sdk.update_raw(r2["new_memory_id"], "v3")

        # v2 has 2 links
        provs = sdk.provenance(r2["new_memory_id"], limit=1, offset=0)
        assert len(provs) == 1


class TestSDKAuditTrailOffset:
    def test_offset(self, sdk):
        added = sdk.add_raw(content="test", user_id="default")
        all_trail = sdk.audit_trail(added["memory_id"])
        offset_trail = sdk.audit_trail(added["memory_id"], offset=1)
        assert len(offset_trail) == len(all_trail) - 1


# ── get() ───────────────────────────────────────────────────

class TestHandlerGetMemory:
    def test_returns_memory(self, handler):
        added = _add(handler, content="hello world")
        mem = handler.get_memory(added["memory_id"])
        assert mem["memory_id"] == added["memory_id"]
        assert mem["content"] == "hello world"

    def test_not_found_raises(self, handler):
        with pytest.raises(ValueError, match="Memory not found"):
            handler.get_memory("nonexistent-id")

    def test_includes_confidence(self, handler):
        added = _add(handler, _confidence="high")
        mem = handler.get_memory(added["memory_id"])
        assert mem["confidence"] == "high"

    def test_includes_source_trust(self, handler):
        added = _add(handler, _source_trust="verified")
        mem = handler.get_memory(added["memory_id"])
        assert mem["source_trust"] == "verified"


class TestSDKGet:
    def test_basic(self, sdk):
        added = sdk.add_raw(content="test content", user_id="default")
        mem = sdk.get(added["memory_id"])
        assert mem["memory_id"] == added["memory_id"]
        assert mem["content"] == "test content"

    def test_not_found_raises(self, sdk):
        from trustmem.exceptions import NotFoundError
        with pytest.raises(NotFoundError):
            sdk.get("nonexistent-id")

    def test_after_update_raw(self, sdk):
        v1 = sdk.add_raw(content="v1", user_id="default")
        result = sdk.update_raw(v1["memory_id"], "v2")

        new_mem = sdk.get(result["new_memory_id"])
        assert new_mem["content"] == "v2"
        assert new_mem["confidence"] == "high"

        old_mem = sdk.get(result["old_memory_id"])
        assert old_mem["content"] == "v1"
        assert old_mem["confidence"] == "low"

    def test_with_contradictions_workflow(self, sdk, handler):
        """get() + contradictions() workflow: find opponent then inspect."""
        old, new = _make_contradict(handler, "fact A", "fact B")
        contras = sdk.contradictions(new["memory_id"])
        opponent = sdk.get(contras[0]["opponent_memory_id"])
        assert opponent["content"] == "fact A"
