"""Canonical JSON serialisation and hashing helpers for evidence workflows."""

from __future__ import annotations

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

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


def canonical_json_dumps(value: Any) -> str:
    """Return deterministic JSON text for a serialisable Python value."""
    return json.dumps(
        value,
        sort_keys=True,
        separators=(",", ":"),
        ensure_ascii=False,
        allow_nan=False,
    )


def canonical_json_bytes(value: Any) -> bytes:
    """Return UTF-8 bytes for deterministic 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 canonical bytes for a mapping payload with optional field exclusions."""
    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 canonical JSON bytes for a serialisable value."""
    return sha256_hex(canonical_json_bytes(value))
