"""AWOOOI production deploy readback blocker.

Loads the committed production deploy/readback blocker snapshot. This service is
read-only: it does not call Gitea, trigger workflows, inspect credentials, touch
K8s, or modify runtime state.
"""

from __future__ import annotations

import json
import os
import re
from pathlib import Path
from typing import Any

from src.services.snapshot_paths import default_operations_dir

_DEFAULT_OPERATIONS_DIR = default_operations_dir(Path(__file__))
_SNAPSHOT_FILE = "awoooi-production-deploy-readback-blocker.snapshot.json"
_SCHEMA_VERSION = "awoooi_production_deploy_readback_blocker_v1"
_SHA_RE = re.compile(r"^[0-9a-f]{40}$")


def load_latest_awoooi_production_deploy_readback_blocker(
    operations_dir: Path | None = None,
) -> dict[str, Any]:
    """Load the committed production deploy/readback blocker snapshot."""
    directory = operations_dir or _DEFAULT_OPERATIONS_DIR
    path = directory / _SNAPSHOT_FILE
    with path.open(encoding="utf-8") as handle:
        payload = json.load(handle)

    if not isinstance(payload, dict):
        raise ValueError(f"{path}: expected JSON object")
    _enrich_runtime_build_readback(payload)
    _require_schema(payload, str(path))
    _require_operation_boundaries(payload, str(path))
    _require_rollup_consistency(payload, str(path))
    _require_no_internal_network_literals(payload, str(path))
    return payload


def _require_schema(payload: dict[str, Any], label: str) -> None:
    actual = payload.get("schema_version")
    if actual != _SCHEMA_VERSION:
        raise ValueError(
            f"{label}: expected schema_version={_SCHEMA_VERSION}, got {actual!r}"
        )


def _require_operation_boundaries(payload: dict[str, Any], label: str) -> None:
    boundaries = _dict(payload.get("operation_boundaries"))
    if boundaries.get("read_only_api_allowed") is not True:
        raise ValueError(f"{label}: read_only_api_allowed must be true")

    blocked_flags = {
        "deploy_trigger_allowed",
        "workflow_modification_allowed",
        "gitea_api_write_allowed",
        "host_write_allowed",
        "k8s_write_allowed",
        "secret_read_allowed",
        "github_api_allowed",
        "raw_session_or_sqlite_read_allowed",
    }
    allowed = sorted(flag for flag in blocked_flags if boundaries.get(flag) is not False)
    if allowed:
        raise ValueError(f"{label}: operation boundaries must remain false: {allowed}")


def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None:
    readback = _dict(payload.get("readback"))
    rollups = _dict(payload.get("rollups"))
    blockers = _list(payload.get("blockers"))
    next_actions = _list(payload.get("next_actions"))

    if rollups.get("hard_blocker_count") != len(blockers):
        raise ValueError(f"{label}: hard_blocker_count must match blockers")
    if rollups.get("next_action_count") != len(next_actions):
        raise ValueError(f"{label}: next_action_count must match next_actions")
    if rollups.get("production_image_tag_matches_main") is not (
        readback.get("production_image_tag_matches_main") is True
    ):
        raise ValueError(
            f"{label}: production_image_tag_matches_main must match readback"
        )
    if rollups.get("authorized_dispatch_channel_ready") is not (
        readback.get("authorized_dispatch_channel_ready") is True
    ):
        raise ValueError(
            f"{label}: authorized_dispatch_channel_ready must match readback"
        )


def _enrich_runtime_build_readback(payload: dict[str, Any]) -> None:
    build_sha = os.getenv("AWOOOI_BUILD_COMMIT_SHA", "").strip().lower()
    if not _SHA_RE.fullmatch(build_sha):
        return

    readback = _dict(payload.get("readback"))
    rollups = _dict(payload.get("rollups"))
    committed_source_sha = str(readback.get("observed_source_control_main_sha") or "")
    committed_image_sha = str(readback.get("production_image_tag_sha") or "")
    source_matches_runtime = committed_source_sha == build_sha
    image_matches_runtime = committed_image_sha == build_sha

    readback["runtime_build_commit_sha"] = build_sha
    readback["runtime_build_commit_short_sha"] = build_sha[:10]
    readback["runtime_build_matches_committed_source_control_readback"] = (
        source_matches_runtime
    )
    readback["runtime_build_matches_committed_production_image_tag"] = (
        image_matches_runtime
    )
    matches_committed_deploy_readback = source_matches_runtime and image_matches_runtime
    readback["runtime_build_readback_status"] = (
        "matches_committed_deploy_readback"
        if matches_committed_deploy_readback
        else "runtime_build_diverges_from_committed_deploy_readback"
    )

    desired_tag = os.getenv("AWOOOI_DESIRED_API_IMAGE_TAG", "").strip().lower()
    if not _SHA_RE.fullmatch(desired_tag):
        readback["desired_main_api_image_tag_readback_status"] = "unavailable"
        _mark_runtime_image_tag_blocked(
            payload,
            blocker="gitea_main_desired_api_image_tag_readback_unavailable",
            next_action=(
                "redeploy_with_awoooi_desired_api_image_tag_env_matching_gitops"
                "_desired_api_tag"
            ),
        )
        return

    readback["desired_main_api_image_tag_source"] = "gitops_deployment_env"
    readback["desired_main_api_image_tag_readback_status"] = "ok"
    readback["desired_main_api_image_tag_sha"] = desired_tag
    readback["desired_main_api_image_tag_short_sha"] = desired_tag[:10]
    runtime_matches_gitops_desired = build_sha == desired_tag
    readback["runtime_build_matches_gitops_desired_image_tag"] = (
        runtime_matches_gitops_desired
    )
    if runtime_matches_gitops_desired and not matches_committed_deploy_readback:
        readback["runtime_build_readback_status"] = (
            "matches_gitops_desired_image_tag_with_committed_deploy_readback_drift"
        )
    image_matches_main = runtime_matches_gitops_desired
    readback["production_image_tag_matches_main"] = image_matches_main
    rollups["source_control_main_ready"] = True
    rollups["production_image_tag_matches_main"] = image_matches_main
    if image_matches_main:
        return

    _mark_runtime_image_tag_blocked(
        payload,
        blocker="production_runtime_image_tag_does_not_match_gitea_main_desired_tag",
        next_action=(
            "complete_cd_rollout_until_runtime_build_commit_matches_gitops"
            "_desired_api_image_tag_env"
        ),
        source_control_main_ready=True,
    )


def _mark_runtime_image_tag_blocked(
    payload: dict[str, Any],
    *,
    blocker: str,
    next_action: str,
    source_control_main_ready: bool = False,
) -> None:
    payload["status"] = "blocked_production_runtime_image_tag_not_verified"
    readback = _dict(payload.get("readback"))
    readback["production_image_tag_matches_main"] = False
    blockers = _list(payload.setdefault("blockers", []))
    if blocker not in blockers:
        blockers.append(blocker)
    next_actions = _list(payload.setdefault("next_actions", []))
    if next_action not in next_actions:
        next_actions.append(next_action)
    rollups = _dict(payload.get("rollups"))
    rollups["hard_blocker_count"] = len(blockers)
    rollups["next_action_count"] = len(next_actions)
    rollups["source_control_main_ready"] = source_control_main_ready
    rollups["production_image_tag_matches_main"] = False


def _require_no_internal_network_literals(value: Any, label: str) -> None:
    text = json.dumps(value, ensure_ascii=False, sort_keys=True)
    forbidden = ["192.168.0.", "api.github.com", "github.com/"]
    hits = [item for item in forbidden if item in text]
    if hits:
        raise ValueError(f"{label}: forbidden literal(s) in public readback: {hits}")


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


def _list(value: Any) -> list[Any]:
    return value if isinstance(value, list) else []


def _append_unique(payload: dict[str, Any], key: str, item: str) -> None:
    values = payload.get(key)
    if not isinstance(values, list):
        values = []
        payload[key] = values
    if item not in values:
        values.append(item)
