"""Harbor registry controlled recovery preflight.

This readback packages the current CD-blocking Harbor /v2/ failure into a
controlled recovery candidate. It probes only public/internal HTTP routes and
repo source files; it does not SSH, read secrets, run Docker, restart services,
trigger workflows, or mutate runtime state.
"""

from __future__ import annotations

import re
import urllib.error
import urllib.request
from collections.abc import Callable
from pathlib import Path
from typing import Any

from src.services.snapshot_paths import resolve_repo_root

_SCHEMA_VERSION = "harbor_registry_controlled_recovery_preflight_v1"
_REPO_ROOT = resolve_repo_root(Path(__file__))
_DEFAULT_ROUTE_SOURCE = (
    _REPO_ROOT
    / "infra"
    / "ansible"
    / "roles"
    / "nginx"
    / "templates"
    / "188-internal-tools-https.conf.j2"
)
_DEFAULT_WATCHDOG_SOURCE = (
    _REPO_ROOT
    / "scripts"
    / "reboot-recovery"
    / "harbor-watchdog.sh"
)
_DEFAULT_TIMEOUT_SECONDS = 4.0

Probe = Callable[[str, float], dict[str, Any]]


def load_latest_harbor_registry_controlled_recovery_preflight(
    *,
    timeout_seconds: float = _DEFAULT_TIMEOUT_SECONDS,
    route_source_path: Path | None = None,
    watchdog_source_path: Path | None = None,
    probe: Probe | None = None,
) -> dict[str, Any]:
    """Build the Harbor registry controlled recovery preflight."""
    http_probe = probe or _http_probe
    probes = {
        "public_registry_v2": _probe_endpoint(
            http_probe,
            "https://registry.wooo.work/v2/",
            timeout_seconds,
        ),
        "public_harbor_ui": _probe_endpoint(
            http_probe,
            "https://harbor.wooo.work/",
            timeout_seconds,
        ),
        "public_harbor_api_health": _probe_endpoint(
            http_probe,
            "https://harbor.wooo.work/api/v2.0/health",
            timeout_seconds,
        ),
        "internal_110_registry_v2": _probe_endpoint(
            http_probe,
            "http://192.168.0.110:5000/v2/",
            timeout_seconds,
        ),
        "internal_110_harbor_ui": _probe_endpoint(
            http_probe,
            "http://192.168.0.110:5000/",
            timeout_seconds,
        ),
    }
    route_contract = _load_route_contract(route_source_path or _DEFAULT_ROUTE_SOURCE)
    watchdog_contract = _load_watchdog_contract(
        watchdog_source_path or _DEFAULT_WATCHDOG_SOURCE
    )
    return _build_payload(
        timeout_seconds=timeout_seconds,
        probes=probes,
        route_contract=route_contract,
        watchdog_contract=watchdog_contract,
    )


def _build_payload(
    *,
    timeout_seconds: float,
    probes: dict[str, dict[str, Any]],
    route_contract: dict[str, Any],
    watchdog_contract: dict[str, Any],
) -> dict[str, Any]:
    public_v2 = _dict(probes.get("public_registry_v2"))
    public_ui = _dict(probes.get("public_harbor_ui"))
    public_health = _dict(probes.get("public_harbor_api_health"))
    internal_v2 = _dict(probes.get("internal_110_registry_v2"))
    internal_ui = _dict(probes.get("internal_110_harbor_ui"))

    public_v2_ready = _registry_ready(public_v2.get("http_status"))
    internal_v2_ready = _registry_ready(internal_v2.get("http_status"))
    public_ui_ready = public_ui.get("http_status") == 200
    internal_ui_ready = internal_ui.get("http_status") == 200
    route_target_ready = route_contract.get("route_target_ready") is True
    watchdog_executor_ready = watchdog_contract.get("executor_ready") is True
    upstream_502_shape = (
        public_v2.get("http_status") == 502
        and internal_v2.get("http_status") == 502
        and (public_ui_ready or internal_ui_ready)
    )
    gateway_drift_shape = (
        public_v2.get("http_status") == 502
        and internal_v2_ready
        and route_target_ready
    )
    registry_ready = public_v2_ready and internal_v2_ready
    candidate_packaged = (
        not registry_ready
        and route_target_ready
        and watchdog_executor_ready
        and (upstream_502_shape or gateway_drift_shape)
    )
    status = _status(
        registry_ready=registry_ready,
        candidate_packaged=candidate_packaged,
        upstream_502_shape=upstream_502_shape,
        gateway_drift_shape=gateway_drift_shape,
    )
    active_blockers = _active_blockers(
        registry_ready=registry_ready,
        route_target_ready=route_target_ready,
        watchdog_executor_ready=watchdog_executor_ready,
        upstream_502_shape=upstream_502_shape,
        gateway_drift_shape=gateway_drift_shape,
        public_v2_status=public_v2.get("http_status"),
        internal_v2_status=internal_v2.get("http_status"),
    )

    return {
        "schema_version": _SCHEMA_VERSION,
        "priority": "P0-006",
        "scope": "harbor_registry_controlled_recovery_preflight",
        "status": status,
        "safe_next_step": _safe_next_step(
            registry_ready=registry_ready,
            upstream_502_shape=upstream_502_shape,
            gateway_drift_shape=gateway_drift_shape,
            candidate_packaged=candidate_packaged,
        ),
        "active_blockers": active_blockers,
        "active_blocker_count": len(active_blockers),
        "timeout_seconds": timeout_seconds,
        "target_selector": {
            "component_id": "harbor_registry",
            "public_registry_route": "https://registry.wooo.work/v2/",
            "public_harbor_ui_route": "https://harbor.wooo.work/",
            "internal_registry_route": "http://192.168.0.110:5000/v2/",
            "internal_harbor_ui_route": "http://192.168.0.110:5000/",
            "source_route_file": str(route_contract.get("route_source_file") or ""),
            "route_targets": _strings(route_contract.get("route_targets")),
            "watchdog_source_file": str(
                watchdog_contract.get("watchdog_source_file") or ""
            ),
            "watchdog_check_command": "harbor-watchdog.sh --check",
            "watchdog_repair_command": "harbor-watchdog.sh --repair-once",
            "receipt_validator_endpoint": (
                "/api/v1/agents/harbor-registry-controlled-recovery-receipt"
            ),
        },
        "control_channel_contract": _control_channel_contract(
            registry_ready=registry_ready,
            candidate_packaged=candidate_packaged,
            upstream_502_shape=upstream_502_shape,
        ),
        "source_of_truth_diff": {
            "status": _source_diff_status(
                registry_ready=registry_ready,
                route_target_ready=route_target_ready,
                upstream_502_shape=upstream_502_shape,
                gateway_drift_shape=gateway_drift_shape,
            ),
            "public_registry_v2_http_status": public_v2.get("http_status"),
            "public_harbor_ui_http_status": public_ui.get("http_status"),
            "public_harbor_api_health_http_status": public_health.get("http_status"),
            "internal_110_registry_v2_http_status": internal_v2.get("http_status"),
            "internal_110_harbor_ui_http_status": internal_ui.get("http_status"),
            "route_source_file": str(route_contract.get("route_source_file") or ""),
            "route_targets": _strings(route_contract.get("route_targets")),
            "watchdog_executor_ready": watchdog_executor_ready,
        },
        "controlled_apply_policy": {
            "risk_level": "high",
            "break_glass_required": False,
            "runtime_write_gate": "controlled_after_preflight",
            "controlled_recovery_candidate_packaged": candidate_packaged,
            "current_apply_allowed": False,
            "current_apply_blocker": _current_apply_blocker(
                registry_ready=registry_ready,
                candidate_packaged=candidate_packaged,
                upstream_502_shape=upstream_502_shape,
                gateway_drift_shape=gateway_drift_shape,
            ),
            "manual_end_state": False,
            "allowed_scope": [
                "public_registry_v2_reprobe",
                "read_only_110_local_v2_status",
                "harbor_watchdog_check_mode",
                "harbor_watchdog_repair_once_after_status_only_failure",
                "post_apply_public_registry_v2_verifier",
                "km_rag_mcp_playbook_metadata_writeback",
            ],
            "forbidden_scope": [
                "docker_daemon_restart",
                "host_reboot",
                "node_drain",
                "nginx_reload_or_restart",
                "database_restore_or_prune",
                "secret_value_read",
                "workflow_dispatch",
                "force_push_or_repo_ref_mutation",
                "switch_registry_provider_or_paid_route",
            ],
        },
        "controlled_work_items": _controlled_work_items(
            candidate_packaged=candidate_packaged,
            upstream_502_shape=upstream_502_shape,
            gateway_drift_shape=gateway_drift_shape,
        ),
        "controlled_action_candidates": _controlled_action_candidates(
            candidate_packaged=candidate_packaged,
            upstream_502_shape=upstream_502_shape,
            gateway_drift_shape=gateway_drift_shape,
        ),
        "post_apply_verifiers": _post_apply_verifiers(),
        "learning_writeback_contracts": _learning_writeback_contracts(),
        "rollups": {
            "registry_ready": registry_ready,
            "public_registry_v2_ready": public_v2_ready,
            "internal_110_registry_v2_ready": internal_v2_ready,
            "public_harbor_ui_ready": public_ui_ready,
            "internal_110_harbor_ui_ready": internal_ui_ready,
            "route_target_ready": route_target_ready,
            "watchdog_executor_ready": watchdog_executor_ready,
            "harbor_registry_upstream_502_shape": upstream_502_shape,
            "public_gateway_route_drift_shape": gateway_drift_shape,
            "control_channel_readback_required": (
                not registry_ready and upstream_502_shape
            ),
            "awoooi_host_runner_required": (
                not registry_ready and upstream_502_shape
            ),
            "ssh_publickey_diagnosis_required": (
                not registry_ready and upstream_502_shape
            ),
            "controlled_recovery_candidate_packaged": candidate_packaged,
            "controlled_work_item_count": len(
                _controlled_work_items(
                    candidate_packaged=candidate_packaged,
                    upstream_502_shape=upstream_502_shape,
                    gateway_drift_shape=gateway_drift_shape,
                )
            ),
            "post_apply_verifier_count": len(_post_apply_verifiers()),
            "learning_writeback_contract_count": len(_learning_writeback_contracts()),
        },
        "probes": probes,
        "operation_boundaries": {
            "read_only_public_https_probe": True,
            "read_only_internal_http_probe": True,
            "source_route_file_read": True,
            "watchdog_source_file_read": True,
            "ssh_used": False,
            "docker_command_performed": False,
            "docker_restart_performed": False,
            "docker_daemon_restart_performed": False,
            "host_reboot_performed": False,
            "node_drain_performed": False,
            "nginx_reload_or_restart_performed": False,
            "database_write_or_restore_performed": False,
            "secret_value_collection_allowed": False,
            "github_api_used": False,
            "workflow_trigger_performed": False,
            "runtime_write_allowed_by_this_readback": False,
        },
    }


def _load_route_contract(path: Path) -> dict[str, Any]:
    payload: dict[str, Any] = {
        "route_source_file": str(path),
        "route_targets": [],
        "route_target_ready": False,
        "route_source_present": path.exists(),
    }
    if not path.exists():
        return payload
    text = path.read_text(encoding="utf-8")
    section = _registry_section(text)
    targets = re.findall(r"proxy_pass\s+(http://[^;]+);", section)
    unique_targets = _unique_strings(targets)
    payload["route_targets"] = unique_targets
    payload["route_target_ready"] = (
        "server_name registry.wooo.work;" in section
        and "server_name harbor.wooo.work;" in text
        and bool(unique_targets)
    )
    return payload


def _registry_section(text: str) -> str:
    marker = "server_name registry.wooo.work;"
    start = text.find(marker)
    if start == -1:
        return text
    block_start = text.rfind("server {", 0, start)
    block_end = text.find("\n}", start)
    if block_start == -1 or block_end == -1:
        return text[start:]
    return text[block_start : block_end + 2]


def _load_watchdog_contract(path: Path) -> dict[str, Any]:
    payload: dict[str, Any] = {
        "watchdog_source_file": str(path),
        "watchdog_source_present": path.exists(),
        "executor_ready": False,
    }
    if not path.exists():
        return payload
    text = path.read_text(encoding="utf-8")
    payload["has_check_mode"] = "--check" in text
    payload["has_repair_once"] = "--repair-once" in text
    payload["has_v2_health_check"] = "http://127.0.0.1:5000/v2/" in text
    payload["avoids_docker_daemon_restart"] = "systemctl restart docker" not in text
    payload["avoids_host_reboot"] = not re.search(
        r"(^|[;&|[:space:]])reboot($|[;&|[:space:]])",
        text,
    )
    payload["executor_ready"] = all(
        bool(payload[key])
        for key in (
            "has_check_mode",
            "has_repair_once",
            "has_v2_health_check",
            "avoids_docker_daemon_restart",
            "avoids_host_reboot",
        )
    )
    return payload


def _status(
    *,
    registry_ready: bool,
    candidate_packaged: bool,
    upstream_502_shape: bool,
    gateway_drift_shape: bool,
) -> str:
    if registry_ready:
        return "harbor_registry_ready"
    if candidate_packaged and upstream_502_shape:
        return "controlled_recovery_preflight_packaged_harbor_registry_upstream_502"
    if candidate_packaged and gateway_drift_shape:
        return "controlled_recovery_preflight_packaged_public_gateway_route_drift"
    return "blocked_harbor_registry_recovery_evidence_incomplete"


def _safe_next_step(
    *,
    registry_ready: bool,
    upstream_502_shape: bool,
    gateway_drift_shape: bool,
    candidate_packaged: bool,
) -> str:
    if registry_ready:
        return "rerun_gitea_cd_and_verify_deploy_marker"
    if candidate_packaged and upstream_502_shape:
        return (
            "restore_110_ssh_or_awoooi_host_runner_control_channel_then_run_"
            "harbor_watchdog_check_and_repair_once_with_public_registry_v2_verifier"
        )
    if candidate_packaged and gateway_drift_shape:
        return (
            "prepare_188_registry_nginx_rendered_diff_check_mode_then_route_"
            "smoke_without_provider_switch"
        )
    return "rerun_public_and_internal_registry_v2_readback_before_apply"


def _active_blockers(
    *,
    registry_ready: bool,
    route_target_ready: bool,
    watchdog_executor_ready: bool,
    upstream_502_shape: bool,
    gateway_drift_shape: bool,
    public_v2_status: Any,
    internal_v2_status: Any,
) -> list[str]:
    if registry_ready:
        return []
    blockers = [
        f"public_registry_v2_http_{public_v2_status}",
        f"internal_110_registry_v2_http_{internal_v2_status}",
    ]
    if not route_target_ready:
        blockers.append("registry_public_gateway_route_source_target_missing")
    if not watchdog_executor_ready:
        blockers.append("harbor_watchdog_single_run_executor_contract_missing")
    if upstream_502_shape:
        blockers.append("harbor_registry_upstream_502")
        blockers.append("awoooi_host_runner_or_ssh_control_channel_unavailable")
        blockers.append("ssh_publickey_diagnosis_required_before_repair")
    if gateway_drift_shape:
        blockers.append("registry_public_gateway_route_drift")
    blockers.append("harbor_registry_control_channel_readback_required")
    return _unique_strings([str(item) for item in blockers])


def _source_diff_status(
    *,
    registry_ready: bool,
    route_target_ready: bool,
    upstream_502_shape: bool,
    gateway_drift_shape: bool,
) -> str:
    if registry_ready:
        return "no_recovery_diff_required"
    if not route_target_ready:
        return "route_source_target_missing"
    if upstream_502_shape:
        return "route_source_present_110_harbor_registry_upstream_502"
    if gateway_drift_shape:
        return "route_source_present_public_gateway_route_drift"
    return "route_source_present_reprobe_required"


def _current_apply_blocker(
    *,
    registry_ready: bool,
    candidate_packaged: bool,
    upstream_502_shape: bool,
    gateway_drift_shape: bool,
) -> str:
    if registry_ready:
        return ""
    if candidate_packaged and upstream_502_shape:
        return (
            "110_ssh_or_awoooi_host_runner_control_channel_readback_required_"
            "before_harbor_watchdog_repair_once"
        )
    if candidate_packaged and gateway_drift_shape:
        return "188_nginx_rendered_diff_and_nginx_t_required_before_route_apply"
    return "registry_v2_shape_and_executor_contract_required_before_apply"


def _controlled_work_items(
    *,
    candidate_packaged: bool,
    upstream_502_shape: bool,
    gateway_drift_shape: bool,
) -> list[dict[str, Any]]:
    base_status = (
        "queued_waiting_control_channel_readback"
        if candidate_packaged
        else "blocked_waiting_registry_v2_evidence"
    )
    return [
        {
            "id": "110-control-channel-readback",
            "title": "110 SSH / awoooi-host runner control-channel readback",
            "owner_agent": "DevOps Agent",
            "status": (
                "blocked_waiting_ssh_publickey_or_awoooi_host_runner"
                if upstream_502_shape
                else "not_required_for_current_shape"
            ),
            "required_output": (
                "diagnose-110-ssh-publickey-auth plus public Gitea "
                "awoooi-host runner queue readback without token collection"
            ),
        },
        {
            "id": "harbor-registry-v2-readback",
            "title": "Registry /v2 public and internal readback",
            "owner_agent": "SRE Agent",
            "status": "done" if candidate_packaged else "needs_reprobe",
            "required_output": "public_and_internal_registry_v2_status_matrix",
        },
        {
            "id": "harbor-watchdog-check",
            "title": "Harbor watchdog check-mode control readback",
            "owner_agent": "DevOps Agent",
            "status": base_status,
            "required_output": "AWOOOI_HARBOR_WATCHDOG_CHECK_without_secret_or_docker_daemon_restart",
        },
        {
            "id": "harbor-watchdog-repair-once",
            "title": "Harbor watchdog bounded repair-once",
            "owner_agent": "DevOps Agent",
            "status": (
                "waiting_check_mode_failure"
                if upstream_502_shape
                else "not_selected_for_current_shape"
            ),
            "required_output": "HARBOR_REPAIR_PERFORMED_receipt_and_registry_v2_verifier",
        },
        {
            "id": "registry-public-gateway-route-diff",
            "title": "188 registry public gateway rendered diff",
            "owner_agent": "Gateway Agent",
            "status": (
                "waiting_rendered_diff_check_mode"
                if gateway_drift_shape
                else "not_selected_for_current_shape"
            ),
            "required_output": "nginx_t_and_route_smoke_evidence_no_provider_switch",
        },
        {
            "id": "harbor-registry-learning-writeback",
            "title": "KM / RAG / MCP / PlayBook trust writeback",
            "owner_agent": "Learning Agent",
            "status": "ready",
            "required_output": "metadata_only_registry_recovery_receipt_and_trust_delta",
        },
        {
            "id": "harbor-registry-recovery-receipt-validator",
            "title": "Controlled recovery receipt validator",
            "owner_agent": "Verifier Agent",
            "status": "ready",
            "required_output": "harbor_registry_controlled_recovery_receipt_v1",
        },
    ]


def _controlled_action_candidates(
    *,
    candidate_packaged: bool,
    upstream_502_shape: bool,
    gateway_drift_shape: bool,
) -> list[dict[str, Any]]:
    candidates: list[dict[str, Any]] = [
        {
            "id": "harbor_watchdog_check",
            "execution_route": "host_110_harbor_watchdog_executor",
            "current_status": (
                "blocked_waiting_110_ssh_or_awoooi_host_runner_control_channel"
                if candidate_packaged
                else "blocked_waiting_registry_v2_evidence"
            ),
            "check_mode": "harbor_watchdog_check_registry_v2_probe",
            "forbidden_actions": [
                "secret_read",
                "docker_daemon_restart",
                "host_reboot",
                "node_drain",
                "nginx_reload",
                "database_restore",
            ],
        }
    ]
    if upstream_502_shape:
        candidates.append(
            {
                "id": "harbor_watchdog_repair_once",
                "execution_route": "host_110_harbor_watchdog_executor",
                "current_status": (
                    "blocked_until_110_control_channel_and_check_unhealthy_receipt"
                ),
                "check_mode": "watchdog_check_then_single_repair_attempt",
                "bounded_apply": (
                    "run existing harbor-watchdog --repair-once only; no "
                    "Docker daemon restart, no host reboot, no provider switch"
                ),
            }
        )
    if gateway_drift_shape:
        candidates.append(
            {
                "id": "registry_public_gateway_rendered_diff",
                "execution_route": "host_188_nginx_controlled_apply_executor",
                "current_status": "waiting_rendered_diff_check_mode",
                "check_mode": "nginx_rendered_diff_and_nginx_t",
                "bounded_apply": (
                    "apply only registry.wooo.work route source diff after "
                    "nginx -t and rollback backup; no provider switch"
                ),
            }
        )
    return candidates


def _control_channel_contract(
    *,
    registry_ready: bool,
    candidate_packaged: bool,
    upstream_502_shape: bool,
) -> dict[str, Any]:
    required = (not registry_ready) and candidate_packaged and upstream_502_shape
    return {
        "required_before_runtime_apply": required,
        "accepted_control_channels": [
            "110_local_console_or_root_shell",
            "working_ssh_to_wooo_192_168_0_110",
            "online_gitea_runner_label_awoooi-host",
        ],
        "current_required_readbacks": (
            [
                "scripts/reboot-recovery/diagnose-110-ssh-publickey-auth.sh",
                "ops/runner/check-awoooi-110-controlled-cd-lane-readiness.sh",
                "ops/runner/read-public-gitea-actions-queue.py --json",
                "scripts/reboot-recovery/recover-110-control-path-and-harbor-local.sh --check",
            ]
            if required
            else []
        ),
        "blocked_runtime_apply_reason": (
            "harbor_watchdog_repair_once_requires_110_local_or_awoooi_host_runner_control_channel"
            if required
            else ""
        ),
        "token_collection_allowed": False,
        "runner_registration_token_read_allowed": False,
        "raw_authorized_keys_read_allowed": False,
        "workflow_dispatch_allowed": False,
        "host_write_allowed_by_this_contract": False,
    }


def _post_apply_verifiers() -> list[dict[str, Any]]:
    return [
        {
            "id": "public_registry_v2",
            "url": "https://registry.wooo.work/v2/",
            "expected_http_status": [200, 401],
        },
        {
            "id": "internal_110_registry_v2",
            "url": "http://192.168.0.110:5000/v2/",
            "expected_http_status": [200, 401],
        },
        {
            "id": "gitea_cd_rerun_readback",
            "url": "/api/v1/agents/awoooi-priority-work-order-readback",
            "expected_rollup": "production deploy marker advances after Harbor recovery",
        },
        {
            "id": "harbor_registry_controlled_recovery_receipt",
            "url": "/api/v1/agents/harbor-registry-controlled-recovery-receipt",
            "expected_status": "harbor_registry_recovery_receipt_verified",
        },
    ]


def _learning_writeback_contracts() -> list[dict[str, Any]]:
    targets = [
        "timeline_event",
        "knowledge_entry",
        "rag_embedding",
        "mcp_tool_registry_signal",
        "playbook_trust_score",
        "controlled_recovery_report",
    ]
    return [
        {
            "target": target,
            "writeback_mode": "metadata_only",
            "raw_log_allowed": False,
            "secret_value_allowed": False,
            "receipt_key": f"harbor_registry_recovery::{target}",
        }
        for target in targets
    ]


def _probe_endpoint(
    probe: Probe,
    url: str,
    timeout_seconds: float,
) -> dict[str, Any]:
    try:
        result = probe(url, timeout_seconds)
    except Exception as exc:  # pragma: no cover - defensive API boundary
        result = {"http_status": 0, "error": exc.__class__.__name__}
    return {"url": url, **_dict(result)}


def _http_probe(url: str, timeout_seconds: float) -> dict[str, Any]:
    request = urllib.request.Request(
        url,
        headers={"User-Agent": "awoooi-harbor-registry-preflight/1.0"},
    )
    try:
        with urllib.request.urlopen(request, timeout=timeout_seconds) as response:
            response.read(512)
            return {"http_status": int(getattr(response, "status", 200))}
    except urllib.error.HTTPError as exc:
        exc.read(512)
        return {"http_status": int(exc.code)}
    except urllib.error.URLError as exc:
        return {"http_status": 0, "error": str(exc.reason)}
    except TimeoutError:
        return {"http_status": 0, "error": "timeout"}


def _registry_ready(status_code: Any) -> bool:
    return status_code in {200, 401}


def _dict(value: Any) -> dict[str, Any]:
    return value if isinstance(value, dict) else {}


def _strings(value: Any) -> list[str]:
    if not isinstance(value, list):
        return []
    return [str(item) for item in value if item is not None]


def _unique_strings(values: list[str]) -> list[str]:
    seen: set[str] = set()
    unique: list[str] = []
    for value in values:
        if value in seen:
            continue
        seen.add(value)
        unique.append(value)
    return unique
