"""Resumable source-to-target migration.

The active provider passed in is returned unchanged until production verification
succeeds. This engine does not read or write customer projects. The Pages to W4P
cohort keeps its own task.
"""
from __future__ import annotations

from dataclasses import dataclass
from typing import Any
from uuid import uuid4

from src.domain.hosting.execution import get_execution_adapter
from src.domain.hosting.migration_state import migration_evidence
from src.domain.hosting.profile import (
    ApplicationHostingRequirements,
    HostingProfileError,
    plan_hosting_migration,
)
from src.domain.hosting.routing_ops import get_routing_adapter

_AFTER_CUTOVER = frozenset({"cutover", "verify_production"})


@dataclass(frozen=True)
class MigrationRun:
    active_provider: str
    state: dict[str, Any]
    committed: bool
    rolled_back: bool


def _append(state: dict[str, Any], stage: str, *, ok: bool, detail: str) -> None:
    state["stage"] = stage
    evidence = list(state.get("evidence") or [])
    evidence.append(migration_evidence(stage, ok=ok, detail=detail))
    state["evidence"] = evidence[-50:]


def execute_hosting_migration(
    *,
    source: str,
    target: str,
    requirements: ApplicationHostingRequirements,
    active_provider: str,
    context: dict[str, Any] | None = None,
) -> MigrationRun:
    """Run one migration against registered adapters.

    Failure before cutover leaves ``active_provider`` as it was. Failure after
    the routing adapter activates attempts rollback and still does not commit
    the destination as the active provider.
    """

    ctx = dict(context or {})
    plan = plan_hosting_migration(source, target, requirements)
    state: dict[str, Any] = {
        "operation_id": str(uuid4()),
        "engine": "generic",
        "source": plan.active_provider,
        "target": plan.migration_target,
        "stage": "discover",
        "status": "in_progress",
        "cutover_verified": False,
        "allow_destination_deploy": False,
        "rollback": {"provider": active_provider},
        "evidence": [],
    }
    if plan.compatibility.status == "blocked":
        state["status"] = "failed"
        _append(state, "preflight", ok=False, detail=",".join(plan.compatibility.reasons))
        return MigrationRun(active_provider, state, committed=False, rolled_back=False)

    _append(state, "discover", ok=True, detail="profiles_resolved")
    _append(state, "preflight", ok=True, detail=plan.compatibility.status)
    _append(state, "snapshot", ok=True, detail="rollback_reference_retained")

    execution = get_execution_adapter(plan.migration_target)
    routing = get_routing_adapter(plan.migration_target)
    current = "prepare"
    try:
        current = "prepare"
        prepared = execution.prepare(ctx)
        _append(state, current, ok=True, detail=str(prepared.get("detail") or "prepared"))
        current = "build"
        built = execution.plan_build(requirements.framework)
        _append(state, current, ok=True, detail=str(built.get("server_function_surface") or ""))
        current = "deploy_destination"
        deployed = execution.deploy(ctx)
        if not deployed.get("ok"):
            raise HostingProfileError(str(deployed.get("detail") or "deploy_failed"))
        state["allow_destination_deploy"] = True
        _append(state, current, ok=True, detail="destination_deployed")
        current = "verify_destination"
        ready = execution.readiness(ctx)
        if not ready.get("ok"):
            raise HostingProfileError(str(ready.get("detail") or "destination_not_ready"))
        _append(state, current, ok=True, detail="destination_ready")
        current = "prepare_routing"
        route_plan = routing.prepare(ctx)
        strategy = str(route_plan.get("routing_strategy") or "routing_prepared")
        _append(state, current, ok=True, detail=strategy)
        current = "cutover"
        routing.activate(ctx)
        _append(state, current, ok=True, detail="traffic_moved")
        current = "verify_production"
        production = execution.readiness(ctx)
        if not production.get("ok"):
            raise HostingProfileError(str(production.get("detail") or "production_unverified"))
        state["cutover_verified"] = True
        _append(state, current, ok=True, detail="production_verified")
        if not state["cutover_verified"]:
            raise HostingProfileError("refusing_to_commit_unverified_cutover")
        current = "commit_active_provider"
        state["committed_provider"] = plan.migration_target
        _append(state, current, ok=True, detail="active_provider_committed")
        state["status"] = "completed"
        state["stage"] = "complete"
        _append(state, "complete", ok=True, detail="complete")
        return MigrationRun(
            plan.migration_target,
            state,
            committed=True,
            rolled_back=False,
        )
    except Exception as exc:
        rolled_back = False
        if current in _AFTER_CUTOVER:
            try:
                routing.rollback(ctx)
                execution.rollback(ctx)
                rolled_back = True
                state["stage"] = "rolled_back"
            except Exception as rollback_exc:
                state["rollback_error"] = str(rollback_exc)[:500]
        state["status"] = "failed"
        state["allow_destination_deploy"] = False
        state["cutover_verified"] = False
        _append(state, current, ok=False, detail=str(exc)[:500])
        return MigrationRun(
            active_provider,
            state,
            committed=False,
            rolled_back=rolled_back,
        )
