"""
tests/test_gdpr_forget.py — GDPR forget entity / owner tests (#108).

Coverage:
  1.  forget_entity deletes related claims (subject side)
  2.  forget_entity deletes related claims (object side)
  3.  forget_entity deletes the entity record
  4.  forget_entity dry_run returns correct counts without deleting
  5.  forget_entity unknown entity returns empty result (no error)
  6.  forget_entity wrong owner raises PermissionError
  7.  forget_entity writes audit changelog entry
  8.  forget_entity emits ENTITY_FORGOTTEN event
  9.  forget_owner deletes all claims for the owner
  10. forget_owner deletes all entities for the owner
  11. forget_owner dry_run returns counts without deleting
  12. forget_owner emits MEMORY_DELETED event
  13. forget_owner does NOT delete data for other owners
  14. ForgetResult.to_dict() serialises correctly
"""
from __future__ import annotations

import os
import tempfile
from typing import List
from unittest.mock import AsyncMock, MagicMock

import pytest
import pytest_asyncio

# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest_asyncio.fixture
async def db():
    """Fresh in-memory SQLiteAdapter (no temp files needed)."""
    from cortex.storage.sqlite_adapter import SQLiteAdapter
    adapter = SQLiteAdapter(db_path=":memory:")
    await adapter.initialize()
    yield adapter
    await adapter.close()


@pytest_asyncio.fixture
async def entity_with_claims(db):
    """Create owner='alice', one entity, and three claims referencing it."""
    entity = await db.create_entity(
        canonical_name="Alice Entity",
        entity_type="person",
        owner_id="alice",
    )
    # Claim where entity is subject
    c1 = await db.create_claim(
        subject="Alice Entity",
        predicate="lives_in",
        object_value="Bangkok",
        owner_id="alice",
        subject_entity_id=entity.id,
    )
    # Claim where entity is object
    c2 = await db.create_claim(
        subject="Bob",
        predicate="knows",
        object_value="Alice Entity",
        owner_id="alice",
        object_entity_id=entity.id,
    )
    # Unrelated claim (same owner, different entity)
    other_entity = await db.create_entity(
        canonical_name="Other Person",
        entity_type="person",
        owner_id="alice",
    )
    c3 = await db.create_claim(
        subject="Other Person",
        predicate="lives_in",
        object_value="Singapore",
        owner_id="alice",
        subject_entity_id=other_entity.id,
    )
    return {
        "entity": entity,
        "claim_subject": c1,
        "claim_object": c2,
        "other_claim": c3,
        "other_entity": other_entity,
    }


# ---------------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------------

def _make_mock_emitter():
    emitter = MagicMock()
    emitter.emit = AsyncMock()
    return emitter


# ---------------------------------------------------------------------------
# 1. forget_entity deletes related claims (subject side)
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_forget_entity_deletes_subject_claims(db, entity_with_claims):
    from cortex.tools.gdpr import forget_entity

    entity = entity_with_claims["entity"]
    await forget_entity(db, entity_id=entity.id, owner_id="alice")

    # The claim where entity was subject must be gone
    claim = await db.get_claim(entity_with_claims["claim_subject"].id)
    assert claim is None


# ---------------------------------------------------------------------------
# 2. forget_entity deletes related claims (object side)
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_forget_entity_deletes_object_claims(db, entity_with_claims):
    from cortex.tools.gdpr import forget_entity

    entity = entity_with_claims["entity"]
    await forget_entity(db, entity_id=entity.id, owner_id="alice")

    claim = await db.get_claim(entity_with_claims["claim_object"].id)
    assert claim is None


# ---------------------------------------------------------------------------
# 3. forget_entity deletes entity record
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_forget_entity_deletes_entity_record(db, entity_with_claims):
    from cortex.tools.gdpr import forget_entity

    entity = entity_with_claims["entity"]
    await forget_entity(db, entity_id=entity.id, owner_id="alice")

    fetched = await db.get_entity(entity.id)
    assert fetched is None


# ---------------------------------------------------------------------------
# 4. dry_run returns counts without deleting
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_forget_entity_dry_run_no_delete(db, entity_with_claims):
    from cortex.tools.gdpr import forget_entity

    entity = entity_with_claims["entity"]
    result = await forget_entity(db, entity_id=entity.id, owner_id="alice", dry_run=True)

    assert result.dry_run is True
    assert result.claims_deleted == 2   # c1 + c2 reference this entity
    assert result.entities_deleted == 1

    # Entity must still exist
    fetched = await db.get_entity(entity.id)
    assert fetched is not None

    # Both claims must still exist
    c1 = await db.get_claim(entity_with_claims["claim_subject"].id)
    c2 = await db.get_claim(entity_with_claims["claim_object"].id)
    assert c1 is not None
    assert c2 is not None


# ---------------------------------------------------------------------------
# 5. forget_entity unknown entity returns empty result
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_forget_entity_unknown_entity(db):
    from cortex.tools.gdpr import forget_entity, ForgetResult

    result = await forget_entity(db, entity_id="nonexistent-id", owner_id="alice")

    assert isinstance(result, ForgetResult)
    assert result.entities_deleted == 0
    assert result.claims_deleted == 0


# ---------------------------------------------------------------------------
# 6. forget_entity wrong owner raises PermissionError
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_forget_entity_wrong_owner_raises(db, entity_with_claims):
    from cortex.tools.gdpr import forget_entity

    entity = entity_with_claims["entity"]
    with pytest.raises(PermissionError):
        await forget_entity(db, entity_id=entity.id, owner_id="eve")


# ---------------------------------------------------------------------------
# 7. forget_entity writes audit log entry (event_log)
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_forget_entity_writes_changelog(db, entity_with_claims):
    """GDPR audit is written to event_log with event_type='gdpr.entity_forget'."""
    from cortex.tools.gdpr import forget_entity
    import json

    entity = entity_with_claims["entity"]
    await forget_entity(db, entity_id=entity.id, owner_id="alice")

    # Audit row lives in event_log under event_type='gdpr.entity_forget'
    events = await db.get_events(owner_id="alice", event_type="gdpr.entity_forget")
    assert len(events) >= 1
    payload = json.loads(events[0]["payload_json"])
    assert payload["action"] == "gdpr_forget"
    assert payload["entity_id"] == entity.id


# ---------------------------------------------------------------------------
# 8. forget_entity emits ENTITY_FORGOTTEN event
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_forget_entity_emits_event(db, entity_with_claims):
    from cortex.tools.gdpr import forget_entity
    from cortex.events.emitter import EventType

    emitter = _make_mock_emitter()
    entity = entity_with_claims["entity"]
    await forget_entity(db, entity_id=entity.id, owner_id="alice", emitter=emitter)

    emitter.emit.assert_awaited_once()
    event = emitter.emit.call_args[0][0]
    assert event.event_type == EventType.ENTITY_FORGOTTEN
    assert event.payload["entity_id"] == entity.id


# ---------------------------------------------------------------------------
# 9. forget_owner deletes all claims
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_forget_owner_deletes_all_claims(db, entity_with_claims):
    from cortex.tools.gdpr import forget_owner

    await forget_owner(db, owner_id="alice")

    # Both alice's entity claims gone
    for key in ("claim_subject", "claim_object", "other_claim"):
        claim = await db.get_claim(entity_with_claims[key].id)
        assert claim is None, f"{key} should be deleted"


# ---------------------------------------------------------------------------
# 10. forget_owner deletes all entities
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_forget_owner_deletes_all_entities(db, entity_with_claims):
    from cortex.tools.gdpr import forget_owner

    await forget_owner(db, owner_id="alice")

    for key in ("entity", "other_entity"):
        ent = await db.get_entity(entity_with_claims[key].id)
        assert ent is None, f"{key} should be deleted"


# ---------------------------------------------------------------------------
# 11. forget_owner dry_run returns counts without deleting
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_forget_owner_dry_run(db, entity_with_claims):
    from cortex.tools.gdpr import forget_owner

    result = await forget_owner(db, owner_id="alice", dry_run=True)

    assert result.dry_run is True
    assert result.claims_deleted == 3   # c1 + c2 + c3
    assert result.entities_deleted == 2

    # Data must still exist
    ent = await db.get_entity(entity_with_claims["entity"].id)
    assert ent is not None


# ---------------------------------------------------------------------------
# 12. forget_owner emits MEMORY_DELETED event
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_forget_owner_emits_event(db, entity_with_claims):
    from cortex.tools.gdpr import forget_owner
    from cortex.events.emitter import EventType

    emitter = _make_mock_emitter()
    await forget_owner(db, owner_id="alice", emitter=emitter)

    emitter.emit.assert_awaited_once()
    event = emitter.emit.call_args[0][0]
    assert event.event_type == EventType.MEMORY_DELETED
    assert event.payload["operation"] == "gdpr_forget_owner"


# ---------------------------------------------------------------------------
# 13. forget_owner does NOT touch other owners' data
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_forget_owner_isolates_other_owners(db):
    from cortex.tools.gdpr import forget_owner

    # Bob owns data
    bob_entity = await db.create_entity(
        canonical_name="Bob Entity",
        entity_type="person",
        owner_id="bob",
    )
    bob_claim = await db.create_claim(
        subject="Bob Entity",
        predicate="works_at",
        object_value="Acme",
        owner_id="bob",
        subject_entity_id=bob_entity.id,
    )

    # Forget alice (no alice data in this test)
    await forget_owner(db, owner_id="alice")

    # Bob's data must be intact
    assert await db.get_entity(bob_entity.id) is not None
    assert await db.get_claim(bob_claim.id) is not None


# ---------------------------------------------------------------------------
# 14. ForgetResult.to_dict serialises correctly
# ---------------------------------------------------------------------------

def test_forget_result_to_dict():
    from cortex.tools.gdpr import ForgetResult

    r = ForgetResult(
        entity_id="ent-1",
        owner_id="alice",
        claims_deleted=5,
        entities_deleted=1,
        brain_nodes_deleted=2,
        dry_run=True,
        entity_name="Alice",
    )
    d = r.to_dict()
    assert d["entity_id"] == "ent-1"
    assert d["claims_deleted"] == 5
    assert d["entities_deleted"] == 1
    assert d["brain_nodes_deleted"] == 2
    assert d["dry_run"] is True
    assert d["entity_name"] == "Alice"
