"""按 video_id 懒加载 InferenceDeps 并路由 dispatch/prompt_builder。

每个视频的 TreeIndex、TreeEnvironment、SkillRegistry 等重量级对象
只在首次访问时构建并缓存，后续同视频的请求直接复用。
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from loguru import logger

from app.harness.factory import InferenceDeps, build_inference_deps

if TYPE_CHECKING:
    from pathlib import Path

    from app.ports import EmbeddingProvider, OCRProvider
    from core.protocols import LLMProvider, VLMProvider
    from core.types import GeneratedQuestion


class InferenceDepsRouter:
    """按 video_id 懒加载 InferenceDeps 并路由工具调度和 prompt 构建。

    职责:
        1. 维护 question_id → video_id 的映射表（由 prompt_builder 自动注册）。
        2. 按 (video_id, skills_dir, prompts_dir) 三元组缓存 InferenceDeps。
        3. 提供 create_dispatch / create_prompt_builder 工厂方法，
           返回的闭包符合 ToolDispatchFn / PromptBuilderFn Protocol。

    参数:
        store_dir: store 根目录。
        embed_provider: 嵌入端口实例。
        llm: LLM 端口实例。
        vlm: VLM 端口实例。
        ocr: OCR 端口实例（None 不启用）。
        default_prompts_dir: 默认 prompt 文件目录。
        default_skills_dir: 默认 skill 文件目录（None 则不加载 skill）。
        skill_mode: skill 模式（"auto"/"manual"/"none"）。
        verify_vision: observe_frame 是否执行验证轮。
        anchor: view_node 是否启用行号锚模式。
        assemble_mode: 锚模式装配形态。
    """

    def __init__(
        self,
        *,
        store_dir: Path,
        embed_provider: EmbeddingProvider,
        llm: LLMProvider,
        vlm: VLMProvider,
        ocr: OCRProvider | None,
        default_prompts_dir: Path,
        default_skills_dir: Path | None,
        skill_mode: str,
        verify_vision: bool,
        anchor: bool,
        assemble_mode: str,
    ) -> None:
        self._store_dir = store_dir
        self._embed = embed_provider
        self._llm = llm
        self._vlm = vlm
        self._ocr = ocr
        self._default_prompts_dir = default_prompts_dir
        self._default_skills_dir = default_skills_dir
        self._skill_mode = skill_mode
        self._verify_vision = verify_vision
        self._anchor = anchor
        self._assemble_mode = assemble_mode
        self._deps_cache: dict[tuple[str, str, str], InferenceDeps] = {}
        self._qid_to_vid: dict[str, str] = {}

    def create_dispatch(self, *, skills_dir: Path | None = None) -> Any:
        """创建工具调度闭包，按 session_id 路由到对应视频的 InferenceDeps。

        参数:
            skills_dir: skill 文件目录覆盖（None 使用默认值）。

        返回:
            符合 ToolDispatchFn 签名的 async 闭包。
        """
        effective_skills = skills_dir or self._default_skills_dir

        async def _dispatch(
            tool_name: str, args: dict[str, Any], *, context: dict[str, Any]
        ) -> str:
            """按 session_id 查找视频 → 获取缓存 deps → 委托执行。"""
            session_id = context.get("session_id")
            if not session_id or session_id not in self._qid_to_vid:
                raise KeyError(
                    f"未注册的 session_id={session_id!r}，已注册 {len(self._qid_to_vid)} 条映射"
                )
            video_id = self._qid_to_vid[session_id]
            deps = self._ensure_deps(video_id, effective_skills, self._default_prompts_dir)
            return await deps.tool_dispatch_fn(tool_name, args, context=context)

        return _dispatch

    def create_prompt_builder(
        self,
        *,
        skills_dir: Path | None = None,
        prompts_dir: Path | None = None,
    ) -> Any:
        """创建 prompt 构建闭包，自动注册 qid→vid 映射并路由到对应视频的 deps。

        参数:
            skills_dir: skill 文件目录覆盖（None 使用默认值）。
            prompts_dir: prompt 文件目录覆盖（None 使用默认值）。

        返回:
            符合 PromptBuilderFn 签名的闭包。
        """
        effective_skills = skills_dir or self._default_skills_dir
        effective_prompts = prompts_dir or self._default_prompts_dir

        def _builder(qa: GeneratedQuestion) -> tuple[str, str]:
            """注册 qid→vid 映射 → 获取缓存 deps → 委托构建 prompt。"""
            self._qid_to_vid[qa.question_id] = qa.video_id
            deps = self._ensure_deps(qa.video_id, effective_skills, effective_prompts)
            return deps.prompt_builder(qa)

        return _builder

    def _ensure_deps(
        self,
        video_id: str,
        skills_dir: Path | None,
        prompts_dir: Path,
    ) -> InferenceDeps:
        """按 (video_id, skills_dir, prompts_dir) 三元组缓存 InferenceDeps。

        参数:
            video_id: 视频标识。
            skills_dir: skill 文件目录。
            prompts_dir: prompt 文件目录。

        返回:
            缓存命中或新建的 InferenceDeps 实例。
        """
        key = (video_id, str(skills_dir), str(prompts_dir))
        if key not in self._deps_cache:
            self._deps_cache[key] = self._build_deps(video_id, skills_dir, prompts_dir)
            logger.debug("InferenceDeps 已缓存: video_id={}", video_id)
        return self._deps_cache[key]

    def _build_deps(
        self,
        video_id: str,
        skills_dir: Path | None,
        prompts_dir: Path,
    ) -> InferenceDeps:
        """调用 build_inference_deps 构建 InferenceDeps 实例。

        参数:
            video_id: 视频标识。
            skills_dir: skill 文件目录。
            prompts_dir: prompt 文件目录。

        返回:
            新建的 InferenceDeps 实例。
        """
        return build_inference_deps(
            store_dir=self._store_dir,
            video_id=video_id,
            prompts_dir=prompts_dir,
            skills_dir=skills_dir,
            skill_mode=self._skill_mode,
            embed_provider=self._embed,
            llm=self._llm,
            vlm=self._vlm,
            ocr=self._ocr,
            verify_vision=self._verify_vision,
            anchor=self._anchor,
            assemble_mode=self._assemble_mode,
        )

    def clear_cache(self) -> None:
        """清空 deps 缓存和 qid→vid 映射表。"""
        self._deps_cache.clear()
        self._qid_to_vid.clear()
