"""
Roneia Client - WebSocket 客户端

提供:
- 自动重连的 WebSocket 连接
- 消息收发
- 心跳维护
- 本地 Skill 执行
"""

import asyncio
import json
import logging
import sys
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Awaitable

import websockets
from websockets.exceptions import ConnectionClosed, WebSocketException

sys.path.insert(0, str(Path(__file__).parent.parent))

from roneia_core.protocol import (
    Message,
    MessageType,
    create_auth_request,
    create_task_request,
    create_skill_result,
    create_skill_list,
    create_permission_response,
    create_heartbeat,
    create_ping,
    create_error,
    create_skill_registry_list_request,
    create_skill_status_update,
    create_voice_config_request,
    create_search_config_request,
    create_persona_get_request,
    create_persona_update_request,
    ErrorCode,
    validate_message,
)

from .config import RoneiaClientConfig, get_client_info
from .local_skill_executor import LocalSkillExecutor
from .permission_ui import PermissionUI, PermissionManager
from .skill_store import SkillStore


logger = logging.getLogger(__name__)


class RoneiaClient:
    """
    Roneia 本地客户端

    连接云端服务器，执行本地 Skill。
    """

    def __init__(self, config: RoneiaClientConfig):
        """
        初始化客户端

        Args:
            config: 客户端配置
        """
        self.config = config

        # WebSocket 连接
        self._websocket = None
        self._connected = False
        self._authenticated = False
        self._client_id: Optional[str] = None

        # 任务管理
        self._pending_tasks: Dict[str, asyncio.Future] = {}
        self._reconnect_count = 0

        # 权限管理
        self._permission_ui = PermissionUI(
            use_gui=config.permissions.use_gui,
            auto_grant=config.permissions.auto_grant,
        )
        self._permission_manager = PermissionManager(self._permission_ui)

        # 本地 Skill 执行器（简化版）
        self._skill_executor = LocalSkillExecutor(
            permission_manager=self._permission_manager,
        )

        # Skill 状态存储
        self._skill_store = SkillStore()

        # 云端 Skill 列表缓存
        self._registry_skills: List[Dict[str, Any]] = []

        # 回调
        self._on_connected: List[Callable[[], Awaitable[None]]] = []
        self._on_disconnected: List[Callable[[], Awaitable[None]]] = []
        self._on_message: List[Callable[[str], Awaitable[None]]] = []
        self._on_response: List[Callable[[str], Awaitable[None]]] = []

        # 后台任务
        self._heartbeat_task: Optional[asyncio.Task] = None
        self._receive_task: Optional[asyncio.Task] = None

    # ============================================================================
    # 连接管理
    # ============================================================================

    async def connect(self) -> bool:
        """
        连接到服务器

        Returns:
            是否连接成功
        """
        url = self.config.server.url
        token = self.config.server.token

        # 添加 token 到 URL 参数
        if token:
            separator = "&" if "?" in url else "?"
            url = f"{url}{separator}token={token}"

        logger.info(f"Connecting to {self.config.server.url}...")

        try:
            self._websocket = await websockets.connect(
                url,
                ping_interval=None,  # 我们自己处理心跳
            )
            self._connected = True
            logger.info("WebSocket connected")

            # 发送认证请求
            await self._authenticate()

            if self._authenticated:
                # 启动后台任务
                await self._start_background_tasks()

                # 上报本地 Skill 列表
                await self._report_skills()

                # 同步已启用的 Skill 到服务端
                await self.sync_enabled_skills()

                # 触发连接回调
                for callback in self._on_connected:
                    try:
                        await callback()
                    except Exception as e:
                        logger.error(f"Connect callback error: {e}")

                self._reconnect_count = 0
                return True

            return False

        except Exception as e:
            logger.error(f"Connection failed: {e}")
            self._connected = False
            return False

    async def disconnect(self) -> None:
        """断开连接"""
        logger.info("Disconnecting...")

        # 停止后台任务
        await self._stop_background_tasks()

        # 关闭 WebSocket
        if self._websocket:
            try:
                await self._websocket.close()
            except Exception:
                pass
            self._websocket = None

        self._connected = False
        self._authenticated = False

        # 触发断开回调
        for callback in self._on_disconnected:
            try:
                await callback()
            except Exception as e:
                logger.error(f"Disconnect callback error: {e}")

        logger.info("Disconnected")

    async def reconnect(self) -> bool:
        """
        重新连接

        Returns:
            是否连接成功
        """
        max_attempts = self.config.server.max_reconnect_attempts
        interval = self.config.server.reconnect_interval

        while max_attempts < 0 or self._reconnect_count < max_attempts:
            self._reconnect_count += 1
            logger.info(f"Reconnecting (attempt {self._reconnect_count})...")

            await asyncio.sleep(interval)

            if await self.connect():
                return True

        logger.error("Max reconnect attempts reached")
        return False

    async def _authenticate(self) -> bool:
        """发送认证请求"""
        client_info = get_client_info()
        client_info["skills"] = [s["name"] for s in self._skill_executor.get_skill_list()]

        auth_msg = create_auth_request(
            token=self.config.server.token,
            client_info=client_info,
        )

        await self._send(auth_msg.to_dict())

        # 等待认证响应
        try:
            response = await asyncio.wait_for(
                self._websocket.recv(),
                timeout=10.0
            )
            data = json.loads(response)
            msg = Message.from_dict(data)

            if msg.type == MessageType.AUTH_RESPONSE:
                if msg.payload.get("success"):
                    self._authenticated = True
                    self._client_id = msg.payload.get("client_id")
                    logger.info(f"Authenticated, client_id: {self._client_id}")
                    return True
                else:
                    error = msg.payload.get("error", "Unknown error")
                    logger.error(f"Authentication failed: {error}")
                    return False

        except asyncio.TimeoutError:
            logger.error("Authentication timeout")
            return False
        except Exception as e:
            logger.error(f"Authentication error: {e}")
            return False

        return False

    async def _report_skills(self) -> None:
        """上报本地 Skill 列表"""
        skills = self._skill_executor.get_skill_list()
        msg = create_skill_list(skills)
        await self._send(msg.to_dict())
        logger.info(f"Reported {len(skills)} skills to server")

    # ============================================================================
    # 消息收发
    # ============================================================================

    async def _send(self, data: Dict[str, Any]) -> bool:
        """
        发送消息

        Args:
            data: 消息数据

        Returns:
            是否发送成功
        """
        if not self._websocket or not self._connected:
            return False

        try:
            await self._websocket.send(json.dumps(data, ensure_ascii=False))
            return True
        except Exception as e:
            logger.error(f"Send error: {e}")
            return False

    async def send_task(self, text: str, timeout: float = 120.0) -> Optional[str]:
        """
        发送任务并等待响应

        Args:
            text: 任务文本
            timeout: 超时时间（秒）

        Returns:
            响应文本，失败返回 None
        """
        if not self._authenticated:
            logger.error("Not authenticated")
            return None

        msg = create_task_request(text)

        # 创建 Future 等待响应
        future = asyncio.get_event_loop().create_future()
        self._pending_tasks[msg.id] = future

        try:
            await self._send(msg.to_dict())
            response = await asyncio.wait_for(future, timeout=timeout)
            return response
        except asyncio.TimeoutError:
            logger.error("Task timeout")
            return None
        finally:
            self._pending_tasks.pop(msg.id, None)

    async def _handle_message(self, data: Dict[str, Any]) -> None:
        """
        处理收到的消息

        Args:
            data: 消息数据
        """
        # 验证消息
        valid, error = validate_message(data)
        if not valid:
            logger.warning(f"Invalid message: {error}")
            return

        msg = Message.from_dict(data)
        msg_type = msg.type

        logger.debug(f"Received: {msg_type.value}")

        if msg_type == MessageType.TASK_RESPONSE:
            # 任务响应
            correlation_id = msg.correlation_id
            if correlation_id and correlation_id in self._pending_tasks:
                future = self._pending_tasks[correlation_id]
                if not future.done():
                    text = msg.payload.get("text", "")
                    future.set_result(text)

                    # 触发响应回调
                    for callback in self._on_response:
                        try:
                            await callback(text)
                        except Exception as e:
                            logger.error(f"Response callback error: {e}")

        elif msg_type == MessageType.SKILL_CALL:
            # 执行本地 Skill
            await self._handle_skill_call(msg)

        elif msg_type == MessageType.PERMISSION_REQUEST:
            # 权限请求
            await self._handle_permission_request(msg)

        elif msg_type == MessageType.PONG:
            # Pong 响应，忽略
            pass

        elif msg_type == MessageType.SKILL_REGISTRY_RESPONSE:
            # Skill 列表响应
            correlation_id = msg.correlation_id
            if correlation_id and correlation_id in self._pending_tasks:
                future = self._pending_tasks[correlation_id]
                if not future.done():
                    future.set_result(msg.to_dict())

        elif msg_type == MessageType.VOICE_CONFIG_RESPONSE:
            # 语音配置响应
            correlation_id = msg.correlation_id
            if correlation_id and correlation_id in self._pending_tasks:
                future = self._pending_tasks[correlation_id]
                if not future.done():
                    future.set_result(msg.to_dict())

        elif msg_type == MessageType.SEARCH_CONFIG_RESPONSE:
            # 搜索配置响应
            correlation_id = msg.correlation_id
            if correlation_id and correlation_id in self._pending_tasks:
                future = self._pending_tasks[correlation_id]
                if not future.done():
                    future.set_result(msg.to_dict())

        elif msg_type == MessageType.PERSONA_RESPONSE:
            # 人设响应
            correlation_id = msg.correlation_id
            if correlation_id and correlation_id in self._pending_tasks:
                future = self._pending_tasks[correlation_id]
                if not future.done():
                    future.set_result(msg.to_dict())

        elif msg_type == MessageType.HEARTBEAT:
            # 心跳
            pass

        elif msg_type == MessageType.NOTIFICATION:
            # 通知
            notification_type = msg.payload.get("notification_type", "")
            notification_data = msg.payload.get("data", {})
            logger.info(f"Notification [{notification_type}]: {notification_data}")

            # 触发消息回调
            for callback in self._on_message:
                try:
                    await callback(f"[{notification_type}] {notification_data}")
                except Exception as e:
                    logger.error(f"Message callback error: {e}")

        elif msg_type == MessageType.ERROR:
            # 错误
            error_code = msg.payload.get("code")
            error_message = msg.payload.get("message")
            logger.error(f"Server error [{error_code}]: {error_message}")

            # 如果有 correlation_id，解除等待中的任务
            correlation_id = msg.correlation_id
            if correlation_id and correlation_id in self._pending_tasks:
                future = self._pending_tasks[correlation_id]
                if not future.done():
                    future.set_result(f"[服务器错误] {error_message}")

        else:
            logger.debug(f"Unhandled message type: {msg_type.value}")

    async def _handle_skill_call(self, msg: Message) -> None:
        """处理 Skill 调用请求"""
        payload = msg.payload
        skill_name = payload.get("skill", "")
        tool_name = payload.get("tool", "")
        arguments = payload.get("args", {})
        permission = payload.get("permission")
        purpose = payload.get("purpose")

        logger.info(f"Executing: {skill_name}.{tool_name}")
        print(f"\n  [执行 {skill_name}.{tool_name}]", end="", flush=True)

        try:
            result = await self._skill_executor.execute_tool(
                skill_name, tool_name, arguments, permission, purpose
            )

            # 发送结果（result 现在是 dict 格式）
            success = result.get("success", False)

            # 显示执行结果附加信息（如搜索引擎类型）
            if success and isinstance(result, dict) and result.get("provider"):
                print(f" → {result['provider']}", flush=True)
            else:
                print(flush=True)

            response = create_skill_result(
                correlation_id=msg.id,
                success=success,
                data=result if success else None,
                error={
                    "code": ErrorCode.SKILL_CALL_FAILED.value,
                    "message": result.get("error", "Unknown error"),
                } if not success else None,
            )
            await self._send(response.to_dict())

        except Exception as e:
            logger.error(f"Skill execution error: {e}")
            response = create_skill_result(
                correlation_id=msg.id,
                success=False,
                error={
                    "code": ErrorCode.SKILL_CALL_FAILED.value,
                    "message": str(e),
                },
            )
            await self._send(response.to_dict())

    async def _handle_permission_request(self, msg: Message) -> None:
        """处理权限请求"""
        payload = msg.payload
        skill_name = payload.get("skill_name", "")
        permission = payload.get("permission", "")
        reason = payload.get("reason", "")
        purpose = payload.get("purpose", "")

        # 请求用户授权
        granted = await self._permission_manager.check_and_request(
            skill_name, permission, reason, purpose
        )

        # 发送响应
        response = create_permission_response(
            correlation_id=msg.id,
            granted=granted,
        )
        await self._send(response.to_dict())

    # ============================================================================
    # 后台任务
    # ============================================================================

    async def _start_background_tasks(self) -> None:
        """启动后台任务"""
        # 初始化 Skill 执行器
        await self._skill_executor.initialize()

        # 启动心跳
        self._heartbeat_task = asyncio.create_task(self._heartbeat_loop())

        # 启动消息接收
        self._receive_task = asyncio.create_task(self._receive_loop())

    async def _stop_background_tasks(self) -> None:
        """停止后台任务"""
        if self._heartbeat_task:
            self._heartbeat_task.cancel()
            try:
                await self._heartbeat_task
            except asyncio.CancelledError:
                pass
            self._heartbeat_task = None

        if self._receive_task:
            self._receive_task.cancel()
            try:
                await self._receive_task
            except asyncio.CancelledError:
                pass
            self._receive_task = None

        # 关闭 Skill 执行器
        await self._skill_executor.shutdown()

    async def _heartbeat_loop(self) -> None:
        """心跳循环"""
        interval = self.config.server.heartbeat_interval

        while self._connected:
            await asyncio.sleep(interval)

            if not self._connected:
                break

            try:
                msg = create_ping()
                await self._send(msg.to_dict())
            except Exception as e:
                logger.error(f"Heartbeat error: {e}")

    async def _receive_loop(self) -> None:
        """消息接收循环"""
        while self._connected:
            try:
                data = await self._websocket.recv()
                msg_data = json.loads(data)
                await self._handle_message(msg_data)

            except ConnectionClosed:
                logger.warning("Connection closed by server")
                self._connected = False
                break

            except asyncio.CancelledError:
                break

            except Exception as e:
                logger.error(f"Receive error: {e}")

        # 连接断开，尝试重连
        if not self._connected:
            for callback in self._on_disconnected:
                try:
                    await callback()
                except Exception as e:
                    logger.error(f"Disconnect callback error: {e}")

            # 自动重连
            await self.reconnect()

    # ============================================================================
    # 事件回调
    # ============================================================================

    def on_connected(self, callback: Callable[[], Awaitable[None]]) -> None:
        """注册连接成功回调"""
        self._on_connected.append(callback)

    def on_disconnected(self, callback: Callable[[], Awaitable[None]]) -> None:
        """注册断开连接回调"""
        self._on_disconnected.append(callback)

    def on_message(self, callback: Callable[[str], Awaitable[None]]) -> None:
        """注册消息回调"""
        self._on_message.append(callback)

    def on_response(self, callback: Callable[[str], Awaitable[None]]) -> None:
        """注册任务响应回调"""
        self._on_response.append(callback)

    # ============================================================================
    # 状态查询
    # ============================================================================

    @property
    def is_connected(self) -> bool:
        """是否已连接"""
        return self._connected

    @property
    def is_authenticated(self) -> bool:
        """是否已认证"""
        return self._authenticated

    @property
    def client_id(self) -> Optional[str]:
        """客户端ID"""
        return self._client_id

    def get_loaded_skills(self) -> List[str]:
        """获取已加载的本地 Skill"""
        return self._skill_executor.get_loaded_skills()

    # ============================================================================
    # Skill 管理
    # ============================================================================

    async def request_skill_list(self) -> List[Dict[str, Any]]:
        """
        请求云端 Skill 列表

        Returns:
            Skill 列表
        """
        if not self._authenticated:
            logger.error("Not authenticated")
            return []

        msg = create_skill_registry_list_request()

        # 创建 Future 等待响应
        future = asyncio.get_event_loop().create_future()
        self._pending_tasks[msg.id] = future

        try:
            await self._send(msg.to_dict())
            response = await asyncio.wait_for(future, timeout=10.0)

            if isinstance(response, dict):
                skills = response.get("payload", {}).get("skills", [])
                self._registry_skills = skills
                return skills

            return []

        except asyncio.TimeoutError:
            logger.error("Request skill list timeout")
            return []
        finally:
            self._pending_tasks.pop(msg.id, None)

    def get_registry_skills(self) -> List[Dict[str, Any]]:
        """获取缓存的云端 Skill 列表"""
        return self._registry_skills

    def install_skill(self, skill_info: Dict[str, Any]) -> bool:
        """
        安装 Skill

        Args:
            skill_info: Skill 信息

        Returns:
            是否安装成功
        """
        return self._skill_store.install(
            name=skill_info.get("name", ""),
            version=skill_info.get("version", ""),
            title=skill_info.get("title", ""),
            description=skill_info.get("description", ""),
        )

    def remove_skill(self, name: str) -> bool:
        """
        删除 Skill

        Args:
            name: Skill 名称

        Returns:
            是否删除成功
        """
        return self._skill_store.remove(name)

    def enable_skill(self, name: str) -> bool:
        """
        启用 Skill

        Args:
            name: Skill 名称

        Returns:
            是否启用成功
        """
        return self._skill_store.enable(name)

    def disable_skill(self, name: str) -> bool:
        """
        禁用 Skill

        Args:
            name: Skill 名称

        Returns:
            是否禁用成功
        """
        return self._skill_store.disable(name)

    def get_installed_skills(self) -> List[Dict[str, Any]]:
        """获取已安装的 Skill 列表"""
        return [skill.to_dict() for skill in self._skill_store.list_installed()]

    def get_enabled_skills(self) -> List[str]:
        """获取已启用的 Skill 名称列表"""
        return self._skill_store.get_enabled_names()

    def is_skill_installed(self, name: str) -> bool:
        """Skill 是否已安装"""
        return self._skill_store.is_installed(name)

    def is_skill_enabled(self, name: str) -> bool:
        """Skill 是否已启用"""
        return self._skill_store.is_enabled(name)

    async def sync_enabled_skills(self) -> bool:
        """
        同步已启用的 Skill 到服务端

        Returns:
            是否同步成功
        """
        if not self._authenticated:
            logger.error("Not authenticated")
            return False

        enabled_skills = self.get_enabled_skills()
        msg = create_skill_status_update(enabled_skills)
        return await self._send(msg.to_dict())

    async def request_voice_config(self) -> Optional[Dict[str, Any]]:
        """
        请求 Server 下发语音配置

        Returns:
            语音配置字典，失败返回 None
        """
        if not self._authenticated:
            logger.error("Not authenticated")
            return None

        msg = create_voice_config_request()

        # 创建 Future 等待响应
        future = asyncio.get_event_loop().create_future()
        self._pending_tasks[msg.id] = future

        try:
            await self._send(msg.to_dict())
            response = await asyncio.wait_for(future, timeout=10.0)

            if isinstance(response, dict):
                return response.get("payload", {}).get("config", {})

            return None

        except asyncio.TimeoutError:
            logger.error("Request voice config timeout")
            return None
        finally:
            self._pending_tasks.pop(msg.id, None)

    async def request_search_config(self) -> Optional[Dict[str, Any]]:
        """
        请求 Server 下发搜索配置

        Returns:
            搜索配置字典，失败返回 None
        """
        if not self._authenticated:
            logger.error("Not authenticated")
            return None

        msg = create_search_config_request()

        # 创建 Future 等待响应
        future = asyncio.get_event_loop().create_future()
        self._pending_tasks[msg.id] = future

        try:
            await self._send(msg.to_dict())
            response = await asyncio.wait_for(future, timeout=10.0)

            if isinstance(response, dict):
                return response.get("payload", {}).get("config", {})

            return None

        except asyncio.TimeoutError:
            logger.error("Request search config timeout")
            return None
        finally:
            self._pending_tasks.pop(msg.id, None)

    async def get_persona(self) -> Optional[str]:
        """
        获取当前人设

        Returns:
            人设内容，失败返回 None
        """
        if not self._authenticated:
            logger.error("Not authenticated")
            return None

        msg = create_persona_get_request()

        future = asyncio.get_event_loop().create_future()
        self._pending_tasks[msg.id] = future

        try:
            await self._send(msg.to_dict())
            response = await asyncio.wait_for(future, timeout=10.0)

            if isinstance(response, dict):
                payload = response.get("payload", {})
                if payload.get("success", True):
                    return payload.get("content", "")

            return None

        except asyncio.TimeoutError:
            logger.error("Get persona timeout")
            return None
        finally:
            self._pending_tasks.pop(msg.id, None)

    async def update_persona(self, content: str) -> bool:
        """
        更新人设

        Args:
            content: 新的人设内容

        Returns:
            是否更新成功
        """
        if not self._authenticated:
            logger.error("Not authenticated")
            return False

        msg = create_persona_update_request(content)

        future = asyncio.get_event_loop().create_future()
        self._pending_tasks[msg.id] = future

        try:
            await self._send(msg.to_dict())
            response = await asyncio.wait_for(future, timeout=10.0)

            if isinstance(response, dict):
                return response.get("payload", {}).get("success", False)

            return False

        except asyncio.TimeoutError:
            logger.error("Update persona timeout")
            return False
        finally:
            self._pending_tasks.pop(msg.id, None)

    async def reset_persona(self) -> bool:
        """
        恢复默认人设（由 Server 端提供默认值）

        Returns:
            是否重置成功
        """
        if not self._authenticated:
            logger.error("Not authenticated")
            return False

        msg = create_persona_update_request(reset=True)

        future = asyncio.get_event_loop().create_future()
        self._pending_tasks[msg.id] = future

        try:
            await self._send(msg.to_dict())
            response = await asyncio.wait_for(future, timeout=10.0)

            if isinstance(response, dict):
                return response.get("payload", {}).get("success", False)

            return False

        except asyncio.TimeoutError:
            logger.error("Reset persona timeout")
            return False
        finally:
            self._pending_tasks.pop(msg.id, None)

    async def run_forever(self) -> None:
        """
        运行客户端（阻塞）

        持续运行直到被中断。
        """
        # 连接
        if not await self.connect():
            logger.error("Initial connection failed")
            return

        # 保持运行
        try:
            while True:
                await asyncio.sleep(1)
        except asyncio.CancelledError:
            pass
        finally:
            await self.disconnect()
