from __future__ import annotations

import json
import uuid
from datetime import datetime, timezone
from typing import Any, Dict, Mapping, Union

from .encoding import encode_base64, utf8_encode
from .sign import sign_receipt
from .types import to_mapping, to_plain


def _create_receipt_id() -> str:
    return str(uuid.uuid4())


def _iso_now() -> str:
    return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")


def create_receipt(opts: Union[Mapping[str, Any], Any]) -> Dict[str, Any]:
    opts_obj = to_mapping(opts, "opts")

    agent = opts_obj.get("agent")
    if not isinstance(agent, Mapping) or not agent.get("id"):
        raise ValueError("opts.agent.id is required")

    signature_opts = opts_obj.get("signature")
    if signature_opts is None:
        signature_opts = {}
    if not isinstance(signature_opts, Mapping):
        raise ValueError("opts.signature must be an object")

    signature = {
        "alg": "Ed25519",
        "canonicalization": "JCS-SORTED-UTF8-NOWS",
        "kid": signature_opts.get("kid") or (str(agent["id"]) + "#key-1"),
    }

    if "publicKey" in signature_opts and signature_opts.get("publicKey") is not None:
        signature["publicKey"] = signature_opts.get("publicKey")

    if "sig" in signature_opts and signature_opts.get("sig") is not None:
        signature["sig"] = signature_opts.get("sig")

    return {
        "receiptId": opts_obj.get("receiptId") or _create_receipt_id(),
        "agent": to_plain(opts_obj["agent"]),
        "principal": to_plain(opts_obj["principal"]),
        "action": to_plain(opts_obj["action"]),
        "scope": to_plain(opts_obj["scope"]),
        "inputHash": to_plain(opts_obj["inputHash"]),
        "outputHash": to_plain(opts_obj["outputHash"]),
        "timestamp": opts_obj.get("timestamp") or _iso_now(),
        "cost": to_plain(opts_obj["cost"]),
        "signature": signature,
        "metadata": to_plain(opts_obj.get("metadata") or {}),
    }


def sign_and_finalize(unsigned: Union[Mapping[str, Any], Any], secret_key: Union[bytes, bytearray, str]) -> Dict[str, Any]:
    return sign_receipt(unsigned, secret_key)


def encode_receipt_header(receipt: Union[Mapping[str, Any], Any]) -> str:
    receipt_obj = to_plain(receipt)
    text = json.dumps(receipt_obj, ensure_ascii=False, separators=(",", ":"))
    return encode_base64(utf8_encode(text))


createReceipt = create_receipt
signAndFinalize = sign_and_finalize
encodeReceiptHeader = encode_receipt_header

__all__ = [
    "create_receipt",
    "createReceipt",
    "sign_and_finalize",
    "signAndFinalize",
    "encode_receipt_header",
    "encodeReceiptHeader",
]
