"""Multi-cloud dependency graph and wiring map.

Loads per-cloud metadata from tf_mcp.clouds.{aws,azure,gcp} and exposes
unified lookup functions that accept a cloud parameter.
"""

from __future__ import annotations

import importlib
from types import ModuleType

from tf_mcp.models import ModuleDependency, WiringEntry

# Supported clouds and their metadata modules
CLOUD_MODULES: dict[str, ModuleType] = {}

for _cloud in ("aws", "azure", "gcp"):
    try:
        CLOUD_MODULES[_cloud] = importlib.import_module(f"tf_mcp.clouds.{_cloud}")
    except ImportError:
        pass


def _get_cloud(cloud: str) -> ModuleType:
    if cloud not in CLOUD_MODULES:
        raise ValueError(f"Unknown cloud '{cloud}'. Available: {', '.join(CLOUD_MODULES)}")
    return CLOUD_MODULES[cloud]


def get_module_layers(cloud: str) -> dict[str, int]:
    return _get_cloud(cloud).MODULE_LAYERS


def get_module_descriptions(cloud: str) -> dict[str, str]:
    return _get_cloud(cloud).MODULE_DESCRIPTIONS


def get_direct_dependencies(cloud: str, module_name: str) -> list[str]:
    deps = _get_cloud(cloud).DIRECT_DEPENDENCIES
    return deps.get(module_name, [])


def get_wiring_map(cloud: str) -> dict[tuple[str, str], dict[str, str]]:
    return _get_cloud(cloud).WIRING_MAP


def get_provider_config(cloud: str) -> dict:
    return _get_cloud(cloud).PROVIDER_CONFIG


def get_identity_defaults(cloud: str, module_name: str) -> dict[str, str]:
    return _get_cloud(cloud).IDENTITY_DEFAULTS.get(module_name, {})


def get_key_outputs(cloud: str) -> dict[str, list[str]]:
    return _get_cloud(cloud).KEY_OUTPUTS


def get_short_name(cloud: str, module_name: str) -> str:
    short = _get_cloud(cloud).SHORT_NAMES
    return short.get(module_name, module_name.replace("-", "_"))


def get_dependents(cloud: str, module_name: str) -> list[str]:
    deps = _get_cloud(cloud).DIRECT_DEPENDENCIES
    result: list[str] = []
    for target, sources in deps.items():
        if module_name in sources:
            result.append(target)
    return result


def get_wiring(cloud: str, source: str, target: str) -> list[WiringEntry]:
    mapping = get_wiring_map(cloud).get((source, target), {})
    return [
        WiringEntry(source_output=src_out, target_input=tgt_in)
        for src_out, tgt_in in mapping.items()
    ]


def get_module_dependency_info(cloud: str, module_name: str) -> list[ModuleDependency]:
    deps: list[ModuleDependency] = []
    for source in get_direct_dependencies(cloud, module_name):
        deps.append(
            ModuleDependency(
                source=source,
                target=module_name,
                wiring=get_wiring(cloud, source, module_name),
            )
        )
    return deps


def resolve_transitive_dependencies(cloud: str, module_names: list[str]) -> list[str]:
    """Resolve all transitive dependencies, topologically sorted."""
    layers = get_module_layers(cloud)
    needed: set[str] = set()

    def _add(name: str) -> None:
        if name in needed:
            return
        needed.add(name)
        for dep in get_direct_dependencies(cloud, name):
            _add(dep)

    for name in module_names:
        _add(name)

    return sorted(needed, key=lambda m: (layers.get(m, 99), m))


def get_dependency_graph_text(cloud: str) -> str:
    """Return a human-readable text dependency graph for a cloud."""
    layers = get_module_layers(cloud)
    if not layers:
        return f"No modules registered for {cloud}."

    lines: list[str] = []
    lines.append(f"{cloud.upper()} Module Dependency Graph")
    lines.append("=" * 40)
    lines.append("")

    for layer_num in sorted(set(layers.values())):
        layer_modules = sorted(m for m, l in layers.items() if l == layer_num)
        lines.append(f"Layer {layer_num}:")
        for mod in layer_modules:
            deps = get_direct_dependencies(cloud, mod)
            dep_str = f" (depends on: {', '.join(deps)})" if deps else ""
            lines.append(f"  - {mod}{dep_str}")
            for source in deps:
                wiring = get_wiring(cloud, source, mod)
                for w in wiring:
                    lines.append(
                        f"      module.{source}.{w.source_output} -> {w.target_input}"
                    )
        lines.append("")

    return "\n".join(lines)
