from __future__ import annotations

from typing import Any, Dict, Mapping, Optional, Union

from nacl.exceptions import BadSignatureError
from nacl.signing import VerifyKey

from .canonicalize import canonicalize_for_signing
from .encoding import decode_base64url, utf8_encode
from .types import VerifyResult, to_mapping


def _shape_error(receipt: Mapping[str, Any]) -> Optional[str]:
    if not receipt.get("receiptId"):
        return "missing required field: receiptId"

    agent = receipt.get("agent")
    if not isinstance(agent, Mapping) or not agent.get("id"):
        return "missing required field: agent.id"

    principal = receipt.get("principal")
    if not isinstance(principal, Mapping) or not principal.get("id") or not principal.get("type"):
        return "missing required field: principal"

    action = receipt.get("action")
    if not isinstance(action, Mapping) or not action.get("type") or not action.get("target") or not action.get("status"):
        return "missing required field: action"

    scope = receipt.get("scope")
    permissions = scope.get("permissions") if isinstance(scope, Mapping) else None
    if not isinstance(permissions, list) or len(permissions) == 0:
        return "missing required field: scope.permissions"

    input_hash = receipt.get("inputHash")
    output_hash = receipt.get("outputHash")
    if not isinstance(input_hash, Mapping) or not input_hash.get("digest") or not isinstance(output_hash, Mapping) or not output_hash.get("digest"):
        return "missing required field: hash objects"

    signature = receipt.get("signature")
    if not isinstance(signature, Mapping) or not signature.get("sig"):
        return "missing signature.sig"

    if signature.get("alg") != "Ed25519":
        return "unsupported signature.alg"

    if signature.get("canonicalization") != "JCS-SORTED-UTF8-NOWS":
        return "unsupported canonicalization"

    return None


def _resolve_public_key(receipt: Mapping[str, Any], explicit: Optional[Union[bytes, bytearray, str]] = None) -> Optional[bytes]:
    if isinstance(explicit, (bytes, bytearray)):
        return bytes(explicit)

    if isinstance(explicit, str) and len(explicit) > 0:
        return decode_base64url(explicit)

    signature = receipt.get("signature")
    if isinstance(signature, Mapping) and signature.get("publicKey"):
        return decode_base64url(str(signature["publicKey"]))

    agent = receipt.get("agent")
    if isinstance(agent, Mapping) and agent.get("publicKey"):
        return decode_base64url(str(agent["publicKey"]))

    return None


def verify_receipt(receipt: Union[Mapping[str, Any], Any], public_key: Optional[Union[bytes, bytearray, str]] = None) -> VerifyResult:
    receipt_obj = to_mapping(receipt, "receipt")

    err = _shape_error(receipt_obj)
    if err:
        return VerifyResult(ok=False, reason=err)

    try:
        key = _resolve_public_key(receipt_obj, public_key)
        if not key:
            return VerifyResult(ok=False, reason="no public key provided")

        if len(key) != 32:
            return VerifyResult(ok=False, reason="invalid public key length")

        signature = receipt_obj["signature"]
        sig = decode_base64url(str(signature["sig"]))
        if len(sig) != 64:
            return VerifyResult(ok=False, reason="invalid signature length")

        msg = utf8_encode(canonicalize_for_signing(receipt_obj))

        try:
            VerifyKey(key).verify(msg, sig)
            return VerifyResult(ok=True)
        except BadSignatureError:
            return VerifyResult(ok=False, reason="signature verification failed")
    except Exception as exc:  # pragma: no cover - defensive parity with TS
        return VerifyResult(ok=False, reason=str(exc) if str(exc) else "verification error")


verifyReceipt = verify_receipt

__all__ = ["verify_receipt", "verifyReceipt"]
