"""Strict YAML input and signed manifest file helpers."""

from __future__ import annotations

import json
from collections.abc import Mapping
from pathlib import Path
from typing import Any

import yaml
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

from .canonical import canonical_json
from .crypto import TrustStore, sign_envelope, verify_envelope
from .errors import ManifestError
from .models import ScopeManifest, SignedEnvelope

MAX_MANIFEST_BYTES = 256 * 1024


class _UniqueKeySafeLoader(yaml.SafeLoader):
    pass


def _construct_unique_mapping(
    loader: _UniqueKeySafeLoader,
    node: yaml.MappingNode,
    deep: bool = False,
) -> dict[Any, Any]:
    mapping: dict[Any, Any] = {}
    for key_node, value_node in node.value:
        key = loader.construct_object(key_node, deep=deep)
        if key in mapping:
            raise ManifestError(f"duplicate YAML key: {key}")
        mapping[key] = loader.construct_object(value_node, deep=deep)
    return mapping


_UniqueKeySafeLoader.add_constructor(
    yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
    _construct_unique_mapping,
)


def load_manifest_yaml_bytes(body: bytes) -> ScopeManifest:
    if len(body) > MAX_MANIFEST_BYTES:
        raise ManifestError("manifest exceeds the 256 KiB input limit")
    try:
        text = body.decode("utf-8")
    except UnicodeDecodeError as exc:
        raise ManifestError("manifest must be UTF-8") from exc
    try:
        documents = list(yaml.load_all(text, Loader=_UniqueKeySafeLoader))
    except ManifestError:
        raise
    except (yaml.YAMLError, TypeError, RecursionError) as exc:
        raise ManifestError("manifest YAML is invalid") from exc
    if len(documents) != 1 or not isinstance(documents[0], Mapping):
        raise ManifestError("manifest must contain exactly one YAML mapping")
    count = 0
    active: set[int] = set()
    def bounded(value: Any, depth: int = 0) -> None:
        nonlocal count
        count += 1
        if depth > 24 or count > 10000:
            raise ManifestError("manifest structure exceeds the supported depth or size")
        if isinstance(value, (dict, list)):
            if id(value) in active:
                raise ManifestError("recursive YAML aliases are not supported")
            active.add(id(value))
            for child in (value.values() if isinstance(value, dict) else value):
                bounded(child, depth + 1)
            active.remove(id(value))
    bounded(documents[0])
    try:
        return ScopeManifest.model_validate(dict(documents[0]))
    except (ValueError, TypeError, RecursionError) as exc:
        raise ManifestError("manifest schema is invalid") from exc


def load_manifest_yaml(path: str | Path) -> ScopeManifest:
    try:
        body = Path(path).read_bytes()
    except OSError as exc:
        raise ManifestError("manifest file is not readable") from exc
    return load_manifest_yaml_bytes(body)


def sign_manifest_file(
    *,
    manifest_path: str | Path,
    destination: str | Path,
    private_key: Ed25519PrivateKey,
    key_id: str,
) -> SignedEnvelope:
    manifest = load_manifest_yaml(manifest_path)
    envelope = sign_envelope(
        manifest.model_dump(mode="json"),
        private_key,
        key_id,
    )
    target = Path(destination)
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_bytes(canonical_json(envelope.model_dump(mode="json")) + b"\n")
    return envelope


def load_signed_manifest(
    path: str | Path,
    trust: TrustStore,
) -> tuple[ScopeManifest, SignedEnvelope]:
    try:
        raw = json.loads(Path(path).read_text(encoding="utf-8"))
        envelope = SignedEnvelope.model_validate(raw)
        payload = verify_envelope(envelope, trust)
        manifest = ScopeManifest.model_validate(payload)
    except ManifestError:
        raise
    except Exception as exc:
        raise ManifestError("signed manifest is invalid or untrusted") from exc
    return manifest, envelope
