"""Tests for Read Audit (access logging with tamper-proof hash chain).

Read Audit records search/get_all access events when TRUSTMEM_READ_AUDIT=true.
"""
import pytest

from trustmem.handler.persistent import PersistentHandler
from trustmem import TrustMem


# ==============================================================
# Fixtures
# ==============================================================

@pytest.fixture
def handler_with_audit(mock_ai):
    """Handler with read_audit enabled."""
    h = PersistentHandler(
        database_url=":memory:",
        ai_client=mock_ai,
        read_audit=True,
    )
    h.reset_stores()
    return h


@pytest.fixture
def handler_no_audit(mock_ai):
    """Handler with read_audit disabled (default)."""
    h = PersistentHandler(
        database_url=":memory:",
        ai_client=mock_ai,
        read_audit=False,
    )
    h.reset_stores()
    return h


@pytest.fixture
def tm_audit(handler_with_audit):
    """TrustMem SDK with read_audit enabled."""
    return TrustMem._from_handler(handler_with_audit)


@pytest.fixture
def tm_no_audit(handler_no_audit):
    """TrustMem SDK with read_audit disabled."""
    return TrustMem._from_handler(handler_no_audit)


# ==============================================================
# Opt-in behavior
# ==============================================================

class TestOptIn:
    """Read Audit is opt-in (default OFF)."""

    def test_no_audit_by_default(self, tm_no_audit):
        """No access log records when read_audit is disabled."""
        tm_no_audit.add_raw(content="test fact", user_id="test")
        tm_no_audit.search("test", user_id="test")
        log = tm_no_audit.access_log()
        assert log == []

    def test_audit_when_enabled(self, tm_audit):
        """Access log records created when read_audit is enabled."""
        tm_audit.add_raw(content="test fact", user_id="test")
        tm_audit.search("test", user_id="test")
        log = tm_audit.access_log()
        assert len(log) == 1
        assert log[0]["event_type"] == "search"


# ==============================================================
# Recording
# ==============================================================

class TestRecording:
    """Access log recording for different operations."""

    def test_search_recorded(self, tm_audit):
        """search() creates access log entry."""
        tm_audit.add_raw(content="Alex likes sushi", user_id="test")
        tm_audit.search("sushi", user_id="test", accessor="hr-bot")
        log = tm_audit.access_log()
        assert len(log) == 1
        entry = log[0]
        assert entry["event_type"] == "search"
        assert entry["accessor"] == "hr-bot"
        assert entry["query"] == "sushi"
        assert entry["user_id"] == "test"
        assert entry["result_count"] >= 1
        assert len(entry["memory_ids"]) >= 1

    def test_search_raw_recorded(self, tm_audit):
        """search_raw() creates access log entry."""
        tm_audit.add_raw(content="Bob likes tea", user_id="test")
        tm_audit.search_raw("tea", user_id="test", accessor="sales-bot")
        log = tm_audit.access_log()
        assert len(log) == 1
        assert log[0]["event_type"] == "search_raw"
        assert log[0]["accessor"] == "sales-bot"

    def test_get_all_recorded(self, tm_audit):
        """get_all() creates access log entry."""
        tm_audit.add_raw(content="fact 1", user_id="test")
        tm_audit.get_all(user_id="test", accessor="admin")
        log = tm_audit.access_log()
        assert len(log) == 1
        assert log[0]["event_type"] == "get_all"
        assert log[0]["accessor"] == "admin"

    def test_default_accessor(self, tm_audit):
        """Default accessor is 'system'."""
        tm_audit.add_raw(content="test", user_id="test")
        tm_audit.search("test", user_id="test")
        log = tm_audit.access_log()
        assert log[0]["accessor"] == "system"

    def test_filters_recorded(self, tm_audit):
        """Metadata filters are recorded in access log."""
        tm_audit.add_raw(
            content="CRM data",
            user_id="test",
            metadata={"source": "salesforce"},
        )
        tm_audit.search(
            "CRM",
            user_id="test",
            filters={"source": "salesforce"},
        )
        log = tm_audit.access_log()
        assert log[0]["filters"] == {"source": "salesforce"}

    def test_multiple_searches_recorded(self, tm_audit):
        """Multiple searches create multiple access log entries."""
        tm_audit.add_raw(content="fact", user_id="test")
        tm_audit.search("q1", user_id="test", accessor="bot-a")
        tm_audit.search("q2", user_id="test", accessor="bot-b")
        tm_audit.search_raw("q3", user_id="test", accessor="bot-c")
        log = tm_audit.access_log()
        assert len(log) == 3


# ==============================================================
# Querying
# ==============================================================

class TestQuerying:
    """Access log query filters."""

    def test_filter_by_accessor(self, tm_audit):
        """Query access log by accessor name."""
        tm_audit.add_raw(content="fact", user_id="test")
        tm_audit.search("q1", user_id="test", accessor="hr-bot")
        tm_audit.search("q2", user_id="test", accessor="sales-bot")
        log = tm_audit.access_log(accessor="hr-bot")
        assert len(log) == 1
        assert log[0]["accessor"] == "hr-bot"

    def test_filter_by_memory_id(self, tm_audit):
        """Query access log by accessed memory_id."""
        r = tm_audit.add_raw(content="specific fact", user_id="test")
        mid = r["memory_id"]
        tm_audit.search("specific", user_id="test")
        log = tm_audit.access_log(memory_id=mid)
        assert len(log) >= 1
        assert mid in log[0]["memory_ids"]

    def test_filter_by_since(self, tm_audit):
        """Query access log with since filter."""
        tm_audit.add_raw(content="fact", user_id="test")
        tm_audit.search("fact", user_id="test")
        # All records should be after 2020
        log = tm_audit.access_log(since="2020-01-01T00:00:00Z")
        assert len(log) >= 1
        # No records before 2100
        log = tm_audit.access_log(since="2100-01-01T00:00:00Z")
        assert len(log) == 0

    def test_limit_and_offset(self, tm_audit):
        """Pagination with limit and offset."""
        tm_audit.add_raw(content="fact", user_id="test")
        for i in range(5):
            tm_audit.search(f"query{i}", user_id="test")
        log_all = tm_audit.access_log(limit=10)
        assert len(log_all) == 5
        log_page = tm_audit.access_log(limit=2, offset=0)
        assert len(log_page) == 2


# ==============================================================
# Hash Chain
# ==============================================================

class TestHashChain:
    """Tamper-proof hash chain for access log."""

    def test_chain_valid_empty(self, tm_audit):
        """Empty access log has valid chain."""
        result = tm_audit.verify_access_chain()
        assert result["valid"] is True
        assert result["total_records"] == 0

    def test_chain_valid_after_records(self, tm_audit):
        """Chain is valid after recording access events."""
        tm_audit.add_raw(content="fact", user_id="test")
        tm_audit.search("q1", user_id="test")
        tm_audit.search("q2", user_id="test")
        tm_audit.get_all(user_id="test")
        result = tm_audit.verify_access_chain()
        assert result["valid"] is True
        assert result["total_records"] == 3
        assert result["verified"] == 3

    def test_chain_has_sequence_and_hashes(self, tm_audit):
        """Each record has sequence_number, record_hash, prev_hash."""
        tm_audit.add_raw(content="fact", user_id="test")
        tm_audit.search("q", user_id="test")
        log = tm_audit.access_log()
        entry = log[0]
        assert entry["sequence_number"] is not None
        assert entry["record_hash"] is not None
        assert entry["prev_hash"] is not None


# ==============================================================
# Purge
# ==============================================================

class TestPurge:
    """Purge with PURGE marker for auditability."""

    def test_purge_removes_old_records(self, tm_audit):
        """Purge physically deletes old records."""
        tm_audit.add_raw(content="fact", user_id="test")
        tm_audit.search("q1", user_id="test")
        tm_audit.search("q2", user_id="test")

        # Purge all records before far future
        result = tm_audit.purge_access_log(before="2100-01-01T00:00:00Z")
        assert result["purged"] == 2
        assert result["marker"].startswith("PURGE:")

        log = tm_audit.access_log()
        assert len(log) == 0

    def test_purge_partial(self, tm_audit):
        """Partial purge leaves surviving records."""
        tm_audit.add_raw(content="fact", user_id="test")
        # Create 3 records
        tm_audit.search("q1", user_id="test")
        tm_audit.search("q2", user_id="test")
        tm_audit.search("q3", user_id="test")

        log_before = tm_audit.access_log()
        assert len(log_before) == 3

        # Purge nothing (all records are after 2020)
        result = tm_audit.purge_access_log(before="2020-01-01T00:00:00Z")
        assert result["purged"] == 0
        assert result["marker"] is None

    def test_purge_chain_still_valid(self, tm_audit):
        """Chain remains valid after purge (PURGE marker accepted)."""
        tm_audit.add_raw(content="fact", user_id="test")
        for i in range(5):
            tm_audit.search(f"q{i}", user_id="test")

        # Purge all
        tm_audit.purge_access_log(before="2100-01-01T00:00:00Z")
        result = tm_audit.verify_access_chain()
        assert result["valid"] is True

    def test_purge_marker_in_verify(self, handler_with_audit):
        """verify_access_chain reports purge points."""
        # Add memories and search
        handler_with_audit.add_memory("fact1", user_id="test")
        handler_with_audit.add_memory("fact2", user_id="test")
        handler_with_audit.search_memory("fact1", user_id="test")
        handler_with_audit.search_memory("fact2", user_id="test")
        handler_with_audit.search_memory("fact3", user_id="test")

        # Purge first 2 (created very recently, so use far future)
        handler_with_audit.purge_access_log(before="2100-01-01T00:00:00Z")

        # Verify — should be valid with 0 records
        result = handler_with_audit.verify_access_chain()
        assert result["valid"] is True

    def test_purge_validation(self, tm_audit):
        """Purge requires valid ISO date string."""
        with pytest.raises(Exception):
            tm_audit.purge_access_log(before="bad")


# ==============================================================
# Append-only triggers
# ==============================================================

class TestAppendOnly:
    """SQLite triggers prevent modification of access log."""

    def test_update_blocked(self, handler_with_audit):
        """UPDATE on access log raises error."""
        handler_with_audit.add_memory("fact", user_id="test")
        handler_with_audit.search_memory("fact", user_id="test")

        import sqlite3
        with pytest.raises((sqlite3.OperationalError, sqlite3.IntegrityError), match="prohibited"):
            handler_with_audit._db._conn.execute(
                "UPDATE memory_access_log SET accessor = 'hacker'"
            )

    def test_delete_blocked(self, handler_with_audit):
        """DELETE on access log raises error."""
        handler_with_audit.add_memory("fact", user_id="test")
        handler_with_audit.search_memory("fact", user_id="test")

        import sqlite3
        with pytest.raises((sqlite3.OperationalError, sqlite3.IntegrityError), match="prohibited"):
            handler_with_audit._db._conn.execute(
                "DELETE FROM memory_access_log"
            )


# ==============================================================
# Config
# ==============================================================

class TestConfig:
    """TRUSTMEM_READ_AUDIT configuration."""

    def test_config_default_false(self):
        """read_audit defaults to False."""
        from trustmem.config import TrustMemConfig
        config = TrustMemConfig()
        assert config.read_audit is False

    def test_config_from_env(self, monkeypatch):
        """TRUSTMEM_READ_AUDIT=true enables read audit."""
        monkeypatch.setenv("TRUSTMEM_READ_AUDIT", "true")
        from trustmem.config import TrustMemConfig
        config = TrustMemConfig.from_env()
        assert config.read_audit is True

    def test_config_from_env_false(self, monkeypatch):
        """TRUSTMEM_READ_AUDIT=false keeps it disabled."""
        monkeypatch.setenv("TRUSTMEM_READ_AUDIT", "false")
        from trustmem.config import TrustMemConfig
        config = TrustMemConfig.from_env()
        assert config.read_audit is False
