"""Variant payload baking for the development inventory.

Two helpers, both pure: `_resolve_variant_payloads` picks the variant
payload for each app in a round's include set, and `_bake_overrides`
merges those payloads under `applications:` inside the `--vars` JSON
the inventory provision step consumes. Caller-supplied `extra_vars`
always win over the variant bake — that's the documented "user vars
always win" rule.
"""

from __future__ import annotations

from collections.abc import Mapping, Sequence
from typing import Any

from utils.cache.applications import get_variant_overrides_only
from utils.roles.applications.topics import apply_topic
from utils.roles.applications.variants import collect_provider_overrides


def _resolve_variant_payloads(
    *,
    roles_dir: str,
    include: Sequence[str],
    active_variants: Mapping[str, int],
) -> dict[str, Any]:
    """Return ``{app_id: variant_override}`` for the requested round.

    Each value is the raw `meta/variants.yml` entry — NOT the
    deep-merge of variants.yml on top of `meta/services.yml`. Baking
    only the override keeps host_vars sparse so downstream passes
    (notably `apply_mirror_overrides`) can populate fields like
    `image`/`version` that the role declares in `meta/services.yml`.

    Apps without `meta/variants.yml` collapse to a single empty
    override `{}`. Out-of-range indices clamp to 0.

    A config topic a variant declares under a service it depends on is
    finally moved onto that service's provider, so the role pulling a
    dependency in is what decides how lean the dependency runs.
    """
    overrides_per_app = get_variant_overrides_only(roles_dir=roles_dir)
    resolved: dict[str, Any] = {}
    for app_id in include:
        variant_list = overrides_per_app.get(app_id) or [{}]
        if not variant_list:
            continue
        index = active_variants.get(app_id, 0)
        if not 0 <= index < len(variant_list):
            index = 0
        resolved[app_id] = variant_list[index]
    for provider, topics in collect_provider_overrides(
        resolved, roles_dir=roles_dir
    ).items():
        if provider not in resolved:
            continue
        payload = resolved[provider]
        payload = dict(payload) if isinstance(payload, Mapping) else {}
        for topic, value in topics.items():
            payload[topic] = apply_topic(payload.get(topic), value)
        resolved[provider] = payload
    return resolved


def _bake_overrides(
    *,
    base_overrides: Mapping[str, Any],
    variant_payloads: Mapping[str, Any],
) -> dict[str, Any]:
    """Merge the per-app variant payloads under `applications:` into the
    `--vars` JSON the init step will hand to
    `infinito administration inventory provision`.
    Caller-supplied `extra_vars` (already inside `base_overrides`) win
    over the variant bake when the same key is set on both sides — that
    matches the existing "user vars always win" rule."""
    merged: dict[str, Any] = dict(base_overrides)
    if not variant_payloads:
        return merged
    existing_apps = merged.get("applications")
    if not isinstance(existing_apps, Mapping):
        existing_apps = {}
    apps: dict[str, Any] = dict(variant_payloads.items())
    for app_id, override in existing_apps.items():
        base_payload = apps.get(app_id)
        if isinstance(base_payload, Mapping) and isinstance(override, Mapping):
            apps[app_id] = {**base_payload, **override}
        else:
            apps[app_id] = override
    merged["applications"] = apps
    return merged
