"""Deterministic Python-reference JSON serialisation and hashing helpers.

These helpers use ``json.dumps(..., sort_keys=True, separators=(",", ":"),
ensure_ascii=False, allow_nan=False)`` followed by UTF-8 encoding where bytes
are needed. This is the repository's current reference canonicalisation
contract for evidence hashes and signable bytes; it is not RFC 8785 / JCS
canonical JSON.
"""

from __future__ import annotations

import hashlib
import json
from collections.abc import Iterable, Mapping
from typing import Any

from .errors import EvidenceSerialisationError

__all__ = [
    "canonical_json_dumps",
    "canonical_json_bytes",
    "canonical_json_bytes_for_signing",
    "hash_canonical_json",
    "sha256_hex",
]

_EVIDENCE_SERIALISATION_MESSAGE = (
    "ICSD evidence data must be canonical-JSON-serialisable before hashes, "
    "signatures, or evidence JSON can be generated."
)


def canonical_json_dumps(value: Any) -> str:
    """Return reference canonical JSON text for a serialisable Python value.

    The encoding is the current Python-reference contract:
    ``json.dumps(..., sort_keys=True, separators=(",", ":"), ensure_ascii=False,
    allow_nan=False)``. It is deterministic for this implementation but is not
    RFC 8785 / JCS canonical JSON.
    """
    try:
        return json.dumps(
            value,
            sort_keys=True,
            separators=(",", ":"),
            ensure_ascii=False,
            allow_nan=False,
        )
    except (TypeError, ValueError) as exc:
        raise EvidenceSerialisationError(_EVIDENCE_SERIALISATION_MESSAGE) from exc


def canonical_json_bytes(value: Any) -> bytes:
    """Return UTF-8 bytes for the current Python-reference canonical JSON text."""
    return canonical_json_dumps(value).encode("utf-8")


def canonical_json_bytes_for_signing(
    payload: Mapping[str, Any],
    *,
    exclude_fields: Iterable[str] = (),
) -> bytes:
    """Return UTF-8 bytes for the filtered Python-reference canonical payload.

    Fields listed in ``exclude_fields`` are removed from the mapping before the
    remaining payload is encoded with the same reference JSON rules used by
    ``canonical_json_dumps(...)``.
    """
    if not isinstance(payload, Mapping):
        msg = "payload must be a mapping."
        raise TypeError(msg)
    excluded = {field for field in exclude_fields}
    filtered_payload = {key: value for key, value in payload.items() if key not in excluded}
    return canonical_json_bytes(filtered_payload)


def sha256_hex(data: bytes) -> str:
    """Return a lowercase hexadecimal SHA-256 digest for bytes."""
    return hashlib.sha256(data).hexdigest()


def hash_canonical_json(value: Any) -> str:
    """Return SHA-256 over the Python-reference canonical JSON byte contract."""
    return sha256_hex(canonical_json_bytes(value))
