# SPDX-License-Identifier: Apache-2.0
"""Tests for receipt creation, verification, and content-addressing."""

import pytest

from nlai import Receipt, create_receipt, verify_receipt
from nlai.receipt import RECEIPT_SCHEMA_VERSION, VALID_VERDICTS


class TestReceipt:
    """Receipt dataclass behavior."""

    def test_frozen(self):
        r = create_receipt(gate="test", verdict="pass",
                           subject_kind="text", subject_bytes=b"hello")
        with pytest.raises(AttributeError):
            r.verdict = "block"  # type: ignore[misc]

    def test_valid_verdicts(self):
        for v in ("pass", "warn", "block", "observe"):
            r = create_receipt(gate="test", verdict=v,
                               subject_kind="text", subject_bytes=b"x")
            assert r.verdict == v

    def test_invalid_verdict(self):
        with pytest.raises(ValueError, match="Invalid verdict"):
            Receipt(
                receipt_id="fake",
                schema_version=1,
                timestamp="now",
                gate="test",
                verdict="invalid",
                subject_hash="abc",
                evidence_hash="def",
            )

    def test_schema_version(self):
        r = create_receipt(gate="test", verdict="pass",
                           subject_kind="text", subject_bytes=b"hello")
        assert r.schema_version == RECEIPT_SCHEMA_VERSION

    def test_has_timestamp(self):
        r = create_receipt(gate="test", verdict="pass",
                           subject_kind="text", subject_bytes=b"hello")
        assert r.timestamp  # Non-empty

    def test_receipt_id_is_hex(self):
        r = create_receipt(gate="test", verdict="pass",
                           subject_kind="text", subject_bytes=b"hello")
        assert len(r.receipt_id) == 64
        assert all(c in "0123456789abcdef" for c in r.receipt_id)


class TestContentAddressing:
    """Content-addressed identity: same inputs = same receipt_id."""

    def test_same_inputs_same_id(self):
        r1 = create_receipt(gate="test", verdict="pass",
                            subject_kind="text", subject_bytes=b"hello")
        r2 = create_receipt(gate="test", verdict="pass",
                            subject_kind="text", subject_bytes=b"hello")
        assert r1.receipt_id == r2.receipt_id

    def test_different_subject_different_id(self):
        r1 = create_receipt(gate="test", verdict="pass",
                            subject_kind="text", subject_bytes=b"hello")
        r2 = create_receipt(gate="test", verdict="pass",
                            subject_kind="text", subject_bytes=b"world")
        assert r1.receipt_id != r2.receipt_id

    def test_different_gate_different_id(self):
        r1 = create_receipt(gate="gate_a", verdict="pass",
                            subject_kind="text", subject_bytes=b"hello")
        r2 = create_receipt(gate="gate_b", verdict="pass",
                            subject_kind="text", subject_bytes=b"hello")
        assert r1.receipt_id != r2.receipt_id

    def test_different_evidence_different_id(self):
        r1 = create_receipt(gate="test", verdict="pass",
                            subject_kind="text", subject_bytes=b"hello",
                            evidence={"a": 1})
        r2 = create_receipt(gate="test", verdict="pass",
                            subject_kind="text", subject_bytes=b"hello",
                            evidence={"a": 2})
        assert r1.receipt_id != r2.receipt_id

    def test_timestamp_not_in_id(self):
        """Different timestamps should NOT change receipt_id."""
        r1 = create_receipt(gate="test", verdict="pass",
                            subject_kind="text", subject_bytes=b"hello")
        r2 = create_receipt(gate="test", verdict="pass",
                            subject_kind="text", subject_bytes=b"hello")
        # Timestamps will differ (called at different times)
        # but receipt_id should be the same
        assert r1.receipt_id == r2.receipt_id

    def test_verdict_not_in_id(self):
        """Different verdicts with same inputs should have same receipt_id.

        Verdict is the *output* of the gate, not an input to identity.
        The receipt proves "this gate saw this subject with this evidence"
        — the verdict is what happened, not who it is.
        """
        r1 = create_receipt(gate="test", verdict="pass",
                            subject_kind="text", subject_bytes=b"hello")
        r2 = create_receipt(gate="test", verdict="block",
                            subject_kind="text", subject_bytes=b"hello")
        assert r1.receipt_id == r2.receipt_id


class TestVerification:
    """Receipt verification."""

    def test_verify_valid(self):
        r = create_receipt(gate="test", verdict="pass",
                           subject_kind="text", subject_bytes=b"hello")
        assert verify_receipt(r) is True

    def test_verify_tampered(self):
        r = create_receipt(gate="test", verdict="pass",
                           subject_kind="text", subject_bytes=b"hello")
        # Tamper with subject_hash
        tampered = Receipt(
            receipt_id=r.receipt_id,
            schema_version=r.schema_version,
            timestamp=r.timestamp,
            gate=r.gate,
            verdict=r.verdict,
            subject_hash="0" * 64,
            evidence_hash=r.evidence_hash,
        )
        assert verify_receipt(tampered) is False


class TestSerialization:
    """to_dict / from_dict roundtrip."""

    def test_roundtrip(self):
        r = create_receipt(gate="test", verdict="pass",
                           subject_kind="text", subject_bytes=b"hello")
        d = r.to_dict()
        r2 = Receipt.from_dict(d)
        assert r == r2

    def test_to_dict_has_schema_version(self):
        r = create_receipt(gate="test", verdict="pass",
                           subject_kind="text", subject_bytes=b"hello")
        d = r.to_dict()
        assert "schema_version" in d
        assert d["schema_version"] == RECEIPT_SCHEMA_VERSION

    def test_from_dict_rejects_future_version(self):
        d = {
            "receipt_id": "abc",
            "schema_version": 999,
            "timestamp": "now",
            "gate": "test",
            "verdict": "pass",
            "subject_hash": "abc",
            "evidence_hash": "def",
        }
        with pytest.raises(ValueError, match="newer than supported"):
            Receipt.from_dict(d)

    def test_from_dict_defaults_schema_version(self):
        """Missing schema_version defaults to current."""
        d = {
            "receipt_id": "abc",
            "timestamp": "now",
            "gate": "test",
            "verdict": "pass",
            "subject_hash": "abc",
            "evidence_hash": "def",
        }
        r = Receipt.from_dict(d)
        assert r.schema_version == RECEIPT_SCHEMA_VERSION
