"""Post-start r71b maintenance confirmation checks."""

from __future__ import annotations

import hashlib
import stat
from dataclasses import dataclass
from pathlib import Path
from typing import Protocol

from pydantic import JsonValue, TypeAdapter, ValidationError

from gateway.platforms.nutrition_weekly_maintenance_contract import (
    Topic59MaintenanceAuthorityV1,
    Topic59MaintenanceContractError,
    Topic59MaintenanceSkipAuditV1,
    canonical_document,
)
from scripts.nutricoach_v150_r71b_cron_watch import CronJobsEvent
from scripts.nutricoach_v150_r71b_maintenance_transaction import (
    KNOWN_R70_CRON_JOB_ID,
    KNOWN_R70_CRON_JOB_NAME,
    KNOWN_R70_CRON_SCHEDULE,
)

_OBJECT = TypeAdapter(dict[str, JsonValue])


class MaintenanceConfirmationError(RuntimeError):
    """The watched r71b maintenance completion does not meet its sealed proof."""


@dataclass(frozen=True, slots=True)
class MaintenanceConfirmation:
    """Exact post-start evidence required before COMMITTED is reachable."""

    ledger_path: Path
    ledger_sha256: str
    output_path: Path
    provider_calls: int
    network_calls: int


@dataclass(frozen=True, slots=True)
class MaintenanceConfirmationPaths:
    """The exact files a one-shot completion is permitted to inspect."""

    cron_jobs: Path
    hold: Path
    consuming: Path
    audit: Path


class ArmedCronWatcher(Protocol):
    """A subscription armed before service start and awaited once afterward."""

    def wait(self, *, timeout_seconds: int) -> CronJobsEvent: ...


def verify_maintenance_confirmation(
    paths: MaintenanceConfirmationPaths,
    authority: Topic59MaintenanceAuthorityV1,
    watcher: ArmedCronWatcher,
    evidence: MaintenanceConfirmation,
) -> None:
    """Require watched cron success, typed output, authenticated audit, and zero calls."""
    event = watcher.wait(timeout_seconds=120)
    if event.name != "jobs.json" or not event.replacement:
        raise MaintenanceConfirmationError("cron_event")
    _verify_postconditions(paths, authority, evidence)


def verify_recovered_maintenance_confirmation(
    paths: MaintenanceConfirmationPaths,
    authority: Topic59MaintenanceAuthorityV1,
    evidence: MaintenanceConfirmation,
) -> None:
    """Reverify a durably recorded completion without fabricating a past watch event."""
    _verify_postconditions(paths, authority, evidence)


def _verify_postconditions(
    paths: MaintenanceConfirmationPaths,
    authority: Topic59MaintenanceAuthorityV1,
    evidence: MaintenanceConfirmation,
) -> None:
    _require_completed_r70_job(_load_jobs(paths.cron_jobs))
    _require_confirmation_output(evidence.output_path)
    if (
        hashlib.sha256(evidence.ledger_path.read_bytes()).hexdigest()
        != evidence.ledger_sha256
        or evidence.provider_calls != 0
        or evidence.network_calls != 0
        or paths.hold.exists()
        or paths.consuming.exists()
    ):
        raise MaintenanceConfirmationError("maintenance_postcondition")
    _require_audit(paths.audit, authority)


def _load_jobs(path: Path) -> list[dict[str, JsonValue]]:
    try:
        document = _OBJECT.validate_json(path.read_bytes())
    except (OSError, ValidationError) as error:
        raise MaintenanceConfirmationError("cron_json") from error
    rows = document.get("jobs")
    if not isinstance(rows, list):
        raise MaintenanceConfirmationError("cron_json")
    typed_rows: list[dict[str, JsonValue]] = []
    for row in rows:
        if not isinstance(row, dict):
            raise MaintenanceConfirmationError("cron_json")
        typed_rows.append(row)
    return typed_rows


def snapshot_outputs(directory: Path) -> dict[Path, str]:
    """Capture every trusted output digest before the successor's first cron run."""
    if not directory.exists():
        return {}
    if directory.is_symlink() or not directory.is_dir():
        raise MaintenanceConfirmationError("cron_output_directory")
    result: dict[Path, str] = {}
    for path in directory.iterdir():
        info = path.stat(follow_symlinks=False)
        if path.is_symlink() or not stat.S_ISREG(info.st_mode) or info.st_nlink != 1:
            raise MaintenanceConfirmationError("cron_output_directory")
        result[path] = hashlib.sha256(path.read_bytes()).hexdigest()
    return result


def newly_written_output(directory: Path, before: dict[Path, str] | None) -> Path:
    """Return the one output created or changed before the observed jobs.json update."""
    if before is None:
        raise MaintenanceConfirmationError("cron_output_snapshot")
    after = snapshot_outputs(directory)
    changed = [path for path, digest in after.items() if before.get(path) != digest]
    if len(changed) != 1:
        raise MaintenanceConfirmationError("cron_output")
    return changed[0]


def _require_completed_r70_job(jobs: list[dict[str, JsonValue]]) -> None:
    matches = [job for job in jobs if job.get("id") == KNOWN_R70_CRON_JOB_ID]
    if len(matches) != 1:
        raise MaintenanceConfirmationError("cron_completion")
    job = matches[0]
    if (
        job.get("name") != KNOWN_R70_CRON_JOB_NAME
        or job.get("schedule_display") != KNOWN_R70_CRON_SCHEDULE
        or job.get("inline_card") != "nutrition-coaching-tick"
        or job.get("deliver") != "local"
        or job.get("state") != "scheduled"
        or job.get("enabled") is not True
        or job.get("last_status") != "ok"
        or job.get("last_error") is not None
        or not isinstance(job.get("last_run_at"), str)
    ):
        raise MaintenanceConfirmationError("cron_completion")


def _require_confirmation_output(path: Path) -> None:
    info = path.stat(follow_symlinks=False)
    if (
        path.is_symlink()
        or not stat.S_ISREG(info.st_mode)
        or b"Status: maintenance no-op completed\n" not in path.read_bytes()
    ):
        raise MaintenanceConfirmationError("cron_output")


def _require_audit(
    path: Path,
    authority: Topic59MaintenanceAuthorityV1,
) -> None:
    try:
        payload = path.read_bytes()
        audit = Topic59MaintenanceSkipAuditV1.model_validate_json(payload)
    except (OSError, ValidationError, Topic59MaintenanceContractError) as error:
        raise MaintenanceConfirmationError("maintenance_audit") from error
    hold = authority.hold
    if (
        canonical_document(audit) != payload
        or audit.hold_id != hold.hold_id
        or audit.hold_sha256 != authority.hold_sha256
        or audit.package_binding_digest != authority.package_binding_digest
        or audit.candidate_digest != hold.candidate_digest
        or audit.config_digest != hold.config_digest
        or audit.route_digest != hold.route_digest
        or audit.customer_identity_digest != hold.customer_identity_digest
        or audit.card_slot != hold.card_slot
        or audit.kst_day != hold.kst_day
    ):
        raise MaintenanceConfirmationError("maintenance_audit")
