"""插件本地运行态和远程市场目录投影。"""

from __future__ import annotations

import importlib.util
from collections.abc import Callable, Mapping
from typing import Any, Optional

from app.foundation.version import compare_version
from app.runtime.settings import RuntimeSettingsCompat

settings = RuntimeSettingsCompat()
from app.runtime.extensions.plugin.contracts import supports_plugin_hook
from app.runtime.extensions.plugin.storage import PluginStorage
from app.runtime.extensions.plugin.system import PluginSystemServices
from app.schemas.plugin import Plugin, PluginInstance, PluginRuntimeStatus
from app.schemas.types import SystemConfigKey


class PluginCatalogFacade:
    """把插件目录应用服务与运行态注册表连接起来。"""

    def __init__(
        self,
        *,
        classes: Callable[[], Mapping[str, Any]],
        running: Callable[[], Mapping[str, Any]],
        storage: Callable[[], PluginStorage],
        system: Callable[[], PluginSystemServices],
        market_catalog: Callable[[], Any],
        market_loader: Callable[..., Any],
        async_market_loader: Callable[..., Any],
        map_plugin: Callable[..., Optional[Plugin]],
        auth_checker: Callable[..., bool],
        plugin_attr: Callable[[str, str], Any],
        plugin_instance: Callable[[str], Optional[PluginInstance]],
        plugin_instances: Callable[[], dict[str, PluginInstance]],
        runtime_status: Callable[[str], Optional[PluginRuntimeStatus]],
        log: Any,
    ) -> None:
        """保存注册表、目录服务和插件外部系统端口。"""
        self._classes = classes
        self._running = running
        self._storage = storage
        self._system = system
        self._market_catalog = market_catalog
        self._market_loader = market_loader
        self._async_market_loader = async_market_loader
        self._map_plugin = map_plugin
        self._auth_checker = auth_checker
        self._plugin_attr = plugin_attr
        self._plugin_instance = plugin_instance
        self._plugin_instances = plugin_instances
        self._runtime_status = runtime_status
        self._logger = log

    def online(self, force: bool = False) -> list[Plugin]:
        """读取所有兼容代际的在线插件目录。"""
        if not settings.PLUGIN_MARKET:
            return []
        markets = [item for item in settings.PLUGIN_MARKET.split(",") if item]
        result = self._market_catalog().collect(
            markets=markets,
            compatible_flags=self._system().compatible_flags(settings.VERSION_FLAG),
            force=force,
            loader=self._market_loader,
        )
        self._logger.info(f"获取到 {len(result)} 个线上插件")
        return result

    def local(self) -> list[Plugin]:
        """把已加载插件投影为本地插件目录 DTO。"""
        installed = self._installed_ids()
        plugins: list[Plugin] = []
        for plugin_id, plugin_class in self._classes().items():
            plugin_instance = self._running().get(plugin_id)
            instance = self._plugin_instance(plugin_id)
            plugin = Plugin(
                id=plugin_id,
                installed=plugin_id in installed,
                state=self._safe_state(plugin_id, plugin_instance),
                runtime_status=self._runtime_status(plugin_id),
                has_page=supports_plugin_hook(plugin_class, "get_page"),
                plugin_public_key=getattr(plugin_class, "plugin_public_key", None),
                plugin_name=getattr(plugin_class, "plugin_name", None),
                plugin_desc=getattr(plugin_class, "plugin_desc", None),
                plugin_version=getattr(plugin_class, "plugin_version", None),
                plugin_icon=getattr(plugin_class, "plugin_icon", None),
                plugin_author=getattr(plugin_class, "plugin_author", None),
                author_url=getattr(plugin_class, "author_url", None),
                plugin_order=getattr(plugin_class, "plugin_order", 0),
                has_update=False,
                is_local=True,
                source_plugin_id=getattr(plugin_class, "plugin_source_id", None),
                is_instance=instance is not None,
                instance_mode=instance.mode if instance else None,
            )
            if not self._auth_checker(plugin=plugin, source=plugin_class):
                continue
            plugins.append(plugin)
        plugins.sort(key=lambda item: getattr(item, "plugin_order", 0))
        return plugins

    def installed(self) -> list[Plugin]:
        """按安装清单投影插件，未加载项目仍返回可观察占位卡片。"""
        installed_ids = self._installed_ids()
        local_by_id = {
            plugin.id: plugin
            for plugin in self.local()
            if plugin.installed and plugin.id
        }
        result = []
        for plugin_id in installed_ids:
            plugin = local_by_id.get(plugin_id)
            if plugin:
                result.append(plugin)
                continue
            instance = self._plugin_instance(plugin_id)
            result.append(Plugin(
                id=plugin_id,
                plugin_name=plugin_id,
                installed=True,
                state=False,
                runtime_status=self._runtime_status(plugin_id),
                is_local=True,
                source_plugin_id=(
                    instance.source_plugin_id if instance else None
                ),
                is_instance=instance is not None,
                instance_mode=instance.mode if instance else None,
            ))
        # 展示顺序由持久化安装清单保留，避免后台恢复或占位卡片出现后改变用户看到的位置。
        # 前端可用用户级 PluginOrder 覆盖，plugin_order 只用于运行期插件发现顺序。
        return result

    def local_version(self, plugin_id: str) -> Optional[str]:
        """读取指定已安装插件版本，不触发全量目录投影。"""
        installed = self._installed_ids()
        if plugin_id not in installed:
            return None
        plugin_class = self._classes().get(plugin_id)
        return getattr(plugin_class, "plugin_version", None)

    def local_repository(self) -> list[Plugin]:
        """读取本地插件仓候选并映射为目录 DTO。"""
        installed = self._storage().read(SystemConfigKey.UserInstalledPlugins) or []
        candidates = self._system().local_candidates()
        plugins: list[Plugin] = []
        for plugin_id, info in candidates.items():
            package_version = info.get("package_version")
            plugin = self._map_plugin(
                pid=plugin_id,
                plugin_info=info,
                market=self._system().local_repo_url(
                    plugin_id,
                    info.get("repo_path"),
                    package_version,
                ),
                installed_apps=installed,
                add_time=0,
                package_version=package_version,
            )
            if plugin:
                plugin.is_local = True
                plugins.append(plugin)
        plugins.sort(key=lambda item: getattr(item, "plugin_order", 0))
        self._logger.info(f"获取到 {len(plugins)} 个本地插件")
        return plugins

    def exists(self, plugin_id: str, version: Optional[str] = None) -> bool:
        """判断插件包和已加载版本是否满足安装前置条件。"""
        if not plugin_id:
            return False
        try:
            instance = self._plugin_instance(plugin_id)
            source_plugin_id = (
                instance.source_plugin_id if instance else plugin_id
            )
            package_name = f"app.plugins.{source_plugin_id.lower()}"
            spec = importlib.util.find_spec(package_name)
            if spec is None or spec.origin is None:
                return False
            local_version = self._plugin_attr(plugin_id, "plugin_version")
            if not local_version and instance:
                local_version = self._plugin_attr(
                    instance.source_plugin_id,
                    "plugin_version",
                )
            if not local_version:
                return False
            if version and not compare_version(local_version, ">=", version):
                self._logger.warning(
                    f"Plugin {plugin_id} version: {local_version} "
                    f"(older than version: {version})"
                )
                return False
            return True
        except Exception as error:
            self._logger.debug(f"获取插件是否在本地包中存在失败，{error}")
            return False

    def _installed_ids(self) -> list[str]:
        """合并物理安装清单和虚拟实例清单并保持各自持久化顺序。"""
        installed = list(
            self._storage().read(SystemConfigKey.UserInstalledPlugins) or []
        )
        for instance_id in self._plugin_instances():
            if instance_id not in installed:
                installed.append(instance_id)
        return installed

    def get_from_market(
        self,
        market: str,
        package_version: Optional[str] = None,
        force: bool = False,
    ) -> list[Plugin]:
        """读取并映射指定插件市场。"""
        return self._market_catalog().load(market, package_version, force)

    async def async_online(
        self,
        force: bool = False,
        progress_callback: Optional[Callable[..., None]] = None,
    ) -> list[Plugin]:
        """异步读取所有兼容代际的在线插件目录。"""
        if not settings.PLUGIN_MARKET:
            if progress_callback:
                progress_callback(value=100, text="未配置插件市场，跳过刷新")
            return []
        markets = [item for item in settings.PLUGIN_MARKET.split(",") if item]
        result = await self._market_catalog().async_collect(
            markets=markets,
            compatible_flags=self._system().compatible_flags(settings.VERSION_FLAG),
            force=force,
            loader=self._async_market_loader,
            progress_callback=progress_callback,
        )
        self._logger.info(f"获取到 {len(result)} 个线上插件")
        return result

    async def async_get_from_market(
        self,
        market: str,
        package_version: Optional[str] = None,
        force: bool = False,
    ) -> list[Plugin]:
        """异步读取并映射指定插件市场。"""
        return await self._market_catalog().async_load(
            market,
            package_version,
            force,
        )

    def merge(self, higher: list[Plugin], base: list[Plugin]) -> list[Plugin]:
        """合并不同代际插件目录并保留市场优先级。"""
        markets = [item for item in settings.PLUGIN_MARKET.split(",") if item]
        return self._market_catalog().merge(higher, base, markets)

    def _safe_state(self, plugin_id: str, plugin: Any) -> bool:
        """读取插件状态，单个插件异常不阻断整个本地目录。"""
        if not plugin or not hasattr(plugin, "get_state"):
            return False
        try:
            return bool(plugin.get_state())
        except Exception as error:
            self._logger.error(f"获取插件 {plugin_id} 状态出错：{error}")
            return False
