"""Fail-closed checks that must pass before a Codegen provider is invoked."""

from __future__ import annotations

from typing import Any

from src.domain.codegen.client_surface import (
    required_client_operations,
    resolve_client_surface,
    unavailable_client_operations,
)
from src.domain.projects.runtime_invariants import ProjectRuntimeInvariants, validate_runtime_routes


class CodegenPreflightError(ValueError):
    """The resolved architecture is not executable by the generated host."""

    def __init__(self, checks: dict[str, bool], errors: list[str]):
        self.checks = checks
        self.errors = errors
        super().__init__("P2HS_CODEGEN_PREFLIGHT: " + "; ".join(errors))


def validate_codegen_preflight(
    *,
    generation_plan: dict[str, Any],
    resolved_context: dict[str, Any],
    port_context: dict[str, Any],
    runtime_invariants: dict[str, Any],
) -> dict[str, Any]:
    """Validate only deterministic inputs; never call an AI/provider here."""

    checks = {
        "generation_plan_semantically_resolved": True,
        "all_required_app_blocks_installed": True,
        "all_required_feature_packs_installed": True,
        "all_required_backend_ports_admitted": True,
        "all_required_client_operations_available": True,
        "runtime_contract_consistent": True,
        "resolved_bindings_complete": True,
        "managed_platform_tree_clean": True,
        "codegen_base_checkpoint_clean": True,
    }
    errors: list[str] = []
    composition = generation_plan.get("composition") or {}
    resolved = resolved_context or {}

    expected_blocks = set(composition.get("app_block_ids") or [])
    expected_packs = set(composition.get("feature_pack_ids") or [])
    if expected_blocks - {item.get("id") for item in resolved.get("app_blocks") or []}:
        checks["all_required_app_blocks_installed"] = False
        errors.append("required_app_blocks_not_resolved")
    if expected_packs - {item.get("id") for item in resolved.get("feature_packs") or []}:
        checks["all_required_feature_packs_installed"] = False
        errors.append("required_feature_packs_not_resolved")

    required = required_client_operations(generation_plan)
    surface = resolve_client_surface(port_context, required)
    unavailable = unavailable_client_operations(surface)
    if unavailable:
        checks["all_required_backend_ports_admitted"] = False
        checks["all_required_client_operations_available"] = False
        checks["resolved_bindings_complete"] = False
        errors.append(f"unavailable_client_operations={unavailable}")

    runtime = (port_context.get("runtime_contract") or {})
    routes = runtime.get("routes") or {}
    requirements = runtime.get("requirements") or {}
    capabilities = runtime.get("capabilities") or {}
    route_errors = validate_runtime_routes(
        ProjectRuntimeInvariants.model_validate(runtime_invariants)
    )
    if route_errors:
        checks["runtime_contract_consistent"] = False
        errors.extend(route_errors)
    if routes.get("admin_panel_path") != runtime_invariants.get("admin_panel_path", "/admin"):
        checks["runtime_contract_consistent"] = False
        errors.append("runtime_contract_admin_panel_path_mismatch")
    if routes.get("customer_login_path") != runtime_invariants.get("customer_login_path", "/login"):
        checks["runtime_contract_consistent"] = False
        errors.append("runtime_contract_customer_login_path_mismatch")
    if requirements.get("persistence_required") != bool(runtime_invariants.get("persistence_required")):
        checks["runtime_contract_consistent"] = False
        errors.append("runtime_contract_persistence_mismatch")
    if requirements.get("admin_must_read_persisted") != bool(runtime_invariants.get("admin_must_read_persisted")):
        checks["runtime_contract_consistent"] = False
        errors.append("runtime_contract_admin_read_mismatch")
    auth_capabilities = capabilities.get("auth") or {}
    declared_auth_methods = set(auth_capabilities.get("methods") or [])
    # New contracts declare executable methods. Keep the old invariant-only
    # comparison for contracts produced before that field existed.
    expected_magic = (
        bool({"passwordless", "magic_link"} & declared_auth_methods)
        if declared_auth_methods
        else "magic_link" in (runtime_invariants.get("auth_modes") or [])
    )
    if auth_capabilities.get("magic_link") != expected_magic:
        checks["runtime_contract_consistent"] = False
        errors.append("runtime_contract_magic_link_mismatch")

    if errors:
        raise CodegenPreflightError(checks, errors)
    return {
        "name": "P2HS_CODEGEN_PREFLIGHT",
        "checks": checks,
        "required_client_operations": required,
        "client_surface": surface,
    }


__all__ = ["CodegenPreflightError", "validate_codegen_preflight"]
