from __future__ import annotations

import importlib.metadata
import logging
from collections.abc import Callable, Iterable

from pygitweb.api.action import Action
from pygitweb.api.subpage import Subpage
from pygitweb.config import ACTIONS as BUILTIN_ACTIONS

logger = logging.getLogger(__name__)

SUBPAGE_GROUP = "pygitweb.subpages"
ACTION_GROUP = "pygitweb.actions"

PLUGIN_ACTIONS: dict[str, Action] = {}
SUBPAGES: list[Subpage] = []
DISPATCH_ACTIONS: set[str] = set(BUILTIN_ACTIONS)


def _entry_points(group: str) -> Iterable[importlib.metadata.EntryPoint]:
    return importlib.metadata.entry_points().select(group=group)


def _dist_name(ep: importlib.metadata.EntryPoint) -> str | None:
    dist = ep.dist
    if dist is None:
        return None
    return dist.name


def _enabled_set(enabled: frozenset[str] | None) -> frozenset[str]:
    if enabled is not None:
        return enabled
    from pygitweb.config import settings

    return frozenset(settings.ENABLED_PLUGINS)


def list_available_plugins() -> list[str]:
    """Unique sorted distribution names that declare pygitweb entry points."""
    names: set[str] = set()
    for group in (SUBPAGE_GROUP, ACTION_GROUP):
        for ep in _entry_points(group):
            name = _dist_name(ep)
            if name:
                names.add(name)
    return sorted(names)


def _register_plugin(
    register: Callable[[type], type],
    loaded: object,
    ep_name: str,
    kind: str,
) -> type | None:
    if not isinstance(loaded, type):
        logger.error("pygitweb: entry point %r must be a %s class, got %r", ep_name, kind, loaded)
        return None
    try:
        register(loaded)
    except TypeError as exc:
        logger.error(
            "pygitweb: entry point %r must implement %s, got %r (%s)",
            ep_name,
            kind,
            loaded,
            exc,
        )
        return None
    return loaded


def load_subpages(enabled: frozenset[str] | None = None) -> list[Subpage]:
    allowed = _enabled_set(enabled)
    out: list[Subpage] = []
    seen_names: set[str] = set()
    for ep in _entry_points(SUBPAGE_GROUP):
        dist = _dist_name(ep)
        if dist is None or dist not in allowed:
            continue
        try:
            loaded = ep.load()
        except Exception:
            logger.exception("pygitweb: failed to load subpage entry point %r", ep.name)
            continue
        plugin_cls = _register_plugin(Subpage.register, loaded, ep.name, "Subpage")
        if plugin_cls is None:
            continue
        try:
            instance = plugin_cls()
        except Exception:
            logger.exception("pygitweb: failed to instantiate subpage %r", ep.name)
            continue
        key = instance.subpage_name()
        if key in seen_names:
            logger.warning("pygitweb: duplicate subpage %r from %r; skipping", key, ep.name)
            continue
        seen_names.add(key)
        out.append(instance)
    return out


def load_plugin_actions(enabled: frozenset[str] | None = None) -> dict[str, Action]:
    allowed = _enabled_set(enabled)
    out: dict[str, Action] = {}
    for ep in _entry_points(ACTION_GROUP):
        dist = _dist_name(ep)
        if dist is None or dist not in allowed:
            continue
        try:
            loaded = ep.load()
        except Exception:
            logger.exception("pygitweb: failed to load action entry point %r", ep.name)
            continue
        plugin_cls = _register_plugin(Action.register, loaded, ep.name, "Action")
        if plugin_cls is None:
            continue
        try:
            instance = plugin_cls()
        except Exception:
            logger.exception("pygitweb: failed to instantiate action %r", ep.name)
            continue
        name = instance.action_name()
        if name in BUILTIN_ACTIONS:
            logger.warning(
                "pygitweb: plugin action %r conflicts with built-in; skipping entry %r",
                name,
                ep.name,
            )
            continue
        if name in out:
            logger.warning("pygitweb: duplicate plugin action %r; skipping entry %r", name, ep.name)
            continue
        out[name] = instance
    return out


def reload_plugins() -> tuple[dict[str, Action], list[Subpage]]:
    """Re-read ENABLED_PLUGINS and return freshly loaded actions and subpages."""
    from pygitweb.config import settings

    allowed = frozenset(settings.ENABLED_PLUGINS)
    return load_plugin_actions(allowed), load_subpages(allowed)


def apply_plugin_reload() -> None:
    """Reload plugin actions/subpages from ENABLED_PLUGINS into the runtime registry."""
    actions, subpages = reload_plugins()
    PLUGIN_ACTIONS.clear()
    PLUGIN_ACTIONS.update(actions)
    SUBPAGES.clear()
    SUBPAGES.extend(subpages)
    DISPATCH_ACTIONS.clear()
    DISPATCH_ACTIONS.update(BUILTIN_ACTIONS)
    DISPATCH_ACTIONS.update(PLUGIN_ACTIONS.keys())