"""Generated-product runtime dependency manifest.

The install plan decides what is installed. This manifest records what may be
degraded after installation, including the provider contract that explains why.
It is a projection only: Core remains the policy authority and Cloud remains the
runtime enforcement/execution authority.
"""
from __future__ import annotations

from typing import Any

from src.domain.app_blocks.registry import AppBlockRegistry, load_default_registry
from src.domain.app_blocks.schemas import AppBlockPlan
from src.domain.app_blocks.v2_contract import V2AppBlockContract, v2_contract_catalog_payload


def build_runtime_dependency_manifest(
    plan: AppBlockPlan,
    *,
    registry: AppBlockRegistry | None = None,
    runtime_contract: dict[str, Any] | None = None,
) -> dict[str, Any]:
    registry = registry or load_default_registry()
    capabilities = (runtime_contract or {}).get("capabilities") or {}
    by_block: dict[str, dict[str, Any]] = {}
    dependency_rows: dict[str, dict[str, Any]] = {}

    for block in plan.blocks:
        manifest = registry.get(block.id)
        if manifest is None:
            continue
        block_requirements = [
            item for item in manifest.provider_bindings if item.configuration_required
        ]
        rows: list[dict[str, Any]] = []
        for binding in block_requirements:
            cap = binding.capability
            resolved = capabilities.get(cap) if isinstance(capabilities.get(cap), dict) else {}
            available = bool(resolved.get("available"))
            if available:
                status = "ready"
            elif binding.missing_configuration_behavior == "disabled":
                status = "disabled"
            else:
                status = "configuration_required"
            row = {
                "capability": cap,
                "status": status,
                "available": status == "ready",
                "providers": list(binding.providers),
                "configuration_required": binding.configuration_required,
                "missing_configuration_behavior": binding.missing_configuration_behavior,
            }
            rows.append(row)
            dependency_rows[cap] = row
        by_block[block.id] = {
            "id": block.id,
            "requires": [dep.id for dep in manifest.requires],
            "provider_bindings": [
                {
                    "capability": binding.capability,
                    "supported": list(binding.providers),
                    "configuration_required": binding.configuration_required,
                    "missing_configuration_behavior": binding.missing_configuration_behavior,
                }
                for binding in manifest.provider_bindings
            ],
            "status": (
                "configuration_required"
                if any(item["status"] == "configuration_required" for item in rows)
                else "ready"
            ),
            "runtime_dependencies": rows,
        }

    return {
        "schema_version": 1,
        "dependencies": dependency_rows,
        "blocks": by_block,
    }


def runtime_dependency_module(manifest: dict[str, Any]) -> str:
    """Small framework-neutral ESM projection for generated apps."""
    import json

    return (
        "// Generated by P2HS. Dependency state is descriptive; platform routes "
        "remain authoritative.\n"
        f"export const runtimeDependencies = {json.dumps(manifest, indent=2)};\n"
        "export const RUNTIME_DEPENDENCY_STATUSES = ["
        "'ready', 'configuration_required', 'degraded', 'disabled', 'unsupported'];\n"
    )


def build_v2_runtime_dependency_manifest(
    blocks: list[V2AppBlockContract] | None = None,
    *,
    configured_providers: set[str] | None = None,
) -> dict[str, Any]:
    """Project V2 semantic bindings into the generated app manifest.

    Provider names are only binding metadata.  ``requires`` remains semantic,
    so changing Stripe to Paddle changes this projection's binding status but
    never the application's product contract.
    """
    configured = configured_providers or set()
    selected = blocks or list(
        V2AppBlockContract.model_validate(item)
        for item in v2_contract_catalog_payload()["blocks"]
    )
    dependencies: dict[str, dict[str, Any]] = {}
    block_rows: dict[str, dict[str, Any]] = {}
    for block in selected:
        rows: list[dict[str, Any]] = []
        binding_rows: dict[str, dict[str, Any]] = {}
        for binding_key, binding in block.bindings.items():
            configured_provider = next(
                (provider for provider in binding.supported if provider in configured),
                None,
            )
            selected_provider = configured_provider or binding.provider
            status = "ready" if configured_provider else binding.status
            if binding.configuration_required and not configured_provider:
                status = "configuration_required"
            binding_row = binding.model_dump(mode="json")
            binding_row["provider"] = selected_provider
            binding_row["status"] = status
            binding_rows[binding_key] = binding_row
            row = {
                "capability": binding_key,
                "status": status,
                "available": status == "ready",
                "provider": selected_provider,
                "supported": list(binding.supported),
                "configuration_required": binding.configuration_required,
                "missing_configuration_behavior": binding.missing_configuration_behavior,
            }
            rows.append(row)
            dependencies[binding_key] = row
        block_rows[block.id] = {
            "id": block.id,
            "requires": list(block.requires),
            "bindings": binding_rows,
            "status": (
                "configuration_required"
                if any(row["status"] == "configuration_required" for row in rows)
                else ("ready" if rows and all(row["status"] == "ready" for row in rows) else block.status)
            ),
            "runtime_dependencies": rows,
        }
    return {"schema_version": 2, "dependencies": dependencies, "blocks": block_rows}
