from __future__ import annotations

from dataclasses import dataclass
from importlib import import_module
from importlib.metadata import distributions

from flask import Flask

from coati_payroll.log import log
from coati_payroll.model import PluginRegistry, db


PLUGIN_DISTRIBUTION_PREFIX = "coati-payroll-plugin-"


@dataclass(frozen=True)
class DiscoveredPlugin:
    distribution_name: str
    plugin_id: str
    version: str | None


def _distribution_to_plugin_id(distribution_name: str) -> str:
    if not distribution_name.startswith(PLUGIN_DISTRIBUTION_PREFIX):
        return distribution_name
    return distribution_name[len(PLUGIN_DISTRIBUTION_PREFIX) :]


def _plugin_id_to_module_name(plugin_id: str) -> str:
    return f"coati_payroll_plugin_{plugin_id.replace('-', '_')}"


def discover_installed_plugins() -> list[DiscoveredPlugin]:
    found: list[DiscoveredPlugin] = []

    for dist in distributions():
        name = (dist.metadata.get("Name") or "").strip()
        if not name or not name.startswith(PLUGIN_DISTRIBUTION_PREFIX):
            continue

        plugin_id = _distribution_to_plugin_id(name)
        version = (dist.version or "").strip() or None
        found.append(DiscoveredPlugin(distribution_name=name, plugin_id=plugin_id, version=version))

    found.sort(key=lambda p: p.distribution_name)
    return found


def sync_plugin_registry() -> None:
    installed = {p.distribution_name: p for p in discover_installed_plugins()}

    rows = db.session.execute(db.select(PluginRegistry)).scalars().all()
    by_name = {r.distribution_name: r for r in rows}

    changed = False

    for name, plugin in installed.items():
        if name in by_name:
            if by_name[name].plugin_id != plugin.plugin_id:
                by_name[name].plugin_id = plugin.plugin_id
                changed = True
            if by_name[name].version != plugin.version:
                by_name[name].version = plugin.version
                changed = True
            if by_name[name].installed is not True:
                by_name[name].installed = True
                changed = True
            continue

        record = PluginRegistry()
        record.distribution_name = name
        record.plugin_id = plugin.plugin_id
        record.version = plugin.version
        record.active = False
        record.installed = True
        db.session.add(record)
        changed = True

    for name, record in by_name.items():
        if name in installed:
            continue
        if record.installed is not False:
            record.installed = False
            changed = True
        if record.active:
            record.active = False
            changed = True

    if changed:
        db.session.commit()


def _load_plugin_module(distribution_name: str, plugin_id: str):
    module_name = _plugin_id_to_module_name(plugin_id)
    try:
        return import_module(module_name)
    except ModuleNotFoundError as exc:
        raise ModuleNotFoundError(
            f"Plugin '{distribution_name}' is installed but does not provide module '{module_name}'."
        ) from exc


def load_plugin_module(plugin_id: str):
    module_name = _plugin_id_to_module_name(plugin_id)
    return import_module(module_name)


def register_active_plugins(app: Flask) -> None:
    active_plugins = (
        db.session.execute(db.select(PluginRegistry).filter_by(active=True, installed=True)).scalars().all()
    )

    for plugin in active_plugins:
        try:
            module = _load_plugin_module(plugin.distribution_name, plugin.plugin_id)

            register = getattr(module, "register_blueprints", None)
            if register is None or not callable(register):
                raise AttributeError("Missing callable 'register_blueprints(app)'")

            register(app)
        except (ModuleNotFoundError, AttributeError) as exc:
            log.warning(f"Plugin '{plugin.distribution_name}' could not be registered: {exc}")
            plugin.active = False
            plugin.installed = False
            try:
                db.session.commit()
            except Exception:
                db.session.rollback()
        except Exception as exc:
            log.warning(f"Plugin '{plugin.distribution_name}' failed during registration: {exc}")
            plugin.active = False
            try:
                db.session.commit()
            except Exception:
                db.session.rollback()


def get_active_plugins_menu_entries() -> list[dict]:
    active_plugins = (
        db.session.execute(db.select(PluginRegistry).filter_by(active=True, installed=True)).scalars().all()
    )

    entries: list[dict] = []
    for plugin in active_plugins:
        try:
            module = _load_plugin_module(plugin.distribution_name, plugin.plugin_id)

            getter = getattr(module, "get_menu_entry", None)
            if callable(getter):
                entry = getter()
            else:
                entry = getattr(module, "MENU_ENTRY", None)

            if not isinstance(entry, dict):
                continue

            label = entry.get("label")
            icon = entry.get("icon")
            url = entry.get("url")
            if not label or not url:
                continue

            entries.append(
                {
                    "distribution_name": plugin.distribution_name,
                    "plugin_id": plugin.plugin_id,
                    "label": label,
                    "icon": icon,
                    "url": url,
                }
            )
        except Exception as exc:
            log.warning(f"Plugin '{plugin.distribution_name}' menu entry could not be loaded: {exc}")

    return entries
