from __future__ import annotations

import json
from typing import Any, Dict, Mapping, Tuple

from receipts.cbor_encoder import decode as _cbor_decode, encode as _cbor_encode
from receipts.jcs import canonicalize as jcs_canonicalize
from receipts.models import BaseReceipt, load_receipt

__all__ = [
    "payload_to_cbor",
    "cbor_to_payload",
    "receipt_to_cbor",
    "load_cbor_receipt",
]


def _as_canonical_node(payload: Mapping[str, Any]) -> Dict[str, Any]:
    """
    Return a canonicalised mapping suitable for deterministic CBOR encoding.

    The helper round-trips through RFC 8785 canonical JSON to ensure field
    ordering honours the canonical rules used for hashing and signatures.
    """
    canonical_json = jcs_canonicalize(payload)
    return json.loads(canonical_json.decode("utf-8"))


def payload_to_cbor(payload: Mapping[str, Any]) -> bytes:
    """
    Encode a receipt payload mapping into deterministic CBOR bytes.
    """
    canonical_node = _as_canonical_node(payload)
    return _cbor_encode(canonical_node)


def cbor_to_payload(blob: bytes) -> Dict[str, Any]:
    """
    Decode deterministic CBOR bytes back into a Python mapping.
    """
    node = _cbor_decode(blob)
    if not isinstance(node, dict):
        raise ValueError("CBOR receipt payload must decode to a mapping.")
    return node


def receipt_to_cbor(receipt: BaseReceipt) -> bytes:
    """
    Encode a ``BaseReceipt`` instance into deterministic CBOR bytes.
    """
    payload = receipt.model_dump(mode="json", by_alias=True)
    return payload_to_cbor(payload)


def load_cbor_receipt(blob: bytes) -> BaseReceipt:
    """
    Decode a CBOR payload into a strongly-typed receipt model.
    """
    payload = cbor_to_payload(blob)
    return load_receipt(payload)
