from __future__ import annotations

import sys
import threading
from typing import Any, Generator, List, Optional, Tuple, Union

from app.foundation.reflection import ObjectUtils
from app.foundation.singleton import Singleton
from app.runtime.capabilities.model import (
    CapabilityLifecycleState,
    CapabilityObservation,
    CapabilitySpec,
)
from app.runtime.capabilities.runtime import CapabilityRuntime
from app.runtime.settings import RuntimeSettingsCompat

settings = RuntimeSettingsCompat()
from app.runtime.events import Event, EventHandlerBinding, eventmanager
from app.runtime.extensions.host_module_adapter import (
    HOST_MODULE_KIND,
    HostModuleAdapter,
    build_host_module_registry,
    capture_host_module_config,
    should_run_host_module,
)
from app.runtime.log import logger
from app.schemas.types import (
    DownloaderType,
    EventType,
    MediaRecognizeType,
    MediaServerType,
    NotificationChannel,
    ModuleType,
    OtherModulesType,
    StorageSchema,
)


class ModuleManager(metaclass=Singleton):
    """以 Capability Runtime 管理宿主模块，并保留旧插件同步查询合同。"""

    SubType = Union[
        DownloaderType,
        MediaServerType,
        NotificationChannel,
        StorageSchema,
        OtherModulesType,
        MediaRecognizeType,
    ]

    def __init__(self) -> None:
        """发现 data-only manifest，并按当前配置激活所需宿主模块。"""
        self._lock = threading.RLock()
        self._lifecycle_lock = threading.RLock()
        self._modules: dict[str, type] = {}
        self._running_modules: dict[str, Any] = {}
        registry = build_host_module_registry()
        self._runtime = CapabilityRuntime(
            registry,
            adapters={HOST_MODULE_KIND: HostModuleAdapter()},
            observer=self._observe_transition,
        )
        # pkgutil 的既有发现顺序按一级包名稳定排列，兼容视图继续保持该顺序。
        self._specs = tuple(
            sorted(self._runtime.list_specs(), key=lambda item: item.source.parent.name)
        )
        eventmanager.register_handler_instance_resolver(
            "modules",
            self.resolve_event_handler_instance,
        )
        eventmanager.add_event_listener(
            EventType.ConfigChanged,
            self.handle_config_changed,
        )
        self.load_modules()

    @staticmethod
    def _observe_transition(observation: CapabilityObservation) -> None:
        """把 Runtime 的稳定转换结果接入现有日志面。"""
        if observation.outcome == "failed":
            logger.error(
                "Host Module %s %s 失败：%s",
                observation.capability_id,
                observation.operation,
                observation.error,
            )
        elif observation.outcome == "succeeded":
            logger.debug(
                "Host Module %s %s 完成，generation=%s，耗时=%.2fms",
                observation.capability_id,
                observation.operation,
                observation.generation,
                observation.duration_ms,
            )

    @staticmethod
    def _event_changed_keys(event: Optional[Event]) -> set[str]:
        """兼容对象和 dict 两种配置事件载荷。"""
        if not event:
            return set()
        event_data = event.event_data
        if isinstance(event_data, dict):
            keys = event_data.get("key", set())
        else:
            keys = getattr(event_data, "key", set())
        if isinstance(keys, str):
            return {keys}
        return {str(key) for key in (keys or set())}

    def _remember_materialized(self, module_id: str, implementation: type) -> type:
        """更新旧 `_modules` 视图，但不改变能力资源生命周期。"""
        with self._lock:
            self._modules[module_id] = implementation
        return implementation

    def _consumer_materialized_class(self, spec: CapabilitySpec) -> Optional[type]:
        """识别插件显式旧导入产生的真实类，不触发新的 Python import。"""
        module_name, symbol_name = spec.entrypoint.split(":", maxsplit=1)
        module = sys.modules.get(module_name)
        namespace = getattr(module, "__dict__", None) if module is not None else None
        if not isinstance(namespace, dict):
            return None
        implementation = namespace.get(symbol_name)
        return implementation if isinstance(implementation, type) else None

    def _refresh_running_projection(self) -> None:
        """从 Runtime 已发布实例重建插件可见的运行模块字典。"""
        running = {
            spec.id: instance
            for spec in self._specs
            if (instance := self._runtime.get_running(spec.id)) is not None
        }
        with self._lock:
            self._running_modules = running

    def resolve_event_handler_instance(
        self,
        owner_class: type,
    ) -> Optional[EventHandlerBinding]:
        """按 canonical class identity 绑定当前 generation，停止态阻断 fallback 构造。"""
        for spec in self._specs:
            with self._lock:
                implementation = self._modules.get(spec.id)
            if implementation is None:
                implementation = self._consumer_materialized_class(spec)
                if implementation is not None:
                    self._remember_materialized(spec.id, implementation)
                    # 同步 Runtime 的物化观测，但不创建或启动实例。
                    self._runtime.snapshot(spec.id)
            if implementation is not owner_class:
                continue
            return EventHandlerBinding(
                instance=self._runtime.get_running(spec.id),
                owner_name=str(spec.metadata["name"]),
            )
        return None

    def _reconcile(
        self,
        *,
        reason: str,
        changed_keys: Optional[set[str]] = None,
        reload_running: bool = False,
    ) -> None:
        """以一次配置快照串行协调需要启动、重载或停止的能力。"""
        with self._lifecycle_lock:
            selected = tuple(
                spec
                for spec in self._specs
                if changed_keys is None or changed_keys.intersection(spec.watch)
            )
            snapshot = capture_host_module_config(selected)
            for spec in selected:
                desired = should_run_host_module(spec, snapshot)
                running = self._runtime.get_running(spec.id)
                try:
                    if desired and running is None:
                        instance = self._runtime.activate(
                            spec.id,
                            reason=reason,
                            retry=True,
                        )
                        self._remember_materialized(spec.id, type(instance))
                    elif desired and running is not None and reload_running:
                        instance = self._runtime.reload(spec.id, reason=reason)
                        self._remember_materialized(spec.id, type(instance))
                    elif not desired and running is not None:
                        self._runtime.stop(spec.id, reason=reason)
                except Exception:
                    # 单能力失败由 Runtime 完整记录；其它无依赖能力继续 reconcile。
                    continue
            self._refresh_running_projection()

    def load_modules(self) -> None:
        """按当前配置启动未运行模块；已运行模块保持当前 generation。"""
        self._reconcile(reason="module_manager_load")

    def handle_config_changed(self, event: Event) -> None:
        """配置变更时仅协调 watch 命中的能力，并保证单一生命周期 writer。"""
        changed_keys = self._event_changed_keys(event)
        if not changed_keys:
            return
        self._reconcile(
            reason="config_changed",
            changed_keys=changed_keys,
            reload_running=True,
        )

    def stop(self) -> None:
        """停止全部运行模块但保留 Runtime，使旧插件可随后再次 load。"""
        logger.info("正在停止所有模块...")
        with self._lifecycle_lock:
            for spec in reversed(self._specs):
                snapshot = self._runtime.snapshot(spec.id)
                if (
                    self._runtime.get_running(spec.id) is None
                    and snapshot.lifecycle is not CapabilityLifecycleState.FAILED
                ):
                    continue
                try:
                    self._runtime.stop(spec.id, reason="module_manager_stop")
                except Exception:
                    continue
            self._refresh_running_projection()
        logger.info("所有模块停止完成")

    def shutdown(self) -> None:
        """进程关闭时不可逆停止 Runtime，阻止并发能力重新发布。"""
        logger.info("正在关闭模块运行时...")
        with self._lifecycle_lock:
            self._runtime.shutdown(reason="application_shutdown")
            self._refresh_running_projection()
        logger.info("模块运行时关闭完成")

    def reload(self) -> None:
        """保留旧插件可观察的 stop、load、ModuleReload 同步顺序。"""
        with self._lifecycle_lock:
            self.stop()
            self.load_modules()
            eventmanager.send_event(etype=EventType.ModuleReload, data={})

    def test(self, modleid: str) -> Tuple[bool, str]:
        """测试已运行模块；未启用模块保持旧合同返回 `(False, "")`。"""
        module = self.get_running_module(modleid)
        if module is None:
            return False, ""
        if hasattr(module, "test") and ObjectUtils.check_method(module.test):
            result = module.test()
            return result if result else (False, "")
        return True, "模块不支持测试"

    @staticmethod
    def check_setting(setting: Optional[tuple]) -> bool:
        """保留旧模块开关的 truthy 与 membership 判定语义。"""
        if not setting:
            return True
        switch, value = setting
        option = getattr(settings, switch)
        if not option:
            return False
        if value is True:
            return True
        return value in option

    def get_running_module(self, module_id: str) -> Any:
        """根据模块 ID 返回已发布的运行实例，不触发物化。"""
        if not module_id or self._runtime.get_spec(module_id) is None:
            return None
        return self._runtime.get_running(module_id)

    def _running_snapshot(self) -> tuple[Any, ...]:
        """直接读取 Runtime 发布视图，转换期间不暴露旧或候选实例。"""
        return tuple(
            instance
            for spec in self._specs
            if (instance := self._runtime.get_running(spec.id)) is not None
        )

    def get_running_modules(self, method: str) -> Generator:
        """返回实现了指定方法的运行模块快照。"""
        for module in self._running_snapshot():
            candidate = getattr(module, method, None)
            if callable(candidate) and ObjectUtils.check_method(candidate):
                yield module

    def get_running_type_modules(self, module_type: ModuleType) -> Generator:
        """返回指定类型的运行模块快照。"""
        for module in self._running_snapshot():
            if module.get_type() == module_type:
                yield module

    def get_running_subtype_module(self, module_subtype: SubType) -> Generator:
        """返回指定子类型的运行模块快照。"""
        for module in self._running_snapshot():
            if module.get_subtype() == module_subtype:
                yield module

    def get_module(self, module_id: str) -> Any:
        """显式物化并返回 canonical 模块类；失败保持旧合同返回 None。"""
        if not module_id or self._runtime.get_spec(module_id) is None:
            return None
        with self._lock:
            implementation = self._modules.get(module_id)
        if implementation is not None:
            return implementation
        try:
            implementation = self._runtime.materialize(
                module_id,
                reason="compat_get_module",
                retry=True,
            )
        except Exception:
            return None
        return self._remember_materialized(module_id, implementation)

    def get_modules(self) -> dict[str, type]:
        """兼容性显式物化全部真实类；单个失败不阻断其它模块。"""
        for spec in self._specs:
            self.get_module(spec.id)
        with self._lock:
            return dict(self._modules)

    def get_module_ids(self) -> List[str]:
        """从 manifest 返回全部模块 ID，不物化实现。"""
        return [spec.id for spec in self._specs]

    def list_specs(self) -> tuple[CapabilitySpec, ...]:
        """返回全部轻量模块声明，包含物化或启动失败的能力。"""
        return self._specs

    def get_specs(self) -> tuple[CapabilitySpec, ...]:
        """兼容内部调用命名，返回与 `list_specs` 相同的声明快照。"""
        return self.list_specs()
