"""Concrete target-bound NutriCoach v1.5 successor controller."""

from __future__ import annotations

from collections.abc import Callable
from functools import partial
from datetime import UTC, datetime
from pathlib import Path
from types import TracebackType
from typing import Literal, Protocol, Self, final

from pydantic import JsonValue, TypeAdapter

from scripts.nutricoach_v150_concrete_host import (
    ConcreteLiveHost,
)
from scripts.nutricoach_v150_live_models import (
    WEEKLY_AUTHORITY_EXPIRES_AT,
    WEEKLY_AUTHORITY_ISSUED_AT,
)
from scripts.nutricoach_v150_sealed_authority import (
    AuthorityError,
    GlobalLedger,
    capture,
    load_snapshot,
)
from scripts.nutricoach_v150_phase_journal import PhaseJournal
from scripts.nutricoach_v150_rollback_guard import RollbackGuard
from scripts.nutricoach_v150_sealed_target import DisposableService
from scripts.verify_nutricoach_v150_preseal_v15 import verify as verify_preseal

PACKAGE_DIGEST = "DISPOSABLE_V3"
APPROVAL_PHRASE = f"AUTHORIZE NUTRICOACH V1.5 LIVE UPGRADE {PACKAGE_DIGEST}"
V2_APPROVAL_PHRASE = (
    "AUTHORIZE NUTRICOACH V1.5 LIVE UPGRADE "
    "869c1e8f080ce79600ed7af58e0bd33db54cf316fa9aff4efe924fee15409fa5"
)
OLD_APPROVAL_PHRASE = (
    "AUTHORIZE NUTRICOACH V1.5 LIVE UPGRADE "
    "a362d994b41a5d05ed2fcdafc76482e72d18ff69334cd8249eec061bff733acb"
)
OLD_R71_APPROVAL_PHRASE = (
    "AUTHORIZE NUTRICOACH V1.5 LIVE UPGRADE "
    "4b032722f236dacc7f7e413cc37b31232000ce4f5d839bf0360fa8ac008ea6b1"
)
SEALED_TARGET = Path(
    "/home/cube/.hermes/migrations/nutricoach-v1.5.0-combined/"
    + "live-transaction-preseal-v15-runtime-authority-r71b-maintenance/sealed-target.json"
)
_OBJECT = TypeAdapter(dict[str, JsonValue])


class SealedControllerError(RuntimeError):
    """Fail-closed sealed controller error."""


@final
class _MaintenanceSwitchGuard:
    """Rollback an armed maintenance transaction if systemd never reaches start."""

    def __init__(self, maintenance: _MaintenanceLifecycle) -> None:
        self._maintenance = maintenance

    def __enter__(self) -> Self:
        return self

    def __exit__(
        self,
        error_type: type[BaseException] | None,
        error: BaseException | None,
        traceback: TracebackType | None,
    ) -> Literal[False]:
        del error_type, traceback
        if error is not None:
            self._maintenance.rollback()
        return False


class _MaintenanceLifecycle(Protocol):
    """The staged maintenance actions executed around the systemd switch."""

    def arm(self, approval: str) -> None: ...

    def rollback(self) -> None: ...

    def complete_start(self) -> None: ...


def _maintenance_predecessor_cron_fence(
    host: ConcreteLiveHost,
) -> Callable[[], None]:
    from scripts.nutricoach_v150_r71b_maintenance_transaction import pause_r70_scheduler

    return partial(pause_r70_scheduler, host.paths.profile / "cron/jobs.json")


def _execute(
    approval: str,
    host: ConcreteLiveHost,
    expected_approval: str = APPROVAL_PHRASE,
    maintenance_binding: dict[str, JsonValue] | None = None,
) -> str:
    if approval in {OLD_APPROVAL_PHRASE, OLD_R71_APPROVAL_PHRASE, V2_APPROVAL_PHRASE}:
        raise SealedControllerError("superseded_approval")
    if approval != expected_approval:
        raise SealedControllerError("wrong_approval")
    now = datetime.now(UTC)
    if not (
        datetime.fromisoformat(WEEKLY_AUTHORITY_ISSUED_AT)
        <= now
        < datetime.fromisoformat(WEEKLY_AUTHORITY_EXPIRES_AT)
    ):
        raise SealedControllerError("weekly_authority_window")
    host.capture_preflight()
    host.verify_preflight()
    host.semantic_clean_boundary()
    ledger = GlobalLedger(host.paths.ledger_root, host.candidate_digest)
    try:
        ledger.reserve()
    except AuthorityError as exc:
        raise SealedControllerError("authorization_already_used") from exc
    with ledger.attempt():
        host.write_recovery_manifest()
        journal = PhaseJournal(host.paths.execution_root / "phase.json")
        journal.advance("PREPARING")
        journal.advance("RESERVED")
        fence = (
            _maintenance_predecessor_cron_fence(host)
            if maintenance_binding is not None
            else None
        )
        with RollbackGuard(host, journal, SealedControllerError, fence) as guard:
            journal.advance("STOPPING")
            host.stop()
            snapshot = capture(host.snapshot_paths(), host.paths.execution_root)
            guard.bind(snapshot)
            journal.advance("SNAPSHOT")
            host.record_snapshot()
            host.stopped_probe()
            host.semantic_clean_boundary()
            host.install()
            journal.advance("INSTALLED")
            host.off_smoke()
            host.migration_dry_run()
            host.migration_apply()
            journal.advance("MIGRATED")
            host.weekly_startup_smoke()
            host.promote_runtime_authority()
            maintenance: _MaintenanceLifecycle | None
            if maintenance_binding is None:
                maintenance = None
            else:
                from scripts.nutricoach_v150_r71b_sealed_host import (
                    build_r71b_maintenance_controller,
                )

                maintenance = build_r71b_maintenance_controller(
                    host, maintenance_binding, journal
                )
            if maintenance is None:
                host.switch_systemd()
                host.reload()
                host.start()
                journal.advance("STARTED")
            else:
                maintenance.arm(approval)
                with _MaintenanceSwitchGuard(maintenance):
                    host.switch_systemd()
                    host.reload()
                maintenance.complete_start()
            host.post_fence()
            if host.network_events or host.telegram_events or host.provider_events:
                raise SealedControllerError("privacy_postcondition")
            journal.advance("COMMITTING")
            ledger.consume("SUCCEEDED")
            guard.commit()
            journal.advance("COMMITTED")
    return snapshot.manifest_digest


def recover_pending(
    host: ConcreteLiveHost,
    maintenance_binding: dict[str, JsonValue] | None = None,
) -> None:
    journal = PhaseJournal(host.paths.execution_root / "phase.json")
    ledger = GlobalLedger(host.paths.ledger_root, host.candidate_digest)
    if journal.phase() is None:
        if not ledger.reservation_pending():
            return
        ledger.consume("FAILED")
        journal.advance("ROLLED_BACK")
        interrupted = SealedControllerError("interrupted_transaction")
        raise SealedControllerError("rollback_recovery_completed") from interrupted
    phase = journal.phase()
    if not journal.recovery_required():
        return
    if phase == "MAINTENANCE_CONFIRMED":
        if maintenance_binding is None:
            raise SealedControllerError("maintenance_recovery_binding")
        from scripts.nutricoach_v150_r71b_maintenance_transaction import MaintenancePhase
        from scripts.nutricoach_v150_r71b_sealed_host import (
            build_r71b_maintenance_controller,
        )

        host.load_successor_postimages()
        maintenance = build_r71b_maintenance_controller(
            host,
            maintenance_binding,
            journal,
            require_r70_proof=False,
        )
        maintenance.recover(MaintenancePhase.MAINTENANCE_CONFIRMED)
        host.post_fence()
        if host.network_events or host.telegram_events or host.provider_events:
            raise SealedControllerError("privacy_postcondition")
        journal.advance("COMMITTING")
        ledger.consume("SUCCEEDED")
        journal.advance("COMMITTED")
        return
    if phase == "COMMITTING" and ledger.outcome() == "SUCCEEDED":
        journal.advance("COMMITTED")
        return
    fence = (
        _maintenance_predecessor_cron_fence(host)
        if maintenance_binding is not None
        else None
    )
    host.load_recovery_manifest()
    if maintenance_binding is not None and phase in {
        "MAINTENANCE_ARMED",
        "START_WATCH_ARMED",
        "STARTED",
    }:
        # Unconfirmed maintenance must still be tied to the exact r70 incident
        # before the outer guard restores and pauses the predecessor.
        from scripts.nutricoach_v150_r71b_sealed_host import (
            build_r71b_maintenance_controller,
        )

        _ = build_r71b_maintenance_controller(host, maintenance_binding, journal)
    guard = RollbackGuard(host, journal, SealedControllerError, fence)
    if journal.phase() not in {"PREPARING", "STOPPING", "RESERVED"}:
        guard.bind(load_snapshot(host.paths.execution_root))
    interrupted = SealedControllerError("interrupted_transaction")
    guard.rollback(interrupted)
    ledger.consume("FAILED")
    raise SealedControllerError("rollback_recovery_completed") from interrupted


def rollback_committed(host: ConcreteLiveHost, reason: str) -> None:
    """Run the sealed postcommit rollback after a failed Manual-QA fence."""
    if not reason.strip():
        raise SealedControllerError("postcommit_rollback_reason")
    journal = PhaseJournal(host.paths.execution_root / "phase.json")
    ledger = GlobalLedger(host.paths.ledger_root, host.candidate_digest)
    if journal.phase() != "COMMITTED" or ledger.outcome() != "SUCCEEDED":
        raise SealedControllerError("postcommit_rollback_state")
    host.load_recovery_manifest()
    guard = RollbackGuard(host, journal, SealedControllerError)
    guard.bind(load_snapshot(host.paths.execution_root))
    failure = SealedControllerError(f"postcommit_manual_qa:{reason.strip()}")
    guard.rollback(failure)
    if journal.phase() != "ROLLED_BACK":
        raise SealedControllerError("postcommit_rollback_incomplete") from failure


def execute_authorized(approval: str) -> str:
    """Execute only the compile-time V15 runtime-authority target."""
    if approval in {OLD_APPROVAL_PHRASE, OLD_R71_APPROVAL_PHRASE, V2_APPROVAL_PHRASE}:
        raise SealedControllerError("superseded_approval")
    binding = _OBJECT.validate_json(SEALED_TARGET.read_bytes())
    expected = binding.get("approval_phrase")
    package_path = binding.get("permission_package")
    derivation = binding.get("controller_derivation_sha256")
    if (
        not isinstance(expected, str)
        or not isinstance(package_path, str)
        or not isinstance(derivation, str)
    ):
        raise SealedControllerError("sealed_target")
    if approval != expected:
        raise SealedControllerError("wrong_approval")
    host = ConcreteLiveHost.live_target(SEALED_TARGET)
    verified = verify_preseal()
    if verified["approval_phrase"] != approval or verified[
        "package_digest"
    ] != binding.get("package_digest"):
        raise SealedControllerError("preseal_binding")
    recover_pending(host, binding)
    return _execute(approval, host, expected, binding)


def execute_disposable(
    approval: str,
    root: Path,
    host: ConcreteLiveHost,
    *,
    expected_approval: str = APPROVAL_PHRASE,
    maintenance_binding: dict[str, JsonValue] | None = None,
) -> str:
    """Exercise identical semantics against an isolated target."""
    if host.paths.profile != root / "profile":
        raise SealedControllerError("disposable_binding")
    return _execute(approval, host, expected_approval, maintenance_binding)


__all__ = [
    "APPROVAL_PHRASE",
    "OLD_APPROVAL_PHRASE",
    "OLD_R71_APPROVAL_PHRASE",
    "V2_APPROVAL_PHRASE",
    "ConcreteLiveHost",
    "DisposableService",
    "SealedControllerError",
    "execute_authorized",
    "execute_disposable",
    "rollback_committed",
]
