from __future__ import annotations

from src.domain.app_blocks.catalogs import (
    canonical_feature_pack,
    default_known_packs,
    default_primitives,
)
from src.domain.app_blocks.registry import AppBlockRegistry, load_default_registry
from src.domain.composition_recipes.schemas import (
    CompositionRecipe,
    RecipeGap,
    RecipeReadiness,
)
from src.domain.projects.discovery_catalog import CLOUD_CAPABILITIES
from src.domain.starter_library.archetypes import get_archetype_starter


def validate_recipe(
    recipe: CompositionRecipe,
    *,
    registry: AppBlockRegistry | None = None,
    known_packs: frozenset[str] | None = None,
    primitives: frozenset[str] | None = None,
) -> list[RecipeGap]:
    registry = registry or load_default_registry()
    known_packs = known_packs if known_packs is not None else default_known_packs()
    primitives = primitives if primitives is not None else default_primitives()
    gaps: list[RecipeGap] = []
    if get_archetype_starter(recipe.starter_id) is None:
        gaps.append(
            RecipeGap(
                kind="missing_starter",
                id=recipe.starter_id,
                message="Starter is not admitted.",
            )
        )
    for value in recipe.required_feature_packs:
        if canonical_feature_pack(value) not in known_packs:
            gaps.append(
                RecipeGap(
                    kind="missing_feature_pack",
                    id=value,
                    message="Feature Pack is not admitted.",
                )
            )
    for value in recipe.optional_feature_packs:
        if canonical_feature_pack(value) not in known_packs:
            gaps.append(
                RecipeGap(
                    kind="missing_feature_pack",
                    id=value,
                    message="Optional Feature Pack is not admitted.",
                    severity="partial",
                )
            )
    for value in recipe.required_app_blocks:
        block = registry.get(value)
        if block is None:
            gaps.append(
                RecipeGap(
                    kind="missing_app_block", id=value, message="App Block is not admitted."
                )
            )
        elif not block.implemented:
            gaps.append(
                RecipeGap(
                    kind="unimplemented_app_block",
                    id=value,
                    message="App Block is not implemented.",
                )
            )
    for value in recipe.optional_app_blocks:
        block = registry.get(value)
        if block is None or not block.implemented:
            gaps.append(
                RecipeGap(
                    kind="missing_app_block",
                    id=value,
                    message="Optional App Block is unavailable.",
                    severity="partial",
                )
            )
    known_capabilities = set(CLOUD_CAPABILITIES) | set(primitives)
    known_capabilities.update(cap.id for block in registry.all() for cap in block.provides)
    for value in recipe.required_capabilities:
        if value not in known_capabilities:
            gaps.append(
                RecipeGap(
                    kind="missing_capability", id=value, message="Capability is not admitted."
                )
            )
    for value in recipe.optional_capabilities:
        if value not in known_capabilities:
            gaps.append(
                RecipeGap(
                    kind="missing_capability",
                    id=value,
                    message="Optional capability is unavailable.",
                    severity="partial",
                )
            )
    return gaps


def readiness_for_recipe(recipe: CompositionRecipe, **kwargs) -> RecipeReadiness:
    gaps = validate_recipe(recipe, **kwargs)
    blocking = [gap for gap in gaps if gap.severity == "blocking"]
    partial = [gap for gap in gaps if gap.severity == "partial"]
    if blocking:
        status = "blocked" if any(
            gap.kind
            in {
                "missing_starter",
                "missing_feature_pack",
                "missing_app_block",
                "unimplemented_app_block",
                "missing_capability",
            }
            for gap in blocking
        ) else "partial"
    elif partial:
        status = "partial"
    else:
        status = "configuration_only" if recipe.provider_requirements else "ready"
    return RecipeReadiness(
        status=status,
        gaps=gaps,
        checked_catalog_versions={"cloud": "2", "recipe": "1"},
    )
