from __future__ import annotations

import asyncio
import logging
from dataclasses import dataclass
from threading import RLock
from typing import Any, Literal

from witty_agent_server.application.models.runtime_events import build_outbound_event
from witty_agent_server.runtimes.runtime_base import RuntimeType


logger = logging.getLogger(__name__)

SessionState = Literal["running", "idle", "error"]


@dataclass
class _SessionStateSnapshot:
    agent_id: str
    runtime_type: RuntimeType
    state: SessionState
    seq: int


@dataclass
class _SessionQueueBinding:
    loop: asyncio.AbstractEventLoop
    queue: asyncio.Queue[dict[str, Any] | None]


class SessionStateSyncService:
    """管理会话状态事件与 WS 推送通道。"""

    def __init__(self) -> None:
        self._lock = RLock()
        self._states: dict[tuple[str, str], _SessionStateSnapshot] = {}
        self._bindings: dict[tuple[str, str], _SessionQueueBinding] = {}

    def bind_connection(
        self,
        *,
        agent_id: str,
        session_id: str,
        loop: asyncio.AbstractEventLoop,
        queue: asyncio.Queue[dict[str, Any] | None],
    ) -> None:
        """绑定 session 到当前活动连接的发送队列。"""
        with self._lock:
            self._bindings[(agent_id, session_id)] = _SessionQueueBinding(
                loop=loop,
                queue=queue,
            )
        logger.info("session ws bound: agent_id=%s session_id=%s", agent_id, session_id)

    def unbind_connection(self, *, agent_id: str, session_id: str) -> None:
        """解绑 session 的活动连接。"""
        with self._lock:
            self._bindings.pop((agent_id, session_id), None)
        logger.info(
            "session ws unbound: agent_id=%s session_id=%s",
            agent_id,
            session_id,
        )

    def build_state_changed_event(
        self,
        *,
        agent_id: str,
        session_id: str,
        runtime_type: RuntimeType,
        state: SessionState,
        reason: str | None = None,
    ) -> dict[str, Any] | None:
        """构建状态变化事件;若状态未变化则返回 None。"""
        scope_key = (agent_id, session_id)
        with self._lock:
            snapshot = self._states.get(scope_key)
            if snapshot is not None and snapshot.state == state:
                return None
            seq = 1 if snapshot is None else snapshot.seq + 1
            self._states[scope_key] = _SessionStateSnapshot(
                agent_id=agent_id,
                runtime_type=runtime_type,
                state=state,
                seq=seq,
            )

        payload: dict[str, Any] = {"state": state, "seq": seq}
        if isinstance(reason, str) and reason:
            payload["reason"] = reason

        logger.info(
            "session state changed: agent_id=%s session_id=%s state=%s seq=%s reason=%s",
            agent_id,
            session_id,
            state,
            seq,
            reason,
        )
        return build_outbound_event(
            agent_id=agent_id,
            session_id=session_id,
            runtime_type=runtime_type,
            type="session.state_changed",
            payload=payload,
        )

    def emit_event(self, *, agent_id: str, session_id: str, event: dict[str, Any]) -> bool:
        """向当前连接实时发送事件。"""
        with self._lock:
            binding = self._bindings.get((agent_id, session_id))
        if binding is None:
            return False

        binding.loop.call_soon_threadsafe(binding.queue.put_nowait, event)
        return True

    def emit_state_changed(
        self,
        *,
        agent_id: str,
        session_id: str,
        runtime_type: RuntimeType,
        state: SessionState,
        reason: str | None = None,
    ) -> bool:
        """构建并发送状态变化事件。"""
        event = self.build_state_changed_event(
            agent_id=agent_id,
            session_id=session_id,
            runtime_type=runtime_type,
            state=state,
            reason=reason,
        )
        if event is None:
            return False
        return self.emit_event(agent_id=agent_id, session_id=session_id, event=event)