"""Tests for update_raw() — memory version management.

Covers:
- Basic version creation (new memory + old downgrade + derived_from)
- Pre-computed embedding passthrough
- Tags/metadata not inherited
- Audit trail (both sides)
- Provenance chain (derived_from)
- Error handling (not found, empty content)
- SDK layer (update_raw via TrustMem)
"""
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="old content", user_id="default", **kwargs):
    """Helper: add a memory and return result dict."""
    return handler.add_memory(
        content=content,
        user_id=user_id,
        **kwargs,
    )


# ── Basic version creation ───────────────────────────────────

class TestBasicUpdate:
    def test_creates_new_memory(self, handler):
        old = _add(handler)
        result = handler.update_memory(old["memory_id"], "new content")

        assert result["old_memory_id"] == old["memory_id"]
        assert result["new_memory_id"] != old["memory_id"]
        assert result["new_confidence"] == "high"
        assert result["new_source_trust"] == "verified"

    def test_old_memory_downgraded_to_low(self, handler):
        old = _add(handler, _confidence="high")
        handler.update_memory(old["memory_id"], "new content")

        old_mem = handler._db.get_memory(old["memory_id"])
        assert old_mem["confidence"] == "low"

    def test_new_memory_has_correct_content(self, handler):
        old = _add(handler)
        result = handler.update_memory(old["memory_id"], "updated content")

        new_mem = handler._db.get_memory(result["new_memory_id"])
        assert new_mem["content"] == "updated content"
        assert new_mem["confidence"] == "high"
        assert new_mem["source_trust"] == "verified"

    def test_old_memory_content_unchanged(self, handler):
        old = _add(handler, content="original")
        handler.update_memory(old["memory_id"], "updated")

        old_mem = handler._db.get_memory(old["memory_id"])
        assert old_mem["content"] == "original"


# ── Embedding ────────────────────────────────────────────────

class TestEmbedding:
    def test_precomputed_embedding_used(self, handler):
        old = _add(handler)
        embedding = [0.1] * 768
        result = handler.update_memory(
            old["memory_id"], "new content", embedding=embedding,
        )
        # Verify embedding was stored (search should find it)
        search = handler.search_memory(
            user_id="default", query="new content", limit=5,
        )
        ids = [r["memory_id"] for r in search.get("results", [])]
        assert result["new_memory_id"] in ids

    def test_auto_embedding_when_none(self, handler):
        old = _add(handler)
        result = handler.update_memory(old["memory_id"], "new content")

        # Should still be searchable (auto-generated embedding)
        search = handler.search_memory(
            user_id="default", query="new content", limit=5,
        )
        ids = [r["memory_id"] for r in search.get("results", [])]
        assert result["new_memory_id"] in ids


# ── Tags / Metadata not inherited ────────────────────────────

class TestNoInheritance:
    def test_tags_not_inherited(self, handler):
        old = _add(handler, tags=["budget", "2025"])
        result = handler.update_memory(old["memory_id"], "new content")

        new_mem = handler._db.get_memory(result["new_memory_id"])
        # Tags should be empty/default, NOT ["budget", "2025"]
        assert "budget" not in (new_mem.get("tags") or [])

    def test_metadata_not_inherited(self, handler):
        old = _add(handler, metadata={"dept": "sales"})
        result = handler.update_memory(old["memory_id"], "new content")

        new_mem = handler._db.get_memory(result["new_memory_id"])
        assert new_mem.get("metadata", {}).get("dept") is None

    def test_explicit_tags_used(self, handler):
        old = _add(handler, tags=["old_tag"])
        result = handler.update_memory(
            old["memory_id"], "new content", tags=["new_tag"],
        )

        new_mem = handler._db.get_memory(result["new_memory_id"])
        assert "new_tag" in (new_mem.get("tags") or [])
        assert "old_tag" not in (new_mem.get("tags") or [])

    def test_explicit_metadata_used(self, handler):
        old = _add(handler, metadata={"old_key": "v"})
        result = handler.update_memory(
            old["memory_id"], "new content", metadata={"new_key": "v"},
        )

        new_mem = handler._db.get_memory(result["new_memory_id"])
        assert new_mem.get("metadata", {}).get("new_key") == "v"
        assert new_mem.get("metadata", {}).get("old_key") is None


# ── Provenance (derived_from) ────────────────────────────────

class TestProvenance:
    def test_derived_from_recorded(self, handler):
        old = _add(handler)
        result = handler.update_memory(old["memory_id"], "new content")

        provs = handler._db.get_provenance_by_child(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]["child_memory_id"] == result["new_memory_id"]

    def test_version_chain(self, handler):
        """v1 → v2 → v3 should form a chain of derived_from."""
        v1 = _add(handler, content="v1")
        r2 = handler.update_memory(v1["memory_id"], "v2")
        r3 = handler.update_memory(r2["new_memory_id"], "v3")

        # v2 derived_from v1
        p2 = handler._db.get_provenance_by_child(r2["new_memory_id"])
        assert p2[0]["parent_memory_id"] == v1["memory_id"]

        # v3 derived_from v2
        p3 = handler._db.get_provenance_by_child(r3["new_memory_id"])
        assert p3[0]["parent_memory_id"] == r2["new_memory_id"]


# ── Audit Trail ──────────────────────────────────────────────

class TestAuditTrail:
    def test_old_memory_superseded_event(self, handler):
        old = _add(handler, _confidence="high")
        result = handler.update_memory(
            old["memory_id"], "new content", reason="budget revision",
        )

        trail = handler.get_audit_trail(old["memory_id"])
        superseded = [e for e in trail if e["event_type"] == "superseded"]
        assert len(superseded) == 1
        assert superseded[0]["old_confidence"] == "high"
        assert superseded[0]["new_confidence"] == "low"
        assert superseded[0]["details"]["new_memory_id"] == result["new_memory_id"]
        assert superseded[0]["details"]["reason"] == "budget revision"

    def test_new_memory_update_event(self, handler):
        old = _add(handler)
        result = handler.update_memory(
            old["memory_id"], "new content", reason="revision",
        )

        trail = handler.get_audit_trail(result["new_memory_id"])
        updates = [e for e in trail if e["event_type"] == "update"]
        assert len(updates) == 1
        assert updates[0]["new_confidence"] == "high"
        assert updates[0]["details"]["old_memory_id"] == old["memory_id"]
        assert updates[0]["details"]["reason"] == "revision"

    def test_reason_defaults_to_empty(self, handler):
        old = _add(handler)
        result = handler.update_memory(old["memory_id"], "new content")

        trail = handler.get_audit_trail(old["memory_id"])
        superseded = [e for e in trail if e["event_type"] == "superseded"]
        assert superseded[0]["details"]["reason"] == ""


# ── Error Handling ───────────────────────────────────────────

class TestErrors:
    def test_not_found_raises(self, handler):
        with pytest.raises(ValueError, match="Memory not found"):
            handler.update_memory("nonexistent-id", "new content")

    def test_empty_content_raises(self, handler):
        old = _add(handler)
        with pytest.raises(ValueError, match="content must be a non-empty string"):
            handler.update_memory(old["memory_id"], "")

    def test_whitespace_content_raises(self, handler):
        old = _add(handler)
        with pytest.raises(ValueError, match="content must be a non-empty string"):
            handler.update_memory(old["memory_id"], "   ")


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

class TestSDKUpdateRaw:
    def test_basic_update(self, sdk):
        added = sdk.add_raw(content="old content", user_id="default")
        result = sdk.update_raw(added["memory_id"], "new content")

        assert result["old_memory_id"] == added["memory_id"]
        assert result["new_memory_id"] != added["memory_id"]
        assert result["new_confidence"] == "high"
        assert result["new_source_trust"] == "verified"
        assert result["old_confidence"] == "low"

    def test_with_embedding(self, sdk):
        added = sdk.add_raw(content="old content", user_id="default")
        result = sdk.update_raw(
            added["memory_id"], "new content",
            embedding=[0.5] * 768,
        )
        assert result["new_memory_id"]

    def test_with_tags_and_metadata(self, sdk):
        added = sdk.add_raw(content="old", user_id="default")
        result = sdk.update_raw(
            added["memory_id"], "new",
            tags=["v2"], metadata={"version": "2"},
        )
        assert result["new_memory_id"]

    def test_validation_empty_content(self, sdk):
        added = sdk.add_raw(content="old", user_id="default")
        from trustmem.sdk import ValidationError
        with pytest.raises(ValidationError):
            sdk.update_raw(added["memory_id"], "")

    def test_validation_bad_embedding(self, sdk):
        added = sdk.add_raw(content="old", user_id="default")
        from trustmem.sdk import ValidationError
        with pytest.raises(ValidationError):
            sdk.update_raw(added["memory_id"], "new", embedding="not_a_list")

    def test_reason_in_audit(self, sdk):
        added = sdk.add_raw(content="old", user_id="default")
        result = sdk.update_raw(
            added["memory_id"], "new", reason="policy update",
        )
        trail = sdk.audit_trail(result["old_memory_id"])
        superseded = [e for e in trail if e["event_type"] == "superseded"]
        assert superseded[0]["details"]["reason"] == "policy update"
