from __future__ import annotations

import importlib
from dataclasses import dataclass
from pathlib import Path
from types import MappingProxyType
from typing import Any, Mapping

from app.runtime.capabilities.model import (
    ActivationPolicy,
    AdapterExecutionMode,
    CapabilitySpec,
    SelectorSchema,
)
from app.runtime.capabilities.registry import CapabilityRegistry
from app.runtime.settings import RuntimeSettingsCompat

settings = RuntimeSettingsCompat()
from app.runtime.extensions.service_config import ServiceConfigHelper
from app.schemas.types import (
    DownloaderType,
    MediaRecognizeType,
    MediaServerType,
    NotificationChannel,
    ModuleType,
    OtherModulesType,
    StorageSchema,
    SystemConfigKey,
)


HOST_MODULE_KIND = "host_module"
_SETTING_SELECTOR = "setting_truthy"
_SERVICE_SELECTOR = "system_config_item"
_MODULE_ROOT = Path(__file__).resolve().parents[2] / "modules"
_SERVICE_CONFIG_GETTERS = MappingProxyType({
    SystemConfigKey.Downloaders.value: ServiceConfigHelper.get_downloader_configs,
    SystemConfigKey.MediaServers.value: ServiceConfigHelper.get_mediaserver_configs,
    SystemConfigKey.Notifications.value: ServiceConfigHelper.get_notification_configs,
})
_SUBTYPE_NAMES = frozenset(
    item.name
    for enum_type in (
        DownloaderType,
        MediaServerType,
        NotificationChannel,
        StorageSchema,
        OtherModulesType,
        MediaRecognizeType,
    )
    for item in enum_type
)


@dataclass(frozen=True, slots=True)
class HostModuleConfigSnapshot:
    """一次 reconcile 使用的不可变配置视图，避免每个能力重复查询配置。"""

    settings: Mapping[str, Any]
    services: Mapping[str, tuple[Any, ...]]


def _validate_setting_selector(config: Mapping[str, Any]) -> None:
    """限制 setting selector 只能读取已声明的应用设置。"""
    key = config["key"]
    if not isinstance(key, str) or not key or not hasattr(settings, key):
        raise ValueError(f"未知应用设置：{key!r}")


def _validate_service_selector(config: Mapping[str, Any]) -> None:
    """限制服务 selector 使用经过 Schema 校验的三个宿主服务配置。"""
    key = config["key"]
    if key not in _SERVICE_CONFIG_GETTERS:
        raise ValueError(f"不支持的服务配置：{key!r}")
    if config["match_field"] != "type":
        raise ValueError("服务 selector 的 match_field 必须是 type")
    if config["enabled_field"] != "enabled":
        raise ValueError("服务 selector 的 enabled_field 必须是 enabled")
    match_value = config["match_value"]
    if not isinstance(match_value, str) or not match_value:
        raise ValueError("服务 selector 的 match_value 必须是非空字符串")


HOST_MODULE_SELECTOR_SCHEMAS = MappingProxyType({
    _SETTING_SELECTOR: SelectorSchema(
        required_fields=frozenset({"key"}),
        validator=_validate_setting_selector,
    ),
    _SERVICE_SELECTOR: SelectorSchema(
        required_fields=frozenset({
            "key",
            "match_field",
            "match_value",
            "enabled_field",
        }),
        validator=_validate_service_selector,
    ),
})


def _validate_manifest_inventory(registry: CapabilityRegistry) -> None:
    """校验一级模块包与 manifest 一一对应，并固定宿主声明合同。"""
    module_packages = {
        child.name
        for child in _MODULE_ROOT.iterdir()
        if child.is_dir()
        and not child.name.startswith("_")
        and (child / "__init__.py").is_file()
    }
    specs = registry.list_specs()
    manifest_packages = {spec.source.parent.name for spec in specs}
    if module_packages != manifest_packages:
        missing = sorted(module_packages - manifest_packages)
        unknown = sorted(manifest_packages - module_packages)
        raise ValueError(
            f"Host Module manifest inventory 不一致：missing={missing} unknown={unknown}"
        )

    allowed_metadata = {"name", "type", "subtype", "priority"}
    module_type_values = {item.value for item in ModuleType}
    for spec in specs:
        if spec.source.parent.parent != _MODULE_ROOT:
            raise ValueError(f"Host Module manifest 必须位于一级模块包：{spec.source}")
        module_name, symbol_name = spec.entrypoint.split(":", maxsplit=1)
        expected_module = f"app.modules.{spec.source.parent.name}"
        if module_name != expected_module or symbol_name != spec.id:
            raise ValueError(
                f"{spec.source}: entrypoint 必须指向同包且类名等于 capability id"
            )
        if set(spec.metadata) != allowed_metadata:
            raise ValueError(
                f"{spec.source}: metadata 字段必须是 {sorted(allowed_metadata)}"
            )
        if spec.metadata["type"] not in module_type_values:
            raise ValueError(f"{spec.source}: 非法 metadata.type={spec.metadata['type']!r}")
        if spec.metadata["subtype"] not in _SUBTYPE_NAMES:
            raise ValueError(
                f"{spec.source}: 非法 metadata.subtype={spec.metadata['subtype']!r}"
            )
        priority = spec.metadata["priority"]
        if isinstance(priority, bool) or not isinstance(priority, int):
            raise ValueError(f"{spec.source}: metadata.priority 必须是整数")
        if spec.activation is ActivationPolicy.WHEN_CONFIGURED:
            selector_key = str(spec.selector.config["key"])
            if selector_key not in spec.watch:
                raise ValueError(
                    f"{spec.source}: activation.watch 必须包含 selector 配置键"
                )


def build_host_module_registry() -> CapabilityRegistry:
    """从现有物理模块包构建 import-free Host Module Registry。"""
    registry = CapabilityRegistry.discover(
        (_MODULE_ROOT,),
        kinds={HOST_MODULE_KIND},
        selector_schemas=HOST_MODULE_SELECTOR_SCHEMAS,
    )
    _validate_manifest_inventory(registry)
    return registry


def capture_host_module_config(
    specs: tuple[CapabilitySpec, ...],
) -> HostModuleConfigSnapshot:
    """对本轮涉及的设置和服务配置各读取一次并冻结容器。"""
    setting_keys: set[str] = set()
    service_keys: set[str] = set()
    for spec in specs:
        selector = spec.selector
        if selector is None:
            continue
        key = str(selector.config["key"])
        if selector.kind == _SETTING_SELECTOR:
            setting_keys.add(key)
        elif selector.kind == _SERVICE_SELECTOR:
            service_keys.add(key)

    setting_values = {
        key: getattr(settings, key)
        for key in sorted(setting_keys)
    }
    service_values = {
        key: tuple(_SERVICE_CONFIG_GETTERS[key]())
        for key in sorted(service_keys)
    }
    return HostModuleConfigSnapshot(
        settings=MappingProxyType(setting_values),
        services=MappingProxyType(service_values),
    )


def should_run_host_module(
    spec: CapabilitySpec,
    snapshot: HostModuleConfigSnapshot,
) -> bool:
    """依据有限 selector 语法判断能力是否应拥有运行资源。"""
    if spec.activation is ActivationPolicy.BOOTSTRAP:
        return True
    if spec.activation is ActivationPolicy.ON_FIRST_USE:
        return False
    selector = spec.selector
    if selector is None:
        return False
    if selector.kind == _SETTING_SELECTOR:
        return bool(snapshot.settings[selector.config["key"]])
    if selector.kind == _SERVICE_SELECTOR:
        config = selector.config
        return any(
            getattr(item, config["match_field"]) == config["match_value"]
            and bool(getattr(item, config["enabled_field"]))
            for item in snapshot.services[config["key"]]
        )
    raise ValueError(f"未支持的 Host Module selector：{selector.kind}")


class HostModuleAdapter:
    """把现有模块类接入 Capability Runtime，保持类路径与对象 identity 不变。"""

    execution_mode = AdapterExecutionMode.SYNC

    @staticmethod
    def materialize(spec: CapabilitySpec) -> type:
        """按 manifest entrypoint 导入并返回原始模块类。"""
        module_name, symbol_name = spec.entrypoint.split(":", maxsplit=1)
        implementation = getattr(importlib.import_module(module_name), symbol_name)
        if not isinstance(implementation, type):
            raise TypeError(f"{spec.entrypoint} 不是模块类")
        return implementation

    @staticmethod
    def create(
        spec: CapabilitySpec,
        implementation: type,
        generation: int,
        previous: Any = None,
    ) -> Any:
        """首次创建实例；配置重载继续使用原实例以保留既有模块语义。"""
        del spec, generation
        return previous if previous is not None else implementation()

    @staticmethod
    def start(spec: CapabilitySpec, candidate: Any, generation: int) -> None:
        """初始化候选实例拥有的连接、线程或客户端资源。"""
        del spec, generation
        candidate.init_module()

    @staticmethod
    def stop(spec: CapabilitySpec, instance: Any, generation: int) -> None:
        """停止实例拥有的资源；Runtime 会先撤销其运行态可见性。"""
        del spec, generation
        instance.stop()

    @staticmethod
    def cleanup(
        spec: CapabilitySpec,
        candidate: Any,
        generation: int,
        error: BaseException,
    ) -> None:
        """启动失败后尽力回收候选实例已创建的部分资源。"""
        del spec, generation, error
        candidate.stop()
