from __future__ import annotations

from collections.abc import Callable, Iterable, Sequence
import importlib

from govoplan_core.core.access import (
    DEFAULT_CAPABILITY_PROVIDERS,
)
from govoplan_core.core.discovery import discover_module_manifests
from govoplan_core.core.modules import ModuleManifest
from govoplan_core.core.registry import PlatformRegistry, RegistryError
from govoplan_core.server.config import validate_object_path

ManifestFactory = Callable[[], ModuleManifest]

_BUILTIN_MANIFESTS = {
    "access": "govoplan_access.backend.manifest:get_manifest",
    "tenancy": "govoplan_tenancy.backend.manifest:get_manifest",
}

def parse_enabled_modules(value: str | Iterable[str]) -> list[str]:
    if isinstance(value, str):
        items = [item.strip() for item in value.split(",")]
    else:
        items = [str(item).strip() for item in value]
    return [item for item in items if item]


def available_module_manifests(
    manifest_factories: Sequence[ManifestFactory] = (),
    *,
    enabled_modules: Iterable[str] | None = None,
    ignore_load_errors: bool = False,
) -> dict[str, ModuleManifest]:
    manifests = {
        manifest.id: manifest
        for manifest in discover_module_manifests(
            enabled_modules=enabled_modules,
            ignore_load_errors=ignore_load_errors,
        )
    }
    loading_all = enabled_modules is None
    builtin_ids = set(_BUILTIN_MANIFESTS) if loading_all else set(parse_enabled_modules(enabled_modules))
    for module_id in sorted(builtin_ids & set(_BUILTIN_MANIFESTS)):
        if module_id in manifests:
            continue
        try:
            manifests[module_id] = _load_builtin_manifest(module_id)
        except ModuleNotFoundError as exc:
            module_root = _BUILTIN_MANIFESTS[module_id].split(":", 1)[0].split(".", 1)[0]
            if exc.name != module_root or (not ignore_load_errors and not loading_all):
                raise
        except Exception:
            if not ignore_load_errors:
                raise
    for factory in manifest_factories:
        manifest = factory()
        manifests[manifest.id] = manifest
    return manifests


def build_platform_registry(
    enabled_modules: str | Iterable[str],
    *,
    manifest_factories: Sequence[ManifestFactory] = (),
) -> PlatformRegistry:
    requested = parse_enabled_modules(enabled_modules)
    available = _resolve_required_manifests(
        requested,
        manifest_factories=manifest_factories,
    )
    registry = PlatformRegistry()
    for module_id, manifest in available.items():
        registry.register(manifest)
    registry.validate()
    return registry


def _resolve_required_manifests(
    requested: Sequence[str],
    *,
    manifest_factories: Sequence[ManifestFactory],
) -> dict[str, ModuleManifest]:
    manifests: dict[str, ModuleManifest] = {}
    pending = list(dict.fromkeys(requested))

    while pending:
        module_ids = [module_id for module_id in pending if module_id not in manifests]
        pending = []
        if not module_ids:
            break

        available = available_module_manifests(
            manifest_factories,
            enabled_modules=module_ids,
        )
        for module_id in module_ids:
            manifest = available.get(module_id)
            if manifest is None:
                raise RegistryError(f"Enabled module is not available: {module_id}")
            manifests[module_id] = manifest

        provided_capabilities = {
            capability
            for manifest in manifests.values()
            for capability in manifest.capability_factories
        }
        for manifest in tuple(manifests.values()):
            pending.extend(
                dependency_id
                for dependency_id in manifest.dependencies
                if dependency_id not in manifests
            )
            for capability in manifest.required_capabilities:
                if capability in provided_capabilities:
                    continue
                provider_id = DEFAULT_CAPABILITY_PROVIDERS.get(capability)
                if provider_id is not None and provider_id not in manifests:
                    pending.append(provider_id)

    return manifests


def _load_builtin_manifest(module_id: str) -> ModuleManifest:
    target = _BUILTIN_MANIFESTS[module_id]
    module_name, function_name = validate_object_path(target)
    if module_name != _BUILTIN_MANIFESTS[module_id].split(":", 1)[0]:
        raise RegistryError(f"Built-in module manifest target changed unexpectedly: {module_id}")
    module = importlib.import_module(module_name)  # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import
    factory = getattr(module, function_name)
    manifest = factory()
    if not isinstance(manifest, ModuleManifest):
        raise RegistryError(f"Built-in module manifest factory returned invalid value: {module_id}")
    return manifest
