"""Independent replay verifier for a sealed r71b maintenance package."""

from __future__ import annotations

import hashlib
import stat
import subprocess
import zipfile
from dataclasses import dataclass
from pathlib import Path
from typing import final

from pydantic import JsonValue, TypeAdapter, ValidationError

from gateway.platforms.task26_runtime_authority import build_runtime_authority_pin
from scripts.nutricoach_v150_live_upgrade_common import canonical
from scripts.nutricoach_v150_r71b_preparation import (
    MaintenancePreparationError,
    MaintenanceWindow,
    NoSendFacts,
    SanitizedTopic59Preflight,
    collect_authoritative_r71b_preflight,
    parse_maintenance_window,
    prepare_r71b_maintenance,
)
from scripts.nutricoach_v150_r71b_package import (
    PackageDerivationError,
    verify_r71b_package_derivation,
)
from scripts.nutricoach_v150_runtime_ops import dependency_snapshot_digest
from scripts.verify_nutricoach_v150_candidate import verify as verify_candidate

_OBJECT = TypeAdapter(dict[str, JsonValue])


class R71bPresealVerificationError(RuntimeError):
    """A sealed target cannot be replayed from its authoritative source files."""


def verify_r71b_preseal(target_path: Path) -> dict[str, str]:
    """Reconstruct inputs before replaying every retained package artifact.

    ``package-binding-inputs.json`` is evidence, not an input to this verifier:
    the candidate, wheels, controller closure, profile projection, authority,
    dependency tree, service state, and diagnosed evidence are read again from
    the sealed target's authoritative locations.
    """
    target = _load(target_path)
    package_path = Path(_text(target, "permission_package"))
    authority_path = Path(_text(target, "maintenance_authority_path"))
    scope_path = Path(_text(target, "maintenance_scope_path"))
    binding_path = scope_path.parent / "package-binding-inputs.json"
    reconstructed = _reconstruct_binding_inputs(target_path, target)
    try:
        prepared = prepare_r71b_maintenance(
            reconstructed.preflight,
            reconstructed.window,
            reconstructed.facts,
            reconstructed.binding_seed,
        )
        artifacts = verify_r71b_package_derivation(
            prepared.artifacts.binding_inputs_bytes,
            scope_path.read_bytes(),
            authority_path.read_bytes(),
            package_path.read_bytes(),
        )
    except (MaintenancePreparationError, OSError, PackageDerivationError) as error:
        raise R71bPresealVerificationError("r71b_derivation") from error
    try:
        stored_binding = binding_path.read_bytes()
    except OSError as error:
        raise R71bPresealVerificationError("r71b_binding_inputs") from error
    if stored_binding != prepared.artifacts.binding_inputs_bytes:
        raise R71bPresealVerificationError("r71b_binding_reconstruction")
    expected = {
        "package_digest": artifacts.final_package_digest,
        "approval_phrase": str(artifacts.permission_package["approval_phrase"]),
        "package_binding_digest": artifacts.package_binding_digest,
        "maintenance_authority_sha256": artifacts.authority_sha256,
        "maintenance_hold_id": artifacts.hold.hold_id,
        "maintenance_hold_sha256": artifacts.hold_sha256,
        "maintenance_scope_sha256": hashlib.sha256(scope_path.read_bytes()).hexdigest(),
    }
    if any(target.get(key) != value for key, value in expected.items()):
        raise R71bPresealVerificationError("r71b_target_binding")
    return {
        "approval_phrase": expected["approval_phrase"],
        "package_digest": expected["package_digest"],
        "package_sha256": hashlib.sha256(package_path.read_bytes()).hexdigest(),
    }


@final
@dataclass(frozen=True, slots=True)
class _ReconstructedInputs:
    """Internal source-derived inputs retained only for one verification pass."""

    preflight: SanitizedTopic59Preflight
    window: MaintenanceWindow
    facts: NoSendFacts
    binding_seed: dict[str, JsonValue]


def _reconstruct_binding_inputs(
    target_path: Path,
    target: dict[str, JsonValue],
) -> _ReconstructedInputs:
    candidate_manifest = Path(_text(target, "candidate_manifest"))
    candidate_root = candidate_manifest.parent
    candidate_digest = verify_candidate(
        candidate_root / "inputs/base-manifest.json",
        candidate_root,
        candidate_manifest,
    )
    if candidate_digest != _text(target, "candidate_digest"):
        raise R71bPresealVerificationError("candidate_binding")
    if _sha(candidate_manifest) != _text(target, "candidate_manifest_sha256"):
        raise R71bPresealVerificationError("candidate_manifest")
    window_document = _mapping(target.get("maintenance_window"), "maintenance_window")
    window = parse_maintenance_window(
        _text(window_document, "kst_day"),
        _text(window_document, "not_before"),
        _text(window_document, "expires_at"),
    )
    profile = Path(_text(target, "profile_root"))
    collected = collect_authoritative_r71b_preflight(candidate_root, profile, window)
    preseal = target_path.parent
    controller_files = _controller_files(preseal / "controller-source")
    target_binding = _target_binding(target)
    baseline = _reconstruct_authority_baseline(target)
    wheels = _reconstruct_wheels(target)
    protected = Path(_text(target, "protected_inventory"))
    dependency = Path(_text(target, "dependency_snapshot"))
    if (
        _sha(protected) != _text(target, "protected_inventory_sha256")
        or dependency_snapshot_digest(dependency)
        != _text(target, "dependency_snapshot_sha256")
    ):
        raise R71bPresealVerificationError("target_source_binding")
    service = _service_state(_text(target, "service_name"))
    if service != _mapping(target.get("service_preparation"), "service_preparation"):
        raise R71bPresealVerificationError("service_preparation")
    binding_seed = _OBJECT.validate_python({
        "schema": "nutricoach-v150-r71b-package-binding-inputs-v1",
        "product_generation": "r71",
        "package_namespace": "r71b-maintenance",
        "authority_id": _text(target, "authority_id"),
        "paths": {"preseal_root": str(preseal)},
        "candidate": {
            "candidate_digest": candidate_digest,
            "manifest": str(candidate_manifest),
            "manifest_sha256": _sha(candidate_manifest),
        },
        "wheels": wheels,
        "controller": {
            "files": controller_files,
            "derivation_sha256": hashlib.sha256(canonical(controller_files)).hexdigest(),
        },
        "protected_inventory": {
            "path": str(protected), "sha256": _sha(protected),
            "explicit_maintenance_volatile_paths": [],
        },
        "dependency_snapshot": {
            "path": str(dependency),
            "dependency_snapshot_sha256": dependency_snapshot_digest(dependency),
        },
        "authority_baseline": baseline,
        "target_binding_base": target_binding,
        "service_preparation": service,
        "diagnosed_blocker_evidence": [
            collected.evidence["observer_evidence"],
            collected.evidence["fixed_collector_evidence"],
        ],
        **collected.evidence,
    })
    return _ReconstructedInputs(
        collected.preflight, window, collected.facts, binding_seed
    )


def _controller_files(root: Path) -> dict[str, JsonValue]:
    if not root.is_dir() or root.is_symlink():
        raise R71bPresealVerificationError("controller_source")
    files: dict[str, JsonValue] = {}
    for path in root.rglob("*"):
        if path.is_file() and not path.is_symlink():
            files[path.relative_to(root).as_posix()] = _sha(path)
    if not files:
        raise R71bPresealVerificationError("controller_source")
    return dict(sorted(files.items()))


def _reconstruct_wheels(target: dict[str, JsonValue]) -> list[JsonValue]:
    rows = target.get("wheels")
    if not isinstance(rows, list) or len(rows) != 2:
        raise R71bPresealVerificationError("wheels")
    wheels: list[JsonValue] = []
    for row in rows:
        values = _mapping(row, "wheel")
        path = Path(_text(values, "path"))
        if _sha(path) != _text(values, "sha256") or _record_sha(path) != _text(values, "record_sha256"):
            raise R71bPresealVerificationError("wheel")
        wheels.append({
            "path": str(path),
            "sha256": _sha(path),
            "record_sha256": _record_sha(path),
        })
    return wheels


def _reconstruct_authority_baseline(
    target: dict[str, JsonValue],
) -> dict[str, JsonValue]:
    expected = _mapping(target.get("authority_baseline"), "authority_baseline")
    root = Path(_text(expected, "authority_root"))
    pin_path = Path(_text(expected, "pin_path"))
    candidate_path = Path(_text(expected, "candidate_path"))
    registry = root / "candidate-authority/registry.json"
    ledger = root / "candidate-authority/qualification-ledger.json"
    pin = build_runtime_authority_pin(root)
    registry_document = _load(registry)
    events = registry_document.get("events")
    if not isinstance(events, list) or not events or not isinstance(events[-1], dict):
        raise R71bPresealVerificationError("authority_baseline")
    historical_pass = events[-1].get("historical_pass_digest")
    if not isinstance(historical_pass, str):
        raise R71bPresealVerificationError("authority_baseline")
    actual = _OBJECT.validate_python({
        "authority_root": str(root),
        "candidate_digest": candidate_path.read_text(encoding="utf-8").strip(),
        "candidate_path": str(candidate_path),
        "candidate_file_sha256": _sha(candidate_path),
        "event_count": pin["event_count"],
        "genesis_sha256": pin["genesis_sha256"],
        "historical_pass_digest": historical_pass,
        "ledger_file_sha256": _sha(ledger),
        "ledger_head_sha256": pin["ledger_head_sha256"],
        "pin_path": str(pin_path),
        "pin_file_sha256": _sha(pin_path),
        "registry_file_sha256": _sha(registry),
        "registry_head_sha256": pin["registry_head_sha256"],
        "source_id": pin["source_id"],
    })
    if actual != expected:
        raise R71bPresealVerificationError("authority_baseline")
    return actual


def _target_binding(target: dict[str, JsonValue]) -> dict[str, JsonValue]:
    keys = (
        "authority_id", "profile_root", "current_runtime", "successor_runtime",
        "service_name", "unit", "dropin", "execution_root",
        "global_approval_ledger", "protected_inventory",
        "protected_inventory_sha256", "dependency_snapshot",
        "dependency_snapshot_sha256", "registry_sha256",
    )
    return {key: target[key] for key in keys if key in target}


def _service_state(service_name: str) -> dict[str, JsonValue]:
    fields = ("ActiveState", "SubState", "MainPID", "ExecStart", "NRestarts")
    try:
        result = subprocess.run(
            (
                "/usr/bin/systemctl", "--user", "show", service_name,
                *(f"--property={field}" for field in fields),
            ),
            check=True,
            capture_output=True,
            text=True,
        )
    except (OSError, subprocess.CalledProcessError) as error:
        raise R71bPresealVerificationError("service_preparation") from error
    rows = dict(
        line.split("=", 1) for line in result.stdout.splitlines() if "=" in line
    )
    return {field: rows.get(field, "") for field in fields}


def _record_sha(path: Path) -> str:
    try:
        with zipfile.ZipFile(path) as wheel:
            record = next(name for name in wheel.namelist() if name.endswith("/RECORD"))
            return hashlib.sha256(wheel.read(record)).hexdigest()
    except (OSError, StopIteration, zipfile.BadZipFile) as error:
        raise R71bPresealVerificationError("wheel_record") from error


def _sha(path: Path) -> str:
    try:
        info = path.stat(follow_symlinks=False)
        if path.is_symlink() or not stat.S_ISREG(info.st_mode) or info.st_nlink != 1:
            raise R71bPresealVerificationError("source_file")
        return hashlib.sha256(path.read_bytes()).hexdigest()
    except OSError as error:
        raise R71bPresealVerificationError("source_file") from error


def _load(path: Path) -> dict[str, JsonValue]:
    try:
        return _OBJECT.validate_json(path.read_bytes())
    except (OSError, ValidationError) as error:
        raise R71bPresealVerificationError("sealed_target") from error


def _mapping(value: JsonValue | None, label: str) -> dict[str, JsonValue]:
    if not isinstance(value, dict):
        raise R71bPresealVerificationError(label)
    return value


def _text(target: dict[str, JsonValue], label: str) -> str:
    value = target.get(label)
    if not isinstance(value, str):
        raise R71bPresealVerificationError(f"sealed_target:{label}")
    return value
