"""Managed Resource 的声明发现与 Capability Runtime 适配器。"""

from __future__ import annotations

import asyncio
import importlib
import inspect
from pathlib import Path
from typing import Any, Iterable

from app.runtime.capabilities.errors import CapabilityAdapterContractError
from app.runtime.capabilities.model import (
    ActivationPolicy,
    AdapterExecutionMode,
    CapabilitySpec,
)
from app.runtime.capabilities.registry import CapabilityRegistry
from app.runtime.managed_resources import (
    MANAGED_RESOURCE_ASYNC_KIND,
    MANAGED_RESOURCE_SYNC_KIND,
)


_DEFAULT_RESOURCE_ROOT = Path(__file__).resolve().parents[2] / "adapters"
_RESOURCE_KINDS = {MANAGED_RESOURCE_SYNC_KIND, MANAGED_RESOURCE_ASYNC_KIND}


def _load_entrypoint(spec: CapabilitySpec) -> Any:
    """解析声明中的 canonical 实现对象，不创建资源实例。"""
    module_name, symbol_name = spec.entrypoint.split(":", maxsplit=1)
    module = importlib.import_module(module_name)
    try:
        return getattr(module, symbol_name)
    except AttributeError as error:
        raise CapabilityAdapterContractError(
            f"{spec.entrypoint} 未公开 Managed Resource 实现"
        ) from error


def _create_candidate(spec: CapabilitySpec, implementation: Any) -> Any:
    """通过零参数工厂创建资源候选，实例在 start 成功前不可见。"""
    if not callable(implementation):
        raise CapabilityAdapterContractError(
            f"{spec.entrypoint} 不是可调用的 Managed Resource 工厂"
        )
    candidate = implementation()
    if candidate is None or inspect.isawaitable(candidate):
        close = getattr(candidate, "close", None)
        if callable(close):
            close()
        raise CapabilityAdapterContractError(
            f"{spec.entrypoint} 必须同步返回资源候选"
        )
    return candidate


def _resource_method(spec: CapabilitySpec, candidate: Any, name: str) -> Any:
    """读取必需生命周期方法并生成稳定合同错误。"""
    callback = getattr(candidate, name, None)
    if not callable(callback):
        raise CapabilityAdapterContractError(
            f"{spec.entrypoint} 的资源候选缺少 {name}()"
        )
    return callback


class SyncManagedResourceAdapter:
    """把同步 start/stop 资源接入 Capability Runtime。"""

    execution_mode = AdapterExecutionMode.SYNC

    @staticmethod
    def materialize(spec: CapabilitySpec) -> Any:
        """解析资源工厂。"""
        return _load_entrypoint(spec)

    @staticmethod
    def create(
        spec: CapabilitySpec,
        implementation: Any,
        _generation: int,
        _previous: Any = None,
    ) -> Any:
        """创建尚未发布的同步资源候选。"""
        return _create_candidate(spec, implementation)

    @staticmethod
    def start(spec: CapabilitySpec, candidate: Any, _generation: int) -> None:
        """启动同步候选；同步 kind 不接受 awaitable 返回值。"""
        result = _resource_method(spec, candidate, "start")()
        if inspect.isawaitable(result):
            close = getattr(result, "close", None)
            if callable(close):
                close()
            raise CapabilityAdapterContractError(
                f"{spec.entrypoint}.start() 返回 awaitable，与同步 kind 不匹配"
            )

    @staticmethod
    def stop(spec: CapabilitySpec, instance: Any, _generation: int) -> None:
        """停止同步资源，异常交由 Runtime 保留资源所有权并支持重试。"""
        result = _resource_method(spec, instance, "stop")()
        if inspect.isawaitable(result):
            close = getattr(result, "close", None)
            if callable(close):
                close()
            raise CapabilityAdapterContractError(
                f"{spec.entrypoint}.stop() 返回 awaitable，与同步 kind 不匹配"
            )

    @staticmethod
    def cleanup(
        spec: CapabilitySpec,
        candidate: Any,
        generation: int,
        _error: BaseException,
    ) -> None:
        """启动失败时按同一 stop 合同清理尚未发布的候选。"""
        SyncManagedResourceAdapter.stop(spec, candidate, generation)


class AsyncManagedResourceAdapter:
    """把异步 start/stop 资源接入 Capability Runtime。"""

    execution_mode = AdapterExecutionMode.ASYNC

    @staticmethod
    async def materialize(spec: CapabilitySpec) -> Any:
        """在线程中解析资源工厂，避免第三方导入阻塞事件循环。"""
        return await asyncio.to_thread(_load_entrypoint, spec)

    @staticmethod
    async def create(
        spec: CapabilitySpec,
        implementation: Any,
        _generation: int,
        _previous: Any = None,
    ) -> Any:
        """创建尚未发布的异步资源候选。"""
        return _create_candidate(spec, implementation)

    @staticmethod
    async def start(spec: CapabilitySpec, candidate: Any, _generation: int) -> None:
        """等待异步候选完成启动。"""
        result = _resource_method(spec, candidate, "start")()
        if not inspect.isawaitable(result):
            raise CapabilityAdapterContractError(
                f"{spec.entrypoint}.start() 必须返回 awaitable"
            )
        await result

    @staticmethod
    async def stop(spec: CapabilitySpec, instance: Any, _generation: int) -> None:
        """等待异步资源完成停止。"""
        result = _resource_method(spec, instance, "stop")()
        if not inspect.isawaitable(result):
            raise CapabilityAdapterContractError(
                f"{spec.entrypoint}.stop() 必须返回 awaitable"
            )
        await result

    @staticmethod
    async def cleanup(
        spec: CapabilitySpec,
        candidate: Any,
        generation: int,
        _error: BaseException,
    ) -> None:
        """启动失败时等待同一 stop 合同清理候选。"""
        await AsyncManagedResourceAdapter.stop(spec, candidate, generation)


def _validate_registry(registry: CapabilityRegistry) -> None:
    """固定类别级声明合同，资源只能由显式首用触发。"""
    for spec in registry.list_specs():
        if set(spec.metadata) != {"name"}:
            raise ValueError(f"{spec.source}: Managed Resource metadata 只能包含 name")
        if spec.activation is not ActivationPolicy.ON_FIRST_USE:
            raise ValueError(f"{spec.source}: Managed Resource 必须使用 on_first_use")
        if spec.selector is not None or spec.watch:
            raise ValueError(f"{spec.source}: Managed Resource 不接受配置 selector 或 watch")


def build_managed_resource_registry(
    roots: Iterable[Path | str] | None = None,
) -> CapabilityRegistry:
    """从 data-only manifest 构建不导入资源实现的注册表。"""
    registry = CapabilityRegistry.discover(
        tuple(roots) if roots is not None else (_DEFAULT_RESOURCE_ROOT,),
        kinds=_RESOURCE_KINDS,
        selector_schemas={},
    )
    _validate_registry(registry)
    return registry
