"""被动与主动事件的具体处理函数。

每个函数职责单一——从 inbound JSON 里把字段抽出来，路由到用户注册的回调实例 / TaskFlowRequest。
所有 ``handle_*`` 都不直接做 I/O，便于在单测里直接调用。
"""

from __future__ import annotations

import base64
import logging
from typing import Any, Callable, Dict, Optional, TYPE_CHECKING

from ..agent_meta import AgentMeta
from ..agent_param import AgentParam
from ..enums import AgentCallbackType, RobotState
from ..mgr.agent_thread_mgr import AgentThreadMgr
from ..mgr.response_mgr import AgentSdkResponseMgr
from ..mgr.task_flow_mgr import AgentSdkTaskFlowMgr
from ..passive.asr2_llm import Asr2LlmCallback, Asr2LlmResponse
from ..passive.asr2_tts import Asr2TtsCallback, Asr2TtsResponse
from ..passive.asr_video2_tts import AsrVideo2TtsCallback, AsrVideo2TtsResponse
from ..passive.asr_video2_vlm import AsrVideo2VlmCallback, AsrVideo2VlmResponse
from ..passive.audio2_llm import Audio2LlmCallback, Audio2LlmResponse
from ..passive.audio2_tts import Audio2TtsCallback, Audio2TtsResponse
from ..passive.audio_video2_tts import AudioVideo2TtsCallback, AudioVideo2TtsResponse
from ..passive.audio_video2_vlm import AudioVideo2VlmCallback, AudioVideo2VlmResponse
from ..passive.base import GreetResponse, PassiveCallback

if TYPE_CHECKING:
    from ..agent_sdk import AgentSdk
    from .linksky_client import LinkskyClient


_log = logging.getLogger("linksoul_agentsdk.handlers")


# ---------------------------------------------------------------------------
# 通用工具
# ---------------------------------------------------------------------------


def _decode_b64_bytes(msg: Dict[str, Any], key: str) -> Optional[bytes]:
    raw = msg.get(key)
    if raw is None:
        return None
    if isinstance(raw, (bytes, bytearray)):
        return bytes(raw)
    if isinstance(raw, str):
        try:
            return base64.b64decode(raw)
        except Exception:
            _log.warning("decode base64 %s failed", key)
            return None
    return None


def _make_param(msg: Dict[str, Any]) -> AgentParam:
    """从 inbound msg 中抽出 ``param`` 字段（如果有）；否则给个空 AgentParam。"""
    raw = msg.get("param")
    if isinstance(raw, dict):
        return AgentParam(raw)
    return AgentParam()


# ---------------------------------------------------------------------------
# Robot 上下线 / 元数据
# ---------------------------------------------------------------------------


def handle_robot_state_sync(
    agent_sdk: "AgentSdk",
    linksky_client: "LinkskyClient",
    agent_id: str,
    msg: Dict[str, Any],
) -> None:
    state_str = msg.get("state")
    state = RobotState.of(state_str)
    callback_type_str = msg.get("callbackType")
    callback_type = AgentCallbackType.of(callback_type_str)
    callback = agent_sdk.get_robot_callback(callback_type)

    if state is RobotState.ONLINE:
        meta_raw = msg.get("agentMeta") or {}
        agent_meta = AgentMeta(meta_raw if isinstance(meta_raw, dict) else {})
        agent_sdk.bind_agent_callback_type(agent_id, callback_type)
        agent_sdk.bind_agent_meta(agent_id, agent_meta)
        if callback is not None:
            try:
                callback.on_robot_online(agent_id, agent_meta)
            except Exception:  # noqa: BLE001
                _log.exception("on_robot_online failed agent_id=%s", agent_id)
    elif state is RobotState.OFFLINE:
        if callback is not None:
            try:
                callback.on_robot_offline(agent_id)
            except Exception:  # noqa: BLE001
                _log.exception("on_robot_offline failed agent_id=%s", agent_id)
        agent_sdk.unbind_agent_callback_type(agent_id)
        agent_sdk.unbind_agent_meta(agent_id)
        AgentThreadMgr.unbind(agent_id)


# ---------------------------------------------------------------------------
# Audio 三段式（Audio2Llm / Audio2Tts / AudioVideo2Vlm / AudioVideo2Tts）
# ---------------------------------------------------------------------------


def _build_audio_response(
    callback: PassiveCallback,
    linksky_client: "LinkskyClient",
    agent_id: str,
    event_id: str,
    robot_cid: str,
):
    """根据 callback 类型构造对应 Response 并放入 mgr 缓存。"""
    if isinstance(callback, Audio2LlmCallback):
        resp = Audio2LlmResponse(linksky_client, agent_id, robot_cid)
    elif isinstance(callback, Audio2TtsCallback):
        resp = Audio2TtsResponse(linksky_client, agent_id, robot_cid)
    elif isinstance(callback, AudioVideo2VlmCallback):
        resp = AudioVideo2VlmResponse(linksky_client, agent_id, robot_cid)
    elif isinstance(callback, AudioVideo2TtsCallback):
        resp = AudioVideo2TtsResponse(linksky_client, agent_id, robot_cid)
    else:
        return None
    AgentSdkResponseMgr.offer(event_id, resp)
    return resp


def handle_audio_request_start(
    agent_sdk: "AgentSdk",
    linksky_client: "LinkskyClient",
    agent_id: str,
    robot_cid: str,
    event_id: str,
    msg: Dict[str, Any],
) -> None:
    callback_type = agent_sdk.get_agent_callback_type(agent_id)
    if callback_type is None:
        return
    callback = agent_sdk.get_robot_callback(callback_type)
    if callback is None:
        return

    response = _build_audio_response(callback, linksky_client, agent_id, event_id, robot_cid)
    if response is None:
        return

    param = _make_param(msg)
    if isinstance(callback, Audio2TtsCallback):
        item_id = msg.get("itemId") or ""
        callback.on_request(agent_id, event_id, item_id, 0, None, param, response)
    elif isinstance(callback, AudioVideo2TtsCallback):
        callback.on_request(agent_id, event_id, 0, None, param, response)
    elif isinstance(callback, AudioVideo2VlmCallback):
        callback.on_request(agent_id, event_id, 0, None, param, response)
    elif isinstance(callback, Audio2LlmCallback):
        callback.on_request(agent_id, event_id, 0, None, param, response)


def _dispatch_audio_request(
    callback: PassiveCallback,
    response,
    agent_id: str,
    event_id: str,
    item_id: str,
    flag: int,
    buf: Optional[bytes],
    param: AgentParam,
) -> None:
    if isinstance(callback, Audio2TtsCallback):
        callback.on_request(agent_id, event_id, item_id, flag, buf, param, response)
    elif isinstance(callback, AudioVideo2TtsCallback):
        callback.on_request(agent_id, event_id, flag, buf, param, response)
    elif isinstance(callback, AudioVideo2VlmCallback):
        callback.on_request(agent_id, event_id, flag, buf, param, response)
    elif isinstance(callback, Audio2LlmCallback):
        callback.on_request(agent_id, event_id, flag, buf, param, response)


def handle_audio_request_append(
    agent_sdk: "AgentSdk",
    agent_id: str,
    event_id: str,
    audio: Optional[bytes],
    msg: Dict[str, Any],
) -> None:
    callback_type = agent_sdk.get_agent_callback_type(agent_id)
    if callback_type is None:
        return
    callback = agent_sdk.get_robot_callback(callback_type)
    if callback is None:
        return
    response = AgentSdkResponseMgr.get(event_id)
    if response is None:
        return
    if audio is None:
        audio = _decode_b64_bytes(msg, "audio")
    item_id = msg.get("itemId") or ""
    _dispatch_audio_request(callback, response, agent_id, event_id, item_id, 1, audio, _make_param(msg))


def handle_audio_request_commit(
    agent_sdk: "AgentSdk",
    linksky_client: "LinkskyClient",
    agent_id: str,
    event_id: str,
    msg: Dict[str, Any],
) -> None:
    callback_type = agent_sdk.get_agent_callback_type(agent_id)
    if callback_type is None:
        return
    callback = agent_sdk.get_robot_callback(callback_type)
    if callback is None:
        return
    response = AgentSdkResponseMgr.get(event_id)
    if response is None:
        return
    item_id = msg.get("itemId") or ""
    _dispatch_audio_request(callback, response, agent_id, event_id, item_id, 2, None, _make_param(msg))


# ---------------------------------------------------------------------------
# AsrVideo*：先 ASR 文本 / 后 H264 + image
# ---------------------------------------------------------------------------


def _build_asr_video_response(
    callback: PassiveCallback,
    linksky_client: "LinkskyClient",
    agent_id: str,
    event_id: str,
    robot_cid: str,
):
    if isinstance(callback, AsrVideo2VlmCallback):
        resp = AsrVideo2VlmResponse(linksky_client, agent_id, robot_cid)
    elif isinstance(callback, AsrVideo2TtsCallback):
        resp = AsrVideo2TtsResponse(linksky_client, agent_id, robot_cid)
    else:
        return None
    AgentSdkResponseMgr.offer(event_id, resp)
    return resp


def _dispatch_asr_video_request(
    callback: PassiveCallback,
    response,
    agent_id: str,
    event_id: str,
    flag: int,
    text: Optional[str],
    buf: Optional[bytes],
    param: AgentParam,
) -> None:
    if isinstance(callback, AsrVideo2VlmCallback):
        callback.on_request(agent_id, event_id, flag, text, buf, param, response)
    elif isinstance(callback, AsrVideo2TtsCallback):
        callback.on_request(agent_id, event_id, flag, text, buf, param, response)


def handle_asr_request_text(
    agent_sdk: "AgentSdk",
    linksky_client: "LinkskyClient",
    agent_id: str,
    robot_cid: str,
    event_id: str,
    msg: Dict[str, Any],
) -> None:
    callback_type = agent_sdk.get_agent_callback_type(agent_id)
    if callback_type is None:
        return
    callback = agent_sdk.get_robot_callback(callback_type)
    if callback is None:
        return

    text = msg.get("text") or ""
    param = _make_param(msg)

    if isinstance(callback, (Asr2LlmCallback, Asr2TtsCallback)):
        # 单步式 — 直接拿到 final text
        if isinstance(callback, Asr2LlmCallback):
            response = Asr2LlmResponse(linksky_client, agent_id, robot_cid)
            AgentSdkResponseMgr.offer(event_id, response)
            callback.on_request(agent_id, event_id, text, param, response)
        else:
            response = Asr2TtsResponse(linksky_client, agent_id, robot_cid)
            AgentSdkResponseMgr.offer(event_id, response)
            callback.on_request(agent_id, event_id, text, param, response)
    elif isinstance(callback, (AsrVideo2VlmCallback, AsrVideo2TtsCallback)):
        # 多次回调 — 先 text（flag=3）后 video
        response = AgentSdkResponseMgr.get(event_id) or _build_asr_video_response(
            callback, linksky_client, agent_id, event_id, robot_cid
        )
        _dispatch_asr_video_request(callback, response, agent_id, event_id, 3, text, None, param)


def handle_video_h264_request_append(
    agent_sdk: "AgentSdk",
    linksky_client: "LinkskyClient",
    agent_id: str,
    event_id: str,
    robot_cid: str,
    msg: Dict[str, Any],
) -> None:
    callback_type = agent_sdk.get_agent_callback_type(agent_id)
    if callback_type is None:
        return
    callback = agent_sdk.get_robot_callback(callback_type)
    if callback is None:
        return

    param = _make_param(msg)
    buf = _decode_b64_bytes(msg, "videoH264")
    is_key = bool(msg.get("isKeyFrame", False))
    local_ts = int(msg.get("localTs", -1))

    if isinstance(callback, (AsrVideo2VlmCallback, AsrVideo2TtsCallback)):
        response = AgentSdkResponseMgr.get(event_id) or _build_asr_video_response(
            callback, linksky_client, agent_id, event_id, robot_cid
        )
        _dispatch_asr_video_request(callback, response, agent_id, event_id, 4, None, buf, param)
    elif isinstance(callback, (AudioVideo2VlmCallback, AudioVideo2TtsCallback)):
        response = AgentSdkResponseMgr.get(event_id)
        if response is None:
            return
        _dispatch_audio_request(callback, response, agent_id, event_id, "", 3, buf, param)
    else:
        # 任一被动回调都可在自己重载的 on_video_frame 中接收视频帧透传
        callback.on_video_frame(agent_id, event_id, 2, buf, is_key, local_ts, param)


def handle_video_image_request_append(
    agent_sdk: "AgentSdk",
    linksky_client: "LinkskyClient",
    agent_id: str,
    event_id: str,
    robot_cid: str,
    msg: Dict[str, Any],
) -> None:
    callback_type = agent_sdk.get_agent_callback_type(agent_id)
    if callback_type is None:
        return
    callback = agent_sdk.get_robot_callback(callback_type)
    if callback is None:
        return

    param = _make_param(msg)
    buf = _decode_b64_bytes(msg, "videoImage")

    if isinstance(callback, (AsrVideo2VlmCallback, AsrVideo2TtsCallback)):
        response = AgentSdkResponseMgr.get(event_id) or _build_asr_video_response(
            callback, linksky_client, agent_id, event_id, robot_cid
        )
        _dispatch_asr_video_request(callback, response, agent_id, event_id, 5, None, buf, param)
    else:
        callback.on_video_frame(agent_id, event_id, 3, buf, False, -1, param)


# ---------------------------------------------------------------------------
# 基类共享回调：state push / greet signal / face / video
# ---------------------------------------------------------------------------


def handle_state_request_meta(
    agent_sdk: "AgentSdk",
    agent_id: str,
    event_id: str,
    msg: Dict[str, Any],
) -> None:
    callback_type = agent_sdk.get_agent_callback_type(agent_id)
    if callback_type is None:
        return
    callback = agent_sdk.get_robot_callback(callback_type)
    if callback is None:
        return
    state_name = msg.get("stateName") or ""
    raw_value = msg.get("stateValue")
    if isinstance(raw_value, (dict, list)):
        import json as _json
        state_value = _json.dumps(raw_value, ensure_ascii=False)
    else:
        state_value = "" if raw_value is None else str(raw_value)
    callback.on_state(agent_id, event_id, state_name, state_value)


def handle_greet_request_signal(
    agent_sdk: "AgentSdk",
    linksky_client: "LinkskyClient",
    agent_id: str,
    robot_cid: str,
    event_id: str,
    msg: Dict[str, Any],
) -> None:
    callback_type = agent_sdk.get_agent_callback_type(agent_id)
    if callback_type is None:
        return
    callback = agent_sdk.get_robot_callback(callback_type)
    if callback is None:
        return
    response = GreetResponse(linksky_client, agent_id, robot_cid)
    callback.on_greet_signal(agent_id, event_id, _make_param(msg), response)


def handle_greet_request_face(
    agent_sdk: "AgentSdk",
    agent_id: str,
    event_id: str,
    msg: Dict[str, Any],
) -> None:
    callback_type = agent_sdk.get_agent_callback_type(agent_id)
    if callback_type is None:
        return
    callback = agent_sdk.get_robot_callback(callback_type)
    if callback is None:
        return
    callback.on_face_info(agent_id, event_id, _make_param(msg))


def handle_greet_request_video(
    agent_sdk: "AgentSdk",
    agent_id: str,
    event_id: str,
    msg: Dict[str, Any],
) -> None:
    callback_type = agent_sdk.get_agent_callback_type(agent_id)
    if callback_type is None:
        return
    callback = agent_sdk.get_robot_callback(callback_type)
    if callback is None:
        return
    # greet_request.video 同样走 base.on_video_frame，flag=2 / 3 由内部字段决定
    is_h264 = "videoH264" in msg
    if is_h264:
        buf = _decode_b64_bytes(msg, "videoH264")
        callback.on_video_frame(
            agent_id, event_id, 2, buf,
            bool(msg.get("isKeyFrame", False)),
            int(msg.get("localTs", -1)),
            _make_param(msg),
        )
    else:
        buf = _decode_b64_bytes(msg, "videoImage")
        callback.on_video_frame(agent_id, event_id, 3, buf, False, -1, _make_param(msg))


# ---------------------------------------------------------------------------
# Active：Flow ack handlers
# ---------------------------------------------------------------------------


def _get_flow(flow_id: str):
    return AgentSdkTaskFlowMgr.get_request(flow_id)


def handle_flow_start_request_ack(
    agent_sdk: "AgentSdk", agent_id: str, flow_id: str, event_id: str, msg: Dict[str, Any]
) -> None:
    flow = _get_flow(flow_id)
    if flow is None or flow.start_callback is None:
        return
    code = int(msg.get("code", 0))
    info = msg.get("msg") or ""
    flow.start_callback.on_start_request_ack(flow_id, event_id, code, info)


def handle_flow_start_execute_ack(
    agent_sdk: "AgentSdk", agent_id: str, flow_id: str, event_id: str, msg: Dict[str, Any]
) -> None:
    flow = _get_flow(flow_id)
    if flow is None or flow.start_callback is None:
        return
    code = int(msg.get("code", 0))
    info = msg.get("msg") or ""
    flow.start_callback.on_start_execute_ack(flow_id, event_id, code, info)


def handle_flow_task_request_ack(
    agent_sdk: "AgentSdk", agent_id: str, flow_id: str, event_id: str, msg: Dict[str, Any]
) -> None:
    flow = _get_flow(flow_id)
    if flow is None:
        return
    cb = flow.get_task_callback(event_id)
    if cb is None:
        return
    code = int(msg.get("code", 0))
    info = msg.get("msg") or ""
    cb.on_task_request_ack(flow_id, event_id, code, info)


def handle_flow_task_execute_ack(
    agent_sdk: "AgentSdk", agent_id: str, flow_id: str, event_id: str, msg: Dict[str, Any]
) -> None:
    flow = _get_flow(flow_id)
    if flow is None:
        return
    cb = flow.get_task_callback(event_id)
    if cb is None:
        return
    code = int(msg.get("code", 0))
    info = msg.get("msg") or ""
    cb.on_task_execute_ack(flow_id, event_id, code, info)
    flow.remove_task_callback(event_id)


def handle_flow_end_request_ack(
    agent_sdk: "AgentSdk", agent_id: str, flow_id: str, event_id: str, msg: Dict[str, Any]
) -> None:
    flow = _get_flow(flow_id)
    if flow is None or flow.end_callback is None:
        return
    code = int(msg.get("code", 0))
    info = msg.get("msg") or ""
    flow.end_callback.on_end_request_ack(flow_id, event_id, code, info)


def handle_flow_interrupt_request_ack(
    agent_sdk: "AgentSdk", agent_id: str, flow_id: str, event_id: str, msg: Dict[str, Any]
) -> None:
    flow = _get_flow(flow_id)
    if flow is None or flow.interrupt_callback is None:
        return
    code = int(msg.get("code", 0))
    info = msg.get("msg") or ""
    flow.interrupt_callback.on_interrupt_request_ack(flow_id, event_id, code, info)
