"""
Tests for sunaiva_crypto.audit_ledger — Immutable audit trail.

Covers append, chain integrity, tamper detection, serialization, and lookups.
"""

import json
import pytest

from src.audit_ledger import AuditLedger, LedgerEntry, GENESIS_HASH


@pytest.fixture
def ledger():
    """Create a fresh empty ledger."""
    return AuditLedger()


def _make_entry(ledger, output_id="out_001", passed=True, contact_id=None):
    """Helper to append a standard entry."""
    return ledger.append(
        output_id=output_id,
        output_hash="abc123hash",
        validation_passed=passed,
        layers_passed=[1, 2, 3, 4, 5, 6, 7, 8, 9] if passed else [1, 2, 3],
        layers_failed=[] if passed else [4, 5, 6, 7, 8, 9],
        signature="base64sig==",
        public_key_fingerprint="abcdef0123456789",
        contact_id=contact_id,
    )


# ---------------------------------------------------------------------------
# Append
# ---------------------------------------------------------------------------

class TestAppend:
    def test_append_single_entry(self, ledger):
        entry = _make_entry(ledger)
        assert len(ledger) == 1
        assert entry.output_id == "out_001"
        assert entry.chain_hash != ""
        assert entry.prev_entry_id is None

    def test_append_multiple_entries(self, ledger):
        e1 = _make_entry(ledger, "out_001")
        e2 = _make_entry(ledger, "out_002")
        e3 = _make_entry(ledger, "out_003")
        assert len(ledger) == 3
        assert e2.prev_entry_id == e1.entry_id
        assert e3.prev_entry_id == e2.entry_id

    def test_rejects_duplicate_output_id(self, ledger):
        _make_entry(ledger, "out_001")
        with pytest.raises(ValueError, match="Duplicate output_id"):
            _make_entry(ledger, "out_001")

    def test_chain_hash_differs_between_entries(self, ledger):
        e1 = _make_entry(ledger, "out_001")
        e2 = _make_entry(ledger, "out_002")
        assert e1.chain_hash != e2.chain_hash

    def test_entry_has_all_fields(self, ledger):
        entry = _make_entry(ledger, contact_id="contact_abc")
        assert entry.entry_id is not None
        assert entry.output_hash == "abc123hash"
        assert entry.validation_passed is True
        assert entry.contact_id == "contact_abc"
        assert entry.created_at is not None


# ---------------------------------------------------------------------------
# Chain Integrity
# ---------------------------------------------------------------------------

class TestChainIntegrity:
    def test_empty_ledger_is_valid(self, ledger):
        assert ledger.verify_chain_integrity() is True

    def test_single_entry_valid(self, ledger):
        _make_entry(ledger)
        assert ledger.verify_chain_integrity() is True

    def test_multiple_entries_valid(self, ledger):
        for i in range(10):
            _make_entry(ledger, f"out_{i:03d}")
        assert ledger.verify_chain_integrity() is True

    def test_tampered_entry_detected(self, ledger):
        _make_entry(ledger, "out_001")
        _make_entry(ledger, "out_002")
        # Tamper with the first entry
        ledger._entries[0].output_hash = "TAMPERED_HASH"
        assert ledger.verify_chain_integrity() is False

    def test_tampered_chain_hash_detected(self, ledger):
        _make_entry(ledger, "out_001")
        _make_entry(ledger, "out_002")
        # Tamper with chain_hash directly
        ledger._entries[0].chain_hash = "0" * 64
        assert ledger.verify_chain_integrity() is False

    def test_reordered_entries_detected(self, ledger):
        _make_entry(ledger, "out_001")
        _make_entry(ledger, "out_002")
        _make_entry(ledger, "out_003")
        # Swap entries 1 and 2
        ledger._entries[1], ledger._entries[2] = ledger._entries[2], ledger._entries[1]
        assert ledger.verify_chain_integrity() is False


# ---------------------------------------------------------------------------
# Lookups
# ---------------------------------------------------------------------------

class TestLookups:
    def test_get_by_output_id(self, ledger):
        _make_entry(ledger, "out_001")
        entry = ledger.get_by_output_id("out_001")
        assert entry is not None
        assert entry.output_id == "out_001"

    def test_get_by_output_id_not_found(self, ledger):
        assert ledger.get_by_output_id("nonexistent") is None

    def test_get_entries_for_contact(self, ledger):
        _make_entry(ledger, "out_001", contact_id="contact_A")
        _make_entry(ledger, "out_002", contact_id="contact_A")
        _make_entry(ledger, "out_003", contact_id="contact_B")
        entries = ledger.get_entries_for_contact("contact_A")
        assert len(entries) == 2
        assert all(e.contact_id == "contact_A" for e in entries)

    def test_get_entries_for_contact_empty(self, ledger):
        assert ledger.get_entries_for_contact("nobody") == []

    def test_all_entries(self, ledger):
        for i in range(5):
            _make_entry(ledger, f"out_{i}")
        assert len(ledger.all_entries()) == 5


# ---------------------------------------------------------------------------
# Serialization
# ---------------------------------------------------------------------------

class TestSerialization:
    def test_to_json_and_back(self, ledger):
        for i in range(5):
            _make_entry(ledger, f"out_{i}", contact_id=f"contact_{i % 2}")
        json_str = ledger.to_json()
        restored = AuditLedger.from_json(json_str)
        assert len(restored) == 5
        assert restored.verify_chain_integrity() is True

    def test_json_structure(self, ledger):
        _make_entry(ledger, "out_001")
        data = json.loads(ledger.to_json())
        assert data["version"] == "1.0"
        assert data["genesis_hash"] == GENESIS_HASH
        assert data["entry_count"] == 1
        assert len(data["entries"]) == 1

    def test_restored_lookups_work(self, ledger):
        _make_entry(ledger, "out_001", contact_id="contact_X")
        json_str = ledger.to_json()
        restored = AuditLedger.from_json(json_str)
        assert restored.get_by_output_id("out_001") is not None
        assert len(restored.get_entries_for_contact("contact_X")) == 1

    def test_entry_to_dict(self, ledger):
        entry = _make_entry(ledger)
        d = entry.to_dict()
        assert isinstance(d, dict)
        assert d["output_id"] == "out_001"
        assert "chain_hash" in d
