"""Tests for the SQLite audit store.

The tamper detection test is the most important test in the entire project.
If it fails, the product's core value proposition is broken.
"""

from __future__ import annotations

import sqlite3
from typing import TYPE_CHECKING

import pytest

from attestmcp.canonical import GENESIS_HASH
from attestmcp.models import (
    AgentIdentity,
    AuditRecord,
    IdentitySource,
    PolicyDecision,
    TrustLevel,
)
from attestmcp.store import SQLiteAuditStore

if TYPE_CHECKING:
    from pathlib import Path


def _make_record(
    method: str = "tools/call",
    tool_name: str = "store_memory",
    params_hash: str = "abc123",
) -> AuditRecord:
    """Create a test audit record with minimal required fields."""
    return AuditRecord(
        agent_identity=AgentIdentity(
            source=IdentitySource.OAUTH_JWT,
            user="test@example.com",
            agent_name="test-agent",
            org="test-org",
            role="admin",
            verified=True,
            trust_level=TrustLevel.VERIFIED,
        ),
        method=method,
        tool_name=tool_name,
        params_hash=params_hash,
        policy_decision=PolicyDecision.ALLOW,
        result_hash="def456",
        duration_ms=5,
    )


@pytest.fixture
def store(tmp_path: Path) -> SQLiteAuditStore:
    """Create a fresh store in a temporary directory."""
    return SQLiteAuditStore(db_path=tmp_path / "test_audit.db")


class TestAppend:
    """Record appending."""

    def test_append_sets_chain_hash(self, store: SQLiteAuditStore) -> None:
        record = _make_record()
        result = store.append(record)
        assert result.chain_hash != ""
        assert len(result.chain_hash) == 64

    def test_first_record_chains_from_genesis(self, store: SQLiteAuditStore) -> None:
        record = store.append(_make_record())
        assert record.chain_hash != GENESIS_HASH

    def test_second_record_chains_from_first(self, store: SQLiteAuditStore) -> None:
        r1 = store.append(_make_record(params_hash="first"))
        r2 = store.append(_make_record(params_hash="second"))
        assert r2.chain_hash != r1.chain_hash
        assert r2.chain_hash != GENESIS_HASH

    def test_count_increments(self, store: SQLiteAuditStore) -> None:
        assert store.count() == 0
        store.append(_make_record())
        assert store.count() == 1
        store.append(_make_record())
        assert store.count() == 2

    def test_chain_head_updates(self, store: SQLiteAuditStore) -> None:
        assert store.get_chain_head() == GENESIS_HASH
        r1 = store.append(_make_record())
        assert store.get_chain_head() == r1.chain_hash
        r2 = store.append(_make_record())
        assert store.get_chain_head() == r2.chain_hash


class TestQuery:
    """Record querying."""

    def test_query_empty_store(self, store: SQLiteAuditStore) -> None:
        results = store.query()
        assert results == []

    def test_query_returns_all(self, store: SQLiteAuditStore) -> None:
        store.append(_make_record(params_hash="a"))
        store.append(_make_record(params_hash="b"))
        store.append(_make_record(params_hash="c"))
        results = store.query()
        assert len(results) == 3

    def test_query_preserves_order(self, store: SQLiteAuditStore) -> None:
        for i in range(5):
            store.append(_make_record(params_hash=f"record_{i}"))
        results = store.query()
        for i, record in enumerate(results):
            assert record.params_hash == f"record_{i}"

    def test_query_with_limit(self, store: SQLiteAuditStore) -> None:
        for i in range(10):
            store.append(_make_record(params_hash=f"record_{i}"))
        results = store.query(limit=3)
        assert len(results) == 3

    def test_query_roundtrip_preserves_data(self, store: SQLiteAuditStore) -> None:
        original = _make_record()
        stored = store.append(original)
        retrieved = store.query()[0]
        assert retrieved.record_id == stored.record_id
        assert retrieved.agent_identity.user == "test@example.com"
        assert retrieved.agent_identity.org == "test-org"
        assert retrieved.method == "tools/call"
        assert retrieved.tool_name == "store_memory"
        assert retrieved.chain_hash == stored.chain_hash


class TestChainVerification:
    """Hash chain integrity verification.

    THIS IS THE MOST IMPORTANT TEST CLASS IN THE PROJECT.
    """

    def test_empty_chain_is_valid(self, store: SQLiteAuditStore) -> None:
        result = store.verify_chain()
        assert result.valid is True
        assert result.records_checked == 0

    def test_single_record_chain_valid(self, store: SQLiteAuditStore) -> None:
        store.append(_make_record())
        result = store.verify_chain()
        assert result.valid is True
        assert result.records_checked == 1

    def test_multi_record_chain_valid(self, store: SQLiteAuditStore) -> None:
        for i in range(20):
            store.append(_make_record(params_hash=f"record_{i}"))
        result = store.verify_chain()
        assert result.valid is True
        assert result.records_checked == 20

    def test_tamper_detection_modify_record(self, store: SQLiteAuditStore) -> None:
        """CRITICAL TEST: Detect when a record is modified after storage.

        This simulates an attacker who has database access and changes
        a stored record. The chain verification must detect this.
        """
        # Append 5 records
        for i in range(5):
            store.append(_make_record(params_hash=f"record_{i}"))

        # Verify chain is valid before tampering
        assert store.verify_chain().valid is True

        # TAMPER: modify the params_hash of the 3rd record (sequence_num=2)
        conn = sqlite3.connect(store._db_path)
        try:
            # Get the 3rd record
            row = conn.execute(
                "SELECT record_json FROM audit_records WHERE sequence_num = 2"
            ).fetchone()
            assert row is not None

            # Modify the stored JSON
            import json
            record_data = json.loads(row[0])
            record_data["params_hash"] = "TAMPERED_VALUE"
            tampered_json = json.dumps(record_data)

            conn.execute(
                "UPDATE audit_records SET record_json = ?, params_hash = ? "
                "WHERE sequence_num = 2",
                (tampered_json, "TAMPERED_VALUE"),
            )
            conn.commit()
        finally:
            conn.close()

        # Verify chain — MUST detect the tampering
        result = store.verify_chain()
        assert result.valid is False
        assert result.break_at_record_id is not None
        assert result.expected_hash is not None
        assert result.actual_hash is not None
        assert result.expected_hash != result.actual_hash

    def test_tamper_detection_delete_record(self, store: SQLiteAuditStore) -> None:
        """Detect when a record is deleted from the middle of the chain."""
        for i in range(5):
            store.append(_make_record(params_hash=f"record_{i}"))

        assert store.verify_chain().valid is True

        # TAMPER: delete the 2nd record
        conn = sqlite3.connect(store._db_path)
        try:
            conn.execute("DELETE FROM audit_records WHERE sequence_num = 1")
            conn.commit()
        finally:
            conn.close()

        # Chain verification should fail because sequence is broken
        # The 3rd record's chain_hash was computed with the 2nd record's hash
        # as input, but now the 2nd record doesn't exist
        result = store.verify_chain()
        assert result.valid is False

    def test_tamper_detection_reorder_records(self, store: SQLiteAuditStore) -> None:
        """Detect when records are reordered."""
        for i in range(5):
            store.append(_make_record(params_hash=f"record_{i}"))

        assert store.verify_chain().valid is True

        # TAMPER: swap sequence numbers of records 1 and 2
        conn = sqlite3.connect(store._db_path)
        try:
            conn.execute(
                "UPDATE audit_records SET sequence_num = 999 WHERE sequence_num = 1"
            )
            conn.execute(
                "UPDATE audit_records SET sequence_num = 1 WHERE sequence_num = 2"
            )
            conn.execute(
                "UPDATE audit_records SET sequence_num = 2 WHERE sequence_num = 999"
            )
            conn.commit()
        finally:
            conn.close()

        result = store.verify_chain()
        assert result.valid is False

    def test_tamper_detection_append_forged_record(
        self, store: SQLiteAuditStore
    ) -> None:
        """Detect when a forged record is inserted with a fake chain_hash."""
        for i in range(3):
            store.append(_make_record(params_hash=f"record_{i}"))

        assert store.verify_chain().valid is True

        # TAMPER: insert a forged record with a made-up chain_hash
        conn = sqlite3.connect(store._db_path)
        try:
            forged_record = _make_record(params_hash="FORGED")
            forged_json = forged_record.model_dump_json()
            identity_json = forged_record.agent_identity.model_dump_json()

            conn.execute(
                """
                INSERT INTO audit_records (
                    record_id, timestamp, session_id,
                    agent_identity_json, method, tool_name,
                    params_hash, policy_decision, policy_rule,
                    result_hash, duration_ms, error,
                    chain_hash, record_json, sequence_num
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    "forged-id",
                    forged_record.timestamp.isoformat(),
                    None,
                    identity_json,
                    forged_record.method,
                    forged_record.tool_name,
                    "FORGED",
                    "allow",
                    None,
                    "fake_hash",
                    0,
                    None,
                    "0000000000000000000000000000000000000000000000000000000000000000",
                    forged_json,
                    3,
                ),
            )
            conn.commit()
        finally:
            conn.close()

        result = store.verify_chain()
        assert result.valid is False


class TestChainVerificationInCI:
    """This test MUST run in CI on every PR.

    It creates a chain, verifies it, and ensures the verification
    itself is working correctly. This catches silent regressions
    in the hash chain logic.
    """

    def test_full_chain_lifecycle(self, store: SQLiteAuditStore) -> None:
        """End-to-end: create records, verify, tamper, detect."""
        # Build a chain
        records = []
        for i in range(10):
            r = store.append(
                _make_record(
                    method="tools/call",
                    tool_name=f"tool_{i}",
                    params_hash=f"params_{i}",
                )
            )
            records.append(r)

        # Verify intact chain
        verification = store.verify_chain()
        assert verification.valid is True
        assert verification.records_checked == 10
        assert verification.first_record_id == records[0].record_id
        assert verification.last_record_id == records[9].record_id
        assert verification.break_at_record_id is None

        # Each record has a unique chain_hash
        chain_hashes = [r.chain_hash for r in records]
        assert len(set(chain_hashes)) == 10

        # Chain head matches last record
        assert store.get_chain_head() == records[9].chain_hash
