"""Tests for RFC8785 canonicalization."""

from __future__ import annotations

from mailio_receipt.canonicalize import canonicalize, compute_digest
from mailio_receipt.models import MailioAuditEvent


class TestCanonicalize:
    """Tests for canonicalize function."""

    def test_deterministic_output(self) -> None:
        """Test that canonicalization produces deterministic output."""
        data = {"b": 2, "a": 1, "c": 3}

        # Multiple calls should produce identical output
        result1 = canonicalize(data)
        result2 = canonicalize(data)

        assert result1 == result2

    def test_returns_bytes(self) -> None:
        """Test that canonicalize returns bytes."""
        data = {"key": "value"}
        result = canonicalize(data)

        assert isinstance(result, bytes)


class TestComputeDigest:
    """Tests for compute_digest function."""

    def test_deterministic_digest(self) -> None:
        """Test that digest computation is deterministic."""
        data = {"b": 2, "a": 1}

        digest1 = compute_digest(data)
        digest2 = compute_digest(data)

        assert digest1 == digest2

    def test_digest_length(self) -> None:
        """Test that digest is 43 base64url characters (SHA-256)."""
        data = {"key": "value"}
        digest = compute_digest(data)

        assert len(digest) == 43
        assert all(c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-" for c in digest)

    def test_different_data_different_digest(self) -> None:
        """Test that different data produces different digests."""
        data1 = {"key": "value1"}
        data2 = {"key": "value2"}

        digest1 = compute_digest(data1)
        digest2 = compute_digest(data2)

        assert digest1 != digest2

    def test_key_order_independent(self) -> None:
        """Test that key order doesn't affect digest (due to canonicalization)."""
        # These should produce the same digest after canonicalization
        data1 = {"a": 1, "b": 2}
        data2 = {"b": 2, "a": 1}

        digest1 = compute_digest(data1)
        digest2 = compute_digest(data2)

        assert digest1 == digest2

    def test_cbor_canonicalization_ignores_json_field_order(self) -> None:
        """Test that JSON field ordering does not affect CBOR canonicalization.

        Two objects with identical data but different key ordering in the
        'scope' sub-object must produce the same canonical bytes and digest.
        """
        # Object A – scope keys: direction, threshold, ticker
        wrapper = {
            "event": {
                "type": "MailioAuditEvent",
                "v": "v1",
                "id": "b87ddf3d-7934-46a5-bbf2-ed70401054ae",
                "flow_id": "1cb6ffe5-0ec5-45f9-b8f0-b8bf37350d12",
                "ts": "2026-02-16T20:13:20.857Z",
                "actors": {
                    "issuer": "urn:mailio:igordemo-local-69676f72406d61696c2e696f",
                    "subject": "did:web:mio.mailiomail.com:0xb3e49d751ba7d6647a720a52060ceebe42524704",
                },
                "intent": {
                    "kind": "trigger_watch_condition",
                    "action": "price_above",
                    "category": "stock_price",
                    "scope": {
                        "direction": "price_above",
                        "threshold": 61,
                        "ticker": "MIO",
                    },
                },
            }
        }

        # Object B – scope keys: ticker, direction, threshold (different order)
        obj_b = {
            "type": "MailioAuditEvent",
            "v": "v1",
            "id": "b87ddf3d-7934-46a5-bbf2-ed70401054ae",
            "flow_id": "1cb6ffe5-0ec5-45f9-b8f0-b8bf37350d12",
            "ts": "2026-02-16T20:13:20.857Z",
            "actors": {
                "issuer": "urn:mailio:igordemo-local-69676f72406d61696c2e696f",
                "subject": "did:web:mio.mailiomail.com:0xb3e49d751ba7d6647a720a52060ceebe42524704",
            },
            "intent": {
                "kind": "trigger_watch_condition",
                "action": "price_above",
                "category": "stock_price",
                "scope": {
                    "ticker": "MIO",
                    "direction": "price_above",
                    "threshold": 61,
                },
            },
        }

        event_a = MailioAuditEvent.from_dict(wrapper["event"])
        event_b = MailioAuditEvent.from_dict(obj_b)

        bytes_a = canonicalize(event_a.to_dict())
        bytes_b = canonicalize(event_b.to_dict())

        digest_a = compute_digest(event_a.to_dict())
        digest_b = compute_digest(event_b.to_dict())

        assert bytes_a == bytes_b, (
            f"CBOR bytes differ due to JSON key ordering.\n"
            f"  A hex: {bytes_a.hex()}\n"
            f"  B hex: {bytes_b.hex()}"
        )
        assert digest_a == digest_b

    def test_known_digest(self) -> None:
        """Test against a known digest value."""
        # This ensures the implementation doesn't change unexpectedly
        data = {
    "type": "MailioAuditEvent",
    "v": "v1",
    "id": "84d44d51-d5c4-4ab6-a766-d701fa80a666",
    "flow_id": "243313bb-c4a0-4e81-a88c-a31f3566cdac",
    "ts": "2026-02-11T22:49:20.469Z",
    "actors": {
        "issuer": "igor@mail.io",
        "subject": "did:web:mio.mailiomail.com:0xb3e49d751ba7d6647a720a52060ceebe42524704"
    },
    "intent": {
        "kind": "trigger_watch_condition",
        "action": "price_above",
        "category": "stock_price",
        "scope": {
            "ticker": "MIO",
            "direction": "price_above",
            "threshold": 66
        }
    }
}
        event = MailioAuditEvent.from_dict(data)
        digest = compute_digest(event.to_dict())

        print(digest)

        # Pre-computed expected value
        # canonicalize({"hello": "world"}) = b'{"hello":"world"}'
        # sha256(b'{"hello":"world"}').hexdigest() = expected
        expected = "H_-W0w-DvXFGFmLC0skX7l-Lw1gPZuShrVaunk_JbpI"
        assert digest == expected

    def test_again(self) -> None:
        """Another test with a different known digest."""
        obj_a = {
            "type": "MailioReceipt",
            "v": "v1",
            "event": {
                "type": "MailioAuditEvent",
                "v": "v1",
                "id": "7f124fc9-53ae-431e-8db5-7581e6588276",
                "flow_id": "13860930-2800-4418-aeaa-fc4f91767132",
                "ts": "2026-02-16T20:36:13.937Z",
                "actors": {
                "issuer": "urn:mailio:igordemo-local-69676f72406d61696c2e696f",
                "subject": "did:web:mio.mailiomail.com:0xb3e49d751ba7d6647a720a52060ceebe42524704"
                },
                "intent": {
                "kind": "trigger_watch_condition",
                "action": "price_above",
                "category": "stock_price",
                "scope": {
                    "direction": "price_above",
                    "threshold": 66,
                    "ticker": "MIO"
                }
                }
            },
            "event_digest": "qEeF80eJMDkCt_wuk0CJ0J5_qp2cvkANf1AtW8KrXEg",
            "signatures": [
                {
                "role": "user",
                "signer": "urn:mailio:igordemo-local-69676f72406d61696c2e696f#webauthn-cred:EBACb3n-2F1HOkqIuCRRyA",
                "alg": "ES256",
                "signed": {
                    "canonicalization": "RFC8785",
                    "digest": "qEeF80eJMDkCt_wuk0CJ0J5_qp2cvkANf1AtW8KrXEg",
                    "target": "/event"
                },
                "jws": "MEQCIEKDcE2x_JWnIQZ1PgFrHYijvGzPuMpbi2prq8fANrHMAiAaSGmdZU4JPkrxpuPrn8V2GSir4yIXm0mborlrd4t1Rw",
                "authenticator_data": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MdAAAAAA",
                "client_data_json": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoicUVlRjgwZUpNRGtDdF93dWswQ0owSjVfcXAyY3ZrQU5mMUF0VzhLclhFZyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODg4OCIsImNyb3NzT3JpZ2luIjpmYWxzZSwib3RoZXJfa2V5c19jYW5fYmVfYWRkZWRfaGVyZSI6ImRvIG5vdCBjb21wYXJlIGNsaWVudERhdGFKU09OIGFnYWluc3QgYSB0ZW1wbGF0ZS4gU2VlIGh0dHBzOi8vZ29vLmdsL3lhYlBleCJ9"
                },
                {
                "role": "agent",
                "signer": "did:mailio:0xb3e49d751ba7d6647a720a52060ceebe42524704",
                "alg": "EdDSA",
                "signed": {
                    "canonicalization": "RFC8785",
                    "digest": "qEeF80eJMDkCt_wuk0CJ0J5_qp2cvkANf1AtW8KrXEg",
                    "target": "/event"
                },
                "jws": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDptYWlsaW86MHhiM2U0OWQ3NTFiYTdkNjY0N2E3MjBhNTIwNjBjZWViZTQyNTI0NzA0In0..NZjNlvbQwerjbfQUujzXlBK8P0-VbjBCBUpfdAuMUu10OQ-bqcsgIJfntaVTMjy43UAjNRGkoP-2R8odv8X1Aw"
                }
            ]
        }

        obj_b = {
            "type": "MailioReceipt",
            "v": "v1",
            "event": {
                "type": "MailioAuditEvent",
                "v": "v1",
                "id": "7f124fc9-53ae-431e-8db5-7581e6588276",
                "flow_id": "13860930-2800-4418-aeaa-fc4f91767132",
                "ts": "2026-02-16T20:36:13.937Z",
                "actors": {
                    "issuer": "urn:mailio:igordemo-local-69676f72406d61696c2e696f",
                    "subject": "did:web:mio.mailiomail.com:0xb3e49d751ba7d6647a720a52060ceebe42524704"
                },
                "intent": {
                    "kind": "trigger_watch_condition",
                    "action": "price_above",
                    "category": "stock_price",
                    "scope": {
                        "ticker": "MIO",
                        "direction": "price_above",
                        "threshold": 66
                    }
                }
            },
            "event_digest": "qEeF80eJMDkCt_wuk0CJ0J5_qp2cvkANf1AtW8KrXEg",
            "signatures": [
                {
                    "role": "user",
                    "signer": "urn:mailio:igordemo-local-69676f72406d61696c2e696f#webauthn-cred:EBACb3n-2F1HOkqIuCRRyA",
                    "alg": "ES256",
                    "signed": {
                        "canonicalization": "RFC8785",
                        "digest": "qEeF80eJMDkCt_wuk0CJ0J5_qp2cvkANf1AtW8KrXEg",
                        "target": "/event"
                    },
                    "jws": "MEQCIEKDcE2x_JWnIQZ1PgFrHYijvGzPuMpbi2prq8fANrHMAiAaSGmdZU4JPkrxpuPrn8V2GSir4yIXm0mborlrd4t1Rw",
                    "authenticator_data": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MdAAAAAA",
                    "client_data_json": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoicUVlRjgwZUpNRGtDdF93dWswQ0owSjVfcXAyY3ZrQU5mMUF0VzhLclhFZyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODg4OCIsImNyb3NzT3JpZ2luIjpmYWxzZSwib3RoZXJfa2V5c19jYW5fYmVfYWRkZWRfaGVyZSI6ImRvIG5vdCBjb21wYXJlIGNsaWVudERhdGFKU09OIGFnYWluc3QgYSB0ZW1wbGF0ZS4gU2VlIGh0dHBzOi8vZ29vLmdsL3lhYlBleCJ9"
                }
            ]
        }

        event_a = MailioAuditEvent.from_dict(obj_a["event"])
        event_b = MailioAuditEvent.from_dict(obj_b["event"])

        bytes_a = canonicalize(event_a.to_dict())
        bytes_b = canonicalize(event_b.to_dict())

        digest_a = compute_digest(event_a.to_dict())
        digest_b = compute_digest(event_b.to_dict())

        assert bytes_a == bytes_b, (
            f"CBOR bytes differ due to JSON key ordering.\n"
            f"  A hex: {bytes_a.hex()}\n"
            f"  B hex: {bytes_b.hex()}"
        )
        assert digest_a == digest_b
