from __future__ import annotations

from dataclasses import dataclass, fields, is_dataclass, field
from typing import Any, Dict, List, Literal, Mapping, Optional, Sequence, Tuple, Union

HashAlgorithm = Literal["sha256", "sha384", "sha512", "blake3"]
PrincipalType = Literal["user", "organization", "service", "agent", "other"]
ActionStatus = Literal["success", "failure", "partial"]


@dataclass
class HashObject:
    alg: HashAlgorithm
    digest: str


@dataclass
class AgentIdentity:
    id: str
    name: Optional[str] = None
    version: Optional[str] = None
    publicKey: Optional[str] = None
    extra: Dict[str, Any] = field(default_factory=dict)


@dataclass
class Principal:
    id: str
    type: PrincipalType
    extra: Dict[str, Any] = field(default_factory=dict)


@dataclass
class Action:
    type: str
    target: str
    status: ActionStatus
    method: Optional[str] = None
    extra: Dict[str, Any] = field(default_factory=dict)


@dataclass
class Scope:
    permissions: List[str]
    constraints: Optional[Dict[str, Any]] = None
    x402: Optional[Dict[str, Any]] = None
    extra: Dict[str, Any] = field(default_factory=dict)


@dataclass
class Cost:
    amount: str
    currency: str
    unit: Optional[str] = None
    payer: Optional[str] = None
    extra: Dict[str, Any] = field(default_factory=dict)


@dataclass
class Signature:
    kid: str
    sig: str
    alg: Literal["Ed25519"] = "Ed25519"
    publicKey: Optional[str] = None
    canonicalization: Literal["JCS-SORTED-UTF8-NOWS"] = "JCS-SORTED-UTF8-NOWS"


@dataclass
class UnsignedSignature:
    kid: str
    alg: Literal["Ed25519"] = "Ed25519"
    publicKey: Optional[str] = None
    canonicalization: Literal["JCS-SORTED-UTF8-NOWS"] = "JCS-SORTED-UTF8-NOWS"
    sig: Optional[str] = None


@dataclass
class AARReceipt:
    receiptId: str
    agent: Union[AgentIdentity, Mapping[str, Any]]
    principal: Union[Principal, Mapping[str, Any]]
    action: Union[Action, Mapping[str, Any]]
    scope: Union[Scope, Mapping[str, Any]]
    inputHash: Union[HashObject, Mapping[str, Any]]
    outputHash: Union[HashObject, Mapping[str, Any]]
    timestamp: str
    cost: Union[Cost, Mapping[str, Any]]
    signature: Union[Signature, Mapping[str, Any]]
    metadata: Dict[str, Any]


@dataclass
class UnsignedReceipt:
    receiptId: str
    agent: Union[AgentIdentity, Mapping[str, Any]]
    principal: Union[Principal, Mapping[str, Any]]
    action: Union[Action, Mapping[str, Any]]
    scope: Union[Scope, Mapping[str, Any]]
    inputHash: Union[HashObject, Mapping[str, Any]]
    outputHash: Union[HashObject, Mapping[str, Any]]
    timestamp: str
    cost: Union[Cost, Mapping[str, Any]]
    signature: Union[UnsignedSignature, Mapping[str, Any]]
    metadata: Dict[str, Any]


@dataclass
class CreateReceiptOptions:
    agent: Union[AgentIdentity, Mapping[str, Any]]
    principal: Union[Principal, Mapping[str, Any]]
    action: Union[Action, Mapping[str, Any]]
    scope: Union[Scope, Mapping[str, Any]]
    inputHash: Union[HashObject, Mapping[str, Any]]
    outputHash: Union[HashObject, Mapping[str, Any]]
    cost: Union[Cost, Mapping[str, Any]]
    receiptId: Optional[str] = None
    timestamp: Optional[str] = None
    metadata: Optional[Dict[str, Any]] = None
    signature: Optional[Mapping[str, Any]] = None


@dataclass
class VerifyResult:
    ok: bool
    reason: Optional[str] = None


@dataclass
class KeyPair:
    publicKey: bytes
    secretKey: bytes


@dataclass
class WellKnownAARAgent:
    id: str
    publicKey: str
    name: Optional[str] = None
    version: Optional[str] = None


@dataclass
class WellKnownAARConfiguration:
    specVersion: str
    canonicalization: Literal["JCS-SORTED-UTF8-NOWS"]
    signatureAlgorithms: Tuple[Literal["Ed25519"], ...]
    hashAlgorithms: Tuple[Literal["sha256"], ...]
    receiptHeader: str
    agent: Union[WellKnownAARAgent, Mapping[str, Any]]


JSONScalar = Union[None, bool, int, float, str]
JSONValue = Union[JSONScalar, List["JSONValue"], Dict[str, "JSONValue"]]


def to_plain(value: Any) -> Any:
    """Recursively convert dataclasses to plain Python containers.

    Optional dataclass fields set to None are omitted so serialization behavior
    matches TS optional properties (undefined -> omitted by JSON.stringify).
    """
    if is_dataclass(value):
        out: Dict[str, Any] = {}
        extras: Dict[str, Any] = {}
        for dc_field in fields(value):
            item = getattr(value, dc_field.name)
            if dc_field.name == "extra":
                if isinstance(item, Mapping):
                    extras = {str(k): to_plain(v) for k, v in item.items()}
                continue
            if item is None:
                continue
            out[dc_field.name] = to_plain(item)
        out.update(extras)
        return out

    if isinstance(value, Mapping):
        return {str(k): to_plain(v) for k, v in value.items()}

    if isinstance(value, (list, tuple)):
        return [to_plain(v) for v in value]

    return value


def to_mapping(value: Union[Mapping[str, Any], Any], label: str) -> Dict[str, Any]:
    plain = to_plain(value)
    if not isinstance(plain, dict):
        raise TypeError("%s must be an object" % label)
    return plain


__all__ = [
    "AARReceipt",
    "Action",
    "ActionStatus",
    "AgentIdentity",
    "Cost",
    "CreateReceiptOptions",
    "HashAlgorithm",
    "HashObject",
    "JSONValue",
    "KeyPair",
    "Principal",
    "PrincipalType",
    "Scope",
    "Signature",
    "UnsignedReceipt",
    "UnsignedSignature",
    "VerifyResult",
    "WellKnownAARAgent",
    "WellKnownAARConfiguration",
    "to_plain",
    "to_mapping",
]
