"""Agent canonical entrypoint 的 Capability Runtime 适配器。"""

from __future__ import annotations

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

from app.agent.capabilities import AGENT_ENTRYPOINT_KIND, AGENT_SERVICE_KIND
from app.runtime.capabilities.errors import CapabilityAdapterContractError
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()


_DEFAULT_CAPABILITY_ROOT = Path(__file__).resolve().parent
_SETTING_SELECTOR = "setting_truthy"


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


AGENT_SELECTOR_SCHEMAS = {
    _SETTING_SELECTOR: SelectorSchema(
        required_fields=frozenset({"key"}),
        validator=_validate_setting_selector,
    )
}


def _load_entrypoint(spec: CapabilitySpec) -> Any:
    """按 manifest 解析 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} 未公开 Agent entrypoint"
        ) from error


def _lifecycle_method(spec: CapabilitySpec, candidate: Any, name: str) -> Any:
    """读取 Agent Service 必需的异步生命周期方法。"""
    callback = getattr(candidate, name, None)
    if not callable(callback):
        raise CapabilityAdapterContractError(
            f"{spec.entrypoint} 的 Agent Service 缺少 {name}()"
        )
    return callback


class AgentEntrypointAdapter:
    """把 canonical Python 符号作为无资源副作用的同步能力发布。"""

    execution_mode = AdapterExecutionMode.SYNC

    @staticmethod
    def materialize(spec: CapabilitySpec) -> Any:
        """按 manifest entrypoint 导入 canonical 符号。"""
        return _load_entrypoint(spec)

    @staticmethod
    def create(
        _spec: CapabilitySpec,
        implementation: Any,
        _generation: int,
        _previous: Any = None,
    ) -> Any:
        """发布 canonical 符号本身，不创建第二份业务对象。"""
        return implementation

    @staticmethod
    def start(
        _spec: CapabilitySpec,
        _candidate: Any,
        _generation: int,
    ) -> None:
        """entrypoint 不拥有业务资源，初始化由独立 service 能力负责。"""

    @staticmethod
    def stop(
        _spec: CapabilitySpec,
        _instance: Any,
        _generation: int,
    ) -> None:
        """撤销入口可见性；业务资源由独立 service 能力关闭。"""

    @staticmethod
    def cleanup(
        _spec: CapabilitySpec,
        _candidate: Any,
        _generation: int,
        _error: BaseException,
    ) -> None:
        """entrypoint 启动无副作用，因此失败候选无需额外释放。"""


class AgentServiceAdapter:
    """把具备 initialize/close 的 canonical 对象接入异步资源生命周期。"""

    execution_mode = AdapterExecutionMode.ASYNC

    @staticmethod
    async def materialize(spec: CapabilitySpec) -> Any:
        """在线程中导入 canonical service，避免阻塞应用事件循环。"""
        return await asyncio.to_thread(_load_entrypoint, spec)

    @staticmethod
    async def create(
        _spec: CapabilitySpec,
        implementation: Any,
        _generation: int,
        _previous: Any = None,
    ) -> Any:
        """复用 canonical service，不复制其内部队列和后台任务所有权。"""
        return implementation

    @staticmethod
    async def start(
        spec: CapabilitySpec,
        candidate: Any,
        _generation: int,
    ) -> None:
        """等待 service 在当前应用事件循环完成初始化。"""
        result = _lifecycle_method(spec, candidate, "initialize")()
        if not inspect.isawaitable(result):
            raise CapabilityAdapterContractError(
                f"{spec.entrypoint}.initialize() 必须返回 awaitable"
            )
        await result

    @staticmethod
    async def stop(
        spec: CapabilitySpec,
        instance: Any,
        _generation: int,
    ) -> None:
        """等待 service 停止后台任务并释放其资源。"""
        result = _lifecycle_method(spec, instance, "close")()
        if not inspect.isawaitable(result):
            raise CapabilityAdapterContractError(
                f"{spec.entrypoint}.close() 必须返回 awaitable"
            )
        converged = await result
        if converged is False:
            raise CapabilityAdapterContractError(
                f"{spec.entrypoint}.close() 返回未收敛，保留 service owner"
            )

    @staticmethod
    async def cleanup(
        spec: CapabilitySpec,
        candidate: Any,
        generation: int,
        _error: BaseException,
    ) -> None:
        """初始化失败或关闭竞态时按相同 close 合同释放部分资源。"""
        await AgentServiceAdapter.stop(spec, candidate, generation)


def _validate_registry(registry: CapabilityRegistry) -> None:
    """固定 entrypoint 物化轴与 service 资源轴的声明合同。"""
    for spec in registry.list_specs():
        if set(spec.metadata) != {"name"}:
            raise ValueError(f"{spec.source}: Agent Capability metadata 只能包含 name")
        if spec.kind == AGENT_ENTRYPOINT_KIND:
            if spec.activation is not ActivationPolicy.ON_FIRST_USE:
                raise ValueError(
                    f"{spec.source}: Agent entrypoint 必须使用 on_first_use"
                )
            if spec.selector is not None or spec.watch:
                raise ValueError(
                    f"{spec.source}: Agent entrypoint 不接受 selector 或 watch"
                )
            continue
        if spec.activation is not ActivationPolicy.WHEN_CONFIGURED:
            raise ValueError(f"{spec.source}: Agent Service 必须使用 when_configured")
        selector = spec.selector
        if selector is None or selector.kind != _SETTING_SELECTOR:
            raise ValueError(f"{spec.source}: Agent Service 必须声明 setting_truthy")
        selector_key = str(selector.config["key"])
        if spec.watch != (selector_key,):
            raise ValueError(
                f"{spec.source}: Agent Service watch 必须只包含 selector key"
            )


def build_agent_capability_registry(
    roots: Iterable[Path | str] | None = None,
) -> CapabilityRegistry:
    """发现 data-only Agent manifests，不导入编排器、Provider 或工具实现。"""
    registry = CapabilityRegistry.discover(
        tuple(roots) if roots is not None else (_DEFAULT_CAPABILITY_ROOT,),
        kinds={AGENT_ENTRYPOINT_KIND, AGENT_SERVICE_KIND},
        selector_schemas=AGENT_SELECTOR_SCHEMAS,
    )
    _validate_registry(registry)
    return registry


def should_run_agent_service(spec: CapabilitySpec) -> bool:
    """依据 manifest selector 判断 service 是否应拥有运行实例。"""
    selector = spec.selector
    if (
        spec.kind != AGENT_SERVICE_KIND
        or selector is None
        or selector.kind != _SETTING_SELECTOR
    ):
        raise ValueError(f"{spec.source}: 不是可协调的 Agent Service 声明")
    return bool(getattr(settings, selector.config["key"]))
