"""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_key_ordering(self) -> None:
        """Test that keys are sorted in canonical form."""
        data = {"zebra": 1, "apple": 2, "mango": 3}
        result = canonicalize(data)

        # RFC8785 requires lexicographic key ordering
        decoded = result.decode("utf-8")
        assert decoded.index('"apple"') < decoded.index('"mango"')
        assert decoded.index('"mango"') < decoded.index('"zebra"')

    def test_nested_object_ordering(self) -> None:
        """Test that nested objects also have sorted keys."""
        data = {
            "outer": {
                "z": 1,
                "a": 2,
            }
        }
        result = canonicalize(data)
        decoded = result.decode("utf-8")

        # 'a' should appear before 'z' in the nested object
        assert decoded.index('"a"') < decoded.index('"z"')

    def test_no_extra_whitespace(self) -> None:
        """Test that canonical form has no extra whitespace."""
        data = {"key": "value"}
        result = canonicalize(data)
        decoded = result.decode("utf-8")

        # Should be compact with no spaces after colons or commas
        assert decoded == '{"key":"value"}'

    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 64 hex characters (SHA-256)."""
        data = {"key": "value"}
        digest = compute_digest(data)

        assert len(digest) == 64
        assert all(c in "0123456789abcdef" 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_known_digest(self) -> None:
        """Test against a known digest value."""
        # This ensures the implementation doesn't change unexpectedly
        data = {
    "type": "MailioAuditEvent",
    "v": "v1",
    "id": "fe31edad-2eb1-4e72-99e4-6ab7f1a27804",
    "flowId": "46c00538-8004-42b3-b29b-32d83ea03712",
    "ts": "2026-02-11T17:56:52.727Z",
    "actors": {
        "issuer": "igor@mail.io",
        "subject": "igordemo-local-69676f72406d61696c2e696f"
    },
    "intent": {
        "kind": "trigger_watch_condition",
        "action": "price_above",
        "category": "stock_price",
        "scope": {
            "ticker": "MIO",
            "direction": "price_above",
            "threshold": 23
        }
    }
}
        event = MailioAuditEvent.from_dict(data)
        digest = compute_digest(event.to_dict())

        # Pre-computed expected value
        # canonicalize({"hello": "world"}) = b'{"hello":"world"}'
        # sha256(b'{"hello":"world"}').hexdigest() = expected
        expected = "A-uL30GUyUaaVTIRaSaF5tLBAKEh-raxSqZ-oEo2j8I"
        assert digest == expected
