"""noVNC 客户端核心实现"""

import asyncio
import logging
from typing import Optional, Tuple

from PIL import Image

from .types import ServerInfo, MouseButton, ScrollDirection, KeyInput, SpecialKey
from .exceptions import (
    ConnectionError,
    ScreenshotError,
)
from .protocol import (
    create_pointer_event,
    create_key_event,
    get_mouse_button_mask,
    get_scroll_button_mask,
    char_to_keysym,
    get_special_key,
)
from .rfb_handler import RFBHandler

logger = logging.getLogger(__name__)


class NoVNCClient:
    """noVNC 客户端

    用于连接 noVNC WebSocket 服务并进行 VNC 操作的异步客户端。

    Examples:
        >>> async with NoVNCClient('localhost', 6080) as client:
        ...     await client.take_screenshot('screenshot.png')
        ...     await client.mouse_click(100, 100)
    """

    def __init__(
        self,
        host: str = "localhost",
        port: int = 6080,
        path: str = "/websockify",
        timeout: float = 10.0,
    ):
        """初始化客户端

        Args:
            host: 服务器地址
            port: 服务器端口
            path: WebSocket 路径
            timeout: 连接超时时间（秒）
        """
        self.host = host
        self.port = port
        self.path = path
        self.timeout = timeout

        # 底层处理器
        self._handler = RFBHandler(timeout=timeout)

        # 连接状态
        self._connected = False

        # 鼠标位置追踪
        self._mouse_x = 0
        self._mouse_y = 0

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

    @property
    def server_info(self) -> Optional[ServerInfo]:
        """服务器信息"""
        return self._handler.server_info

    @property
    def screen_size(self) -> Tuple[int, int]:
        """屏幕尺寸 (width, height)"""
        if not self._handler.server_info:
            return (0, 0)
        return (self._handler.server_info.width, self._handler.server_info.height)

    @property
    def mouse_position(self) -> Tuple[int, int]:
        """当前鼠标位置 (x, y)"""
        return (self._mouse_x, self._mouse_y)

    async def __aenter__(self):
        """异步上下文管理器入口"""
        await self.connect()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """异步上下文管理器出口"""
        await self.disconnect()

    async def connect(self) -> None:
        """连接到 noVNC 服务器"""
        try:
            # 建立连接
            await self._handler.connect(self.host, self.port, self.path)

            # 执行 RFB 握手
            await self._handler.perform_handshake()

            # 初始化鼠标位置为屏幕中心
            if self._handler.server_info:
                self._mouse_x = self._handler.server_info.width // 2
                self._mouse_y = self._handler.server_info.height // 2
            else:
                self._mouse_x = 0
                self._mouse_y = 0

            self._connected = True
            logger.debug("连接成功")

        except Exception as e:
            await self._handler.disconnect()
            raise ConnectionError(f"连接失败: {e}") from e

    async def disconnect(self) -> None:
        """断开连接"""
        await self._handler.disconnect()
        self._connected = False
        logger.debug("连接已断开")

    async def mouse_move(self, x: int, y: int) -> None:
        """移动鼠标

        Args:
            x: X 坐标
            y: Y 坐标
        """
        message = create_pointer_event(x, y, 0)
        await self._handler.send_binary(message)

        # 更新鼠标位置追踪
        self._mouse_x = x
        self._mouse_y = y

        logger.debug(f"鼠标移动: ({x}, {y})")

    async def mouse_click(self, x: int, y: int, button: MouseButton = "left") -> None:
        """鼠标点击

        Args:
            x: X 坐标
            y: Y 坐标
            button: 按钮类型
        """
        button_mask = get_mouse_button_mask(button)
        if button_mask == 0:
            raise ValueError(f"无效的鼠标按钮: {button}")

        # 按下
        message = create_pointer_event(x, y, button_mask)
        await self._handler.send_binary(message)
        await asyncio.sleep(0.05)  # 短暂延迟

        # 释放
        message = create_pointer_event(x, y, 0)
        await self._handler.send_binary(message)

        # 更新鼠标位置追踪
        self._mouse_x = x
        self._mouse_y = y

        logger.debug(f"鼠标点击: ({x}, {y}) {button}")

    async def mouse_scroll(
        self, x: int, y: int, direction: ScrollDirection = "up", steps: int = 1
    ) -> None:
        """鼠标滚轮

        Args:
            x: X 坐标
            y: Y 坐标
            direction: 滚动方向
            steps: 滚动步数
        """
        button_mask = get_scroll_button_mask(direction)
        if button_mask == 0:
            raise ValueError(f"无效的滚动方向: {direction}")

        for _ in range(steps):
            # 按下
            message = create_pointer_event(x, y, button_mask)
            await self._handler.send_binary(message)
            await asyncio.sleep(0.05)

            # 释放
            message = create_pointer_event(x, y, 0)
            await self._handler.send_binary(message)
            await asyncio.sleep(0.05)

        # 更新鼠标位置追踪
        self._mouse_x = x
        self._mouse_y = y

        logger.debug(f"鼠标滚轮: ({x}, {y}) {direction} x{steps}")

    async def _key_press(self, key: KeyInput, down: bool = True) -> None:
        """发送按键事件

        支持发送单个按键的按下或释放事件。可以处理普通字符、特殊按键和修饰键。

        Args:
            key: 按键输入，可以是：
                - 普通字符：'a', 'A', '1', '!', ' ' 等
                - 特殊按键名称：'enter', 'tab', 'backspace', 'escape', 'delete' 等
                - 方向键：'left', 'right', 'up', 'down'
                - 功能键：'f1', 'f2', ..., 'f12'
                - 修饰键：'ctrl', 'alt', 'shift', 'win'
                - 导航键：'home', 'end', 'page_up', 'page_down', 'insert'
                - SpecialKey 枚举值：SpecialKey.ENTER, SpecialKey.TAB 等
            down: True为按下，False为释放

        Raises:
            ValueError: 如果提供的按键不被支持
            ConnectionError: 如果连接已断开

        Examples:
            >>> await client.key_press('a')           # 按下字符 'a'
            >>> await client.key_press('enter')       # 按下回车键
            >>> await client.key_press('ctrl')        # 按下 Ctrl 键
            >>> await client.key_press('f1')          # 按下 F1 键
            >>> await client.key_press(SpecialKey.TAB)  # 使用枚举值
            >>> await client.key_press('shift', down=False)  # 释放 Shift 键
        """
        # 如果是 SpecialKey 枚举，转换为字符串
        if isinstance(key, SpecialKey):
            key_str = key.value
        else:
            key_str = str(key)

        try:
            # 尝试作为特殊按键处理
            keysym = get_special_key(key_str)
        except ValueError:
            # 如果不是特殊按键，尝试作为字符处理
            try:
                keysym = char_to_keysym(key_str)
            except ValueError:
                raise ValueError(f"无效的按键: {key_str}")

        message = create_key_event(keysym, down)
        await self._handler.send_binary(message)
        logger.debug(f"按键事件: {key_str} ({'按下' if down else '释放'})")

    async def key_down(self, key: KeyInput) -> None:
        """按下按键

        发送按键按下事件。这是一个便捷方法，等同于 key_press(key, down=True)。

        Args:
            key: 按键输入，支持的格式与 key_press 相同

        Examples:
            >>> await client.key_down('ctrl')     # 按下 Ctrl 键
            >>> await client.key_down('a')       # 按下字符 'a'
        """
        await self._key_press(key, down=True)

    async def key_up(self, key: KeyInput) -> None:
        """释放按键

        发送按键释放事件。这是一个便捷方法，等同于 key_press(key, down=False)。

        Args:
            key: 按键输入，支持的格式与 key_press 相同

        Examples:
            >>> await client.key_up('ctrl')      # 释放 Ctrl 键
            >>> await client.key_up('a')        # 释放字符 'a'
        """
        await self._key_press(key, down=False)

    async def key_tap(self, key: KeyInput, duration: float = 0.05) -> None:
        """点击按键（按下然后释放）

        发送完整的按键点击事件，包括按下和释放。这是最常用的按键操作方法。

        Args:
            key: 按键输入，支持的格式与 key_press 相同
            duration: 按住时间（秒），默认 0.05 秒

        Examples:
            >>> await client.key_tap('a')                    # 点击字符 'a'
            >>> await client.key_tap('enter')                # 点击回车键
            >>> await client.key_tap('space')                # 点击空格键
            >>> await client.key_tap('f1', duration=0.1)     # 点击 F1 键，按住 0.1 秒
        """
        await self.key_down(key)
        await asyncio.sleep(duration)
        await self.key_up(key)
        logger.debug(f"按键点击: {key}")

    async def type_text(self, text: str, delay: float = 0.01) -> None:
        """输入文本字符串

        逐个字符输入文本内容。会自动处理换行符、制表符等特殊字符。
        不支持的字符会被跳过并记录警告。

        Args:
            text: 要输入的文本字符串，支持：
                - 普通字符：字母、数字、标点符号
                - 空格和换行符
                - 制表符（\\t）
                - 其他可打印字符
            delay: 字符间延迟时间（秒），默认 0.01 秒，可以设为 0 取消延迟

        Note:
            - 不支持的字符会被跳过，不会导致方法失败
            - 换行符（\\n）会被转换为回车键
            - 制表符（\\t）会被转换为 Tab 键

        Examples:
            >>> await client.type_text("Hello World!")
            >>> await client.type_text("用户名\\t密码\\n", delay=0.05)
            >>> await client.type_text("快速输入", delay=0)  # 无延迟
        """
        for char in text:
            try:
                await self.key_tap(char, duration=0.05)
                if delay > 0:
                    await asyncio.sleep(delay)
            except ValueError as e:
                logger.warning(f"跳过无法输入的字符 '{char}': {e}")

        logger.debug(f"输入文本: {text}")

    async def key_combination(self, *keys: KeyInput, duration: float = 0.1) -> None:
        """发送组合键

        同时按下多个按键，形成组合键操作。按键会按顺序按下，然后按相反顺序释放。

        Args:
            *keys: 按键序列，支持与 key_press 相同的输入格式
            duration: 所有按键同时按住的时间（秒），默认 0.1 秒

        Note:
            - 按键按下顺序：从左到右
            - 按键释放顺序：从右到左（与按下顺序相反）
            - 通常修饰键（Ctrl、Alt、Shift）应该放在前面

        Examples:
            >>> await client.key_combination('ctrl', 'c')         # Ctrl+C
            >>> await client.key_combination('ctrl', 'shift', 'n')  # Ctrl+Shift+N
            >>> await client.key_combination('alt', 'f4')         # Alt+F4
            >>> await client.key_combination('ctrl', 'alt', 'delete')  # Ctrl+Alt+Delete
            >>> await client.key_combination('win', 'r')          # Win+R
        """
        if not keys:
            return

        # 按下所有按键
        for key in keys:
            await self.key_down(key)
            await asyncio.sleep(0.01)  # 短暂延迟确保按键顺序

        await asyncio.sleep(duration)

        # 按相反顺序释放所有按键
        for key in reversed(keys):
            await self.key_up(key)
            await asyncio.sleep(0.01)

        logger.debug(f"组合键: {'+'.join(str(k) for k in keys)}")

    async def take_screenshot(self, filename: Optional[str] = None) -> Image.Image:
        """截取屏幕

        Args:
            filename: 保存文件名，如果为 None 则只返回 PIL 图像对象

        Returns:
            PIL 图像对象
        """
        if not self._handler.server_info:
            raise ScreenshotError("服务器信息未初始化")

        # 请求完整的屏幕更新
        await self._handler.request_screen_update(incremental=False)

        # 接收帧缓冲区更新
        await self._handler.receive_framebuffer_update()

        if not self._handler.framebuffer:
            raise ScreenshotError("帧缓冲区为空")

        # 转换为图像
        image = self._handler.framebuffer_to_image()

        if filename:
            image.save(filename)
            logger.info(f"截图已保存到: {filename}")

        return image
