"""Acyclic r71b maintenance package derivation and replay verification."""

from __future__ import annotations

import hashlib
from dataclasses import dataclass
from typing import Final, override

from pydantic import JsonValue, TypeAdapter, ValidationError

from gateway.platforms.nutrition_weekly_maintenance_contract import (
    Topic59MaintenanceAuthorityV1,
    Topic59MaintenanceContractError,
    Topic59MaintenanceHoldV1,
    Topic59MaintenanceScopeV1,
    canonical_document,
    canonical_json,
    derive_hold_id,
    final_package_digest,
    maintenance_scope_digest,
    package_binding_digest,
)

_BINDING_SCHEMA: Final = "nutricoach-v150-r71b-package-binding-inputs-v1"
_PAYLOAD_SCHEMA: Final = "nutricoach-v150-r71b-final-package-payload-v1"
_PACKAGE_SCHEMA: Final = "nutricoach-v150-r71b-permission-package-v1"
_AUTHORITY_PATH: Final = (
    "maintenance-authority/nutricoach-topic59-maintenance-r71b.json"
)
_BINDING_PATH: Final = "maintenance/package-binding-inputs.json"
_FORBIDDEN_BINDING_KEYS: Final = frozenset({
    "package_binding_digest", "binding_inputs_file_sha256", "hold_id",
    "hold_sha256", "authority_sha256", "final_package_digest",
    "package_digest", "approval_phrase", "permission_package_sha256",
    "sealed_target_sha256", "row_digest",
})
_OBJECT = TypeAdapter(dict[str, JsonValue])


@dataclass(frozen=True, slots=True)
class PackageDerivationError(Exception):
    reason: str

    @override
    def __str__(self) -> str:
        return self.reason


@dataclass(frozen=True, slots=True)
class R71bPackageArtifacts:
    binding_inputs_bytes: bytes
    binding_inputs_file_sha256: str
    package_binding_digest: str
    hold: Topic59MaintenanceHoldV1
    hold_bytes: bytes
    hold_sha256: str
    authority: Topic59MaintenanceAuthorityV1
    authority_bytes: bytes
    authority_sha256: str
    final_payload: dict[str, JsonValue]
    final_package_digest: str
    permission_package: dict[str, JsonValue]
    permission_package_bytes: bytes


def derive_r71b_package(
    binding_inputs: dict[str, JsonValue],
    scope: Topic59MaintenanceScopeV1,
) -> R71bPackageArtifacts:
    """Derive both digest stages once, without placeholders or rewrites."""
    _validate_binding_inputs(binding_inputs, scope)
    binding_bytes = canonical_json(binding_inputs) + b"\n"
    binding_sha = hashlib.sha256(binding_bytes).hexdigest()
    binding_digest = package_binding_digest(binding_inputs)
    scope_digest = maintenance_scope_digest(scope)
    hold = Topic59MaintenanceHoldV1(
        hold_id=derive_hold_id(binding_digest, scope_digest),
        package_binding_digest=binding_digest,
        scope=scope,
    )
    hold_bytes = canonical_document(hold)
    hold_sha = hashlib.sha256(hold_bytes).hexdigest()
    authority = Topic59MaintenanceAuthorityV1(
        package_binding_digest=binding_digest,
        hold_sha256=hold_sha,
        hold=hold,
    )
    authority_bytes = canonical_document(authority)
    authority_sha = hashlib.sha256(authority_bytes).hexdigest()
    payload = _OBJECT.validate_python({
        "schema": _PAYLOAD_SCHEMA,
        "package_binding_inputs": binding_inputs,
        "package_binding_inputs_path": _BINDING_PATH,
        "package_binding_inputs_file_sha256": binding_sha,
        "package_binding_digest": binding_digest,
        "maintenance_authority_path": _AUTHORITY_PATH,
        "maintenance_authority_sha256": authority_sha,
        "maintenance_hold_id": hold.hold_id,
        "maintenance_hold_sha256": hold_sha,
    })
    final_digest = final_package_digest(payload)
    approval = f"AUTHORIZE NUTRICOACH V1.5 R71B MAINTENANCE UPGRADE {final_digest}"
    package = _OBJECT.validate_python({
        "schema": _PACKAGE_SCHEMA,
        "package_digest": final_digest,
        "approval_phrase": approval,
        "payload": payload,
    })
    return R71bPackageArtifacts(
        binding_inputs_bytes=binding_bytes,
        binding_inputs_file_sha256=binding_sha,
        package_binding_digest=binding_digest,
        hold=hold,
        hold_bytes=hold_bytes,
        hold_sha256=hold_sha,
        authority=authority,
        authority_bytes=authority_bytes,
        authority_sha256=authority_sha,
        final_payload=payload,
        final_package_digest=final_digest,
        permission_package=package,
        permission_package_bytes=canonical_json(package) + b"\n",
    )


def verify_r71b_package_derivation(
    binding_inputs_bytes: bytes,
    scope_bytes: bytes,
    authority_bytes: bytes,
    permission_package_bytes: bytes,
) -> R71bPackageArtifacts:
    """Independently replay every retained derivation stage from exact bytes."""
    try:
        binding_inputs = _OBJECT.validate_json(binding_inputs_bytes)
        scope = Topic59MaintenanceScopeV1.model_validate_json(scope_bytes)
        authority = Topic59MaintenanceAuthorityV1.model_validate_json(authority_bytes)
        package = _OBJECT.validate_json(permission_package_bytes)
    except (ValidationError, Topic59MaintenanceContractError) as error:
        raise PackageDerivationError("schema") from error
    if (
        canonical_json(binding_inputs) + b"\n" != binding_inputs_bytes
        or canonical_document(scope) != scope_bytes
        or canonical_document(authority) != authority_bytes
        or canonical_json(package) + b"\n" != permission_package_bytes
    ):
        raise PackageDerivationError("noncanonical")
    expected = derive_r71b_package(binding_inputs, scope)
    if authority_bytes != expected.authority_bytes:
        raise PackageDerivationError("authority")
    if package != expected.permission_package:
        raise PackageDerivationError("final_package")
    return expected


def _validate_binding_inputs(
    binding_inputs: dict[str, JsonValue], scope: Topic59MaintenanceScopeV1,
) -> None:
    if (
        binding_inputs.get("schema") != _BINDING_SCHEMA
        or binding_inputs.get("product_generation") != "r71"
        or binding_inputs.get("package_namespace") != "r71b-maintenance"
        or binding_inputs.get("authority_id")
        != "nutricoach-v150-v15-runtime-authority-r71b-maintenance"
        or binding_inputs.get("maintenance_scope")
        != scope.model_dump(mode="json", by_alias=True)
        or _contains_forbidden_key(binding_inputs)
        or _contains_float(binding_inputs)
    ):
        raise PackageDerivationError("binding_inputs")


def _contains_forbidden_key(value: JsonValue) -> bool:
    if isinstance(value, dict):
        return bool(_FORBIDDEN_BINDING_KEYS.intersection(value)) or any(
            _contains_forbidden_key(child) for child in value.values()
        )
    if isinstance(value, list):
        return any(_contains_forbidden_key(row) for row in value)
    return False


def _contains_float(value: JsonValue) -> bool:
    if type(value) is float:
        return True
    if isinstance(value, dict):
        return any(_contains_float(child) for child in value.values())
    if isinstance(value, list):
        return any(_contains_float(row) for row in value)
    return False
