"""AgentSdk —— 二开 SDK 主入口（单例工厂）。

每个 ``app_id`` 在同一进程内只会有一个实例；
``register_*`` 必须在 ``initialize()`` 之前完成。
"""

from __future__ import annotations

import logging
import threading
from typing import Callable, Dict, List, Optional, Type

from .agent_auth_callback import AgentAuthCallback
from .agent_meta import AgentMeta
from .enums import AgentCallbackType
from .mgr.callback_type_mgr import AgentSdkCallbackTypeMgr
from .mgr.task_flow_mgr import AgentSdkTaskFlowMgr
from .passive.asr2_llm import Asr2LlmCallback
from .passive.asr2_tts import Asr2TtsCallback
from .passive.asr_video2_tts import AsrVideo2TtsCallback
from .passive.asr_video2_vlm import AsrVideo2VlmCallback
from .passive.audio2_llm import Audio2LlmCallback
from .passive.audio2_tts import Audio2TtsCallback
from .passive.audio_video2_tts import AudioVideo2TtsCallback
from .passive.audio_video2_vlm import AudioVideo2VlmCallback
from .passive.base import PassiveCallback


_log = logging.getLogger("linksoul_agentsdk")


# 工厂构造 LinkskyClient 的 hook —— 测试时可以替换为 FakeLinkskyClient
LinkskyClientFactory = Callable[["AgentSdk"], "object"]  # 实际类型由 client.linksky_client.LinkskyClient 提供


def _default_linksky_client_factory(agent_sdk: "AgentSdk"):  # pragma: no cover - 透传
    from .client.linksky_client import LinkskyClient
    return LinkskyClient(agent_sdk)


class AgentSdk:
    """与 Java ``com.agibot.aiem.sdk.AgentSdk`` 对齐的公开主类。"""

    AGIBOT_APP_ID_HEADER = "AGIBOT_APP_ID"
    AGIBOT_AUTHORIZATION_HEADER = "AGIBOT_AUTHORIZATION"

    _lock = threading.Lock()
    _app_id_to_instance: Dict[str, "AgentSdk"] = {}

    # ----- 构造 / 工厂 ----------------------------------------------------------

    def __init__(self, url: str, app_id: str, app_key: str, app_secret: str) -> None:
        self._url = url
        self._app_id = app_id
        self._app_key = app_key
        self._app_secret = app_secret

        self._auth_callback: Optional[AgentAuthCallback] = None
        self._type_to_callback: Dict[AgentCallbackType, PassiveCallback] = {}
        self._agent_id_to_callback_type: Dict[str, AgentCallbackType] = {}
        self._agent_id_to_meta: Dict[str, AgentMeta] = {}

        self._released = False
        self._linksky_client = None  # 类型：LinkskyClient
        self._client_factory: LinkskyClientFactory = _default_linksky_client_factory

    @classmethod
    def create(cls, url: str, app_id: str, app_key: str, app_secret: str) -> "AgentSdk":
        with cls._lock:
            inst = cls._app_id_to_instance.get(app_id)
            if inst is None:
                inst = cls(url, app_id, app_key, app_secret)
                cls._app_id_to_instance[app_id] = inst
            return inst

    @classmethod
    def get(cls, app_id: str) -> Optional["AgentSdk"]:
        with cls._lock:
            return cls._app_id_to_instance.get(app_id)

    @classmethod
    def list_app_ids(cls) -> List[str]:
        with cls._lock:
            return list(cls._app_id_to_instance.keys())

    @classmethod
    def _reset_for_test(cls) -> None:
        with cls._lock:
            cls._app_id_to_instance.clear()

    # ----- 测试钩子 -------------------------------------------------------------

    def set_linksky_client_factory(self, factory: LinkskyClientFactory) -> None:
        """允许测试用 FakeLinkskyClient 替换。**必须**在 ``initialize`` 之前调用。"""
        if self._linksky_client is not None:
            raise RuntimeError("linksky client already initialized")
        self._client_factory = factory

    # ----- 基本属性 -------------------------------------------------------------

    @property
    def url(self) -> str:
        return self._url

    @property
    def app_id(self) -> str:
        return self._app_id

    @property
    def app_key(self) -> str:
        return self._app_key

    @property
    def app_secret(self) -> str:
        return self._app_secret

    @property
    def auth_callback(self) -> Optional[AgentAuthCallback]:
        return self._auth_callback

    @property
    def released(self) -> bool:
        return self._released

    @property
    def linksky_client(self):
        return self._linksky_client

    # ----- 鉴权回调 -------------------------------------------------------------

    def register_auth(self, auth_callback: AgentAuthCallback) -> None:
        self._auth_callback = auth_callback

    def on_auth_state(self, code: int, msg: str) -> None:
        if self._auth_callback is not None:
            try:
                self._auth_callback.on_auth_state(self._app_id, code, msg)
            except Exception:  # noqa: BLE001
                _log.exception("auth_callback raised")
        else:
            _log.warning("no auth_callback: app_id=%s code=%s msg=%s", self._app_id, code, msg)

    # ----- 被动回调注册（v1.4.0 公开 8 种）-------------------------------------

    def _register_callback(self, callback: PassiveCallback) -> None:
        AgentSdkCallbackTypeMgr.offer(self._app_id, callback.callback_type)
        self._type_to_callback[callback.callback_type] = callback

    def register_audio2_llm(self, callback: Audio2LlmCallback) -> None:
        self._ensure_type(callback, Audio2LlmCallback, AgentCallbackType.AUDIO_2_LLM)
        self._register_callback(callback)

    def register_audio2_tts(self, callback: Audio2TtsCallback) -> None:
        self._ensure_type(callback, Audio2TtsCallback, AgentCallbackType.AUDIO_2_TTS)
        self._register_callback(callback)

    def register_asr2_llm(self, callback: Asr2LlmCallback) -> None:
        self._ensure_type(callback, Asr2LlmCallback, AgentCallbackType.ASR_2_LLM)
        self._register_callback(callback)

    def register_asr2_tts(self, callback: Asr2TtsCallback) -> None:
        self._ensure_type(callback, Asr2TtsCallback, AgentCallbackType.ASR_2_TTS)
        self._register_callback(callback)

    def register_asr_video2_vlm(self, callback: AsrVideo2VlmCallback) -> None:
        self._ensure_type(callback, AsrVideo2VlmCallback, AgentCallbackType.ASR_VIDEO_2_VLM)
        self._register_callback(callback)

    def register_asr_video2_tts(self, callback: AsrVideo2TtsCallback) -> None:
        self._ensure_type(callback, AsrVideo2TtsCallback, AgentCallbackType.ASR_VIDEO_2_TTS)
        self._register_callback(callback)

    def register_audio_video2_vlm(self, callback: AudioVideo2VlmCallback) -> None:
        self._ensure_type(callback, AudioVideo2VlmCallback, AgentCallbackType.AUDIO_VIDEO_2_VLM)
        self._register_callback(callback)

    def register_audio_video2_tts(self, callback: AudioVideo2TtsCallback) -> None:
        self._ensure_type(callback, AudioVideo2TtsCallback, AgentCallbackType.AUDIO_VIDEO_2_TTS)
        self._register_callback(callback)

    @staticmethod
    def _ensure_type(callback: PassiveCallback, expected_cls: Type[PassiveCallback], expected_type: AgentCallbackType) -> None:
        if not isinstance(callback, expected_cls):
            raise TypeError(f"expected {expected_cls.__name__}, got {type(callback).__name__}")
        # 防御性：检查 callback.callback_type
        if callback.callback_type is not expected_type:
            callback.callback_type = expected_type

    # ----- 回调表查询 ----------------------------------------------------------

    def get_robot_callback(self, callback_type: Optional[AgentCallbackType]) -> Optional[PassiveCallback]:
        if callback_type is None:
            return None
        return self._type_to_callback.get(callback_type)

    def bind_agent_callback_type(self, agent_id: str, callback_type: AgentCallbackType) -> None:
        self._agent_id_to_callback_type[agent_id] = callback_type
        _log.info("bind_agent_callback_type => %s %s", agent_id, callback_type.value)

    def unbind_agent_callback_type(self, agent_id: str) -> None:
        removed = self._agent_id_to_callback_type.pop(agent_id, None)
        if removed is not None:
            _log.info("unbind_agent_callback_type => %s %s", agent_id, removed.value)

    def get_agent_callback_type(self, agent_id: str) -> Optional[AgentCallbackType]:
        return self._agent_id_to_callback_type.get(agent_id)

    def bind_agent_meta(self, agent_id: str, agent_meta: AgentMeta) -> None:
        self._agent_id_to_meta[agent_id] = agent_meta

    def unbind_agent_meta(self, agent_id: str) -> None:
        self._agent_id_to_meta.pop(agent_id, None)

    def get_agent_meta(self, agent_id: str) -> Optional[AgentMeta]:
        return self._agent_id_to_meta.get(agent_id)

    # ----- 主动任务流 ----------------------------------------------------------

    def register_task_flow(self, task_flow_request) -> None:
        if self._linksky_client is None:
            raise RuntimeError("AgentSdk not initialized — call initialize() first")
        task_flow_request.set_linksky_client(self._linksky_client)
        AgentSdkTaskFlowMgr.offer_request(task_flow_request.flow_id, task_flow_request)

    def unregister_task_flow(self, flow_id: str) -> None:
        AgentSdkTaskFlowMgr.remove_request(flow_id)

    # ----- 生命周期 -----------------------------------------------------------

    def initialize(self) -> None:
        if self._linksky_client is None:
            self._linksky_client = self._client_factory(self)
        self._linksky_client.connect()

    def release(self) -> None:
        if self._released:
            return
        self._released = True
        if self._linksky_client is not None:
            try:
                self._linksky_client.stop()
            except Exception:  # noqa: BLE001
                _log.exception("linksky_client.stop raised")
        AgentSdkCallbackTypeMgr.remove(self._app_id)
        self._type_to_callback.clear()
        self._agent_id_to_callback_type.clear()
        self._agent_id_to_meta.clear()
        with self._lock:
            self._app_id_to_instance.pop(self._app_id, None)
