# -*- coding: utf-8 -*-
"""
设备控制类 - 核心操作接口
"""

import json
import time
import base64
import logging
import threading
from io import BytesIO
from typing import Optional, List, Dict, Any, Union

try:
    from PIL import Image
    HAS_PIL = True
except ImportError:
    HAS_PIL = False

try:
    import websocket
    HAS_WEBSOCKET = True
except ImportError:
    HAS_WEBSOCKET = False

from .element import Element
from .exceptions import (
    ConnectionError, TimeoutError, ElementNotFoundError,
    MethodNotSupportedError
)
from .constants import (
    METHOD_AUTO, METHOD_ACCESSIBILITY, METHOD_HID, METHOD_ADB,
    KEYS, DEFAULT_PORT, DEFAULT_TIMEOUT,
    DEFAULT_SWIPE_DURATION, DEFAULT_LONG_PRESS_DURATION,
    CMD_CLICK, CMD_SWIPE, CMD_LONG_PRESS, CMD_INPUT_TEXT,
    CMD_PRESS_KEY, CMD_FIND_ELEMENT, CMD_FIND_ELEMENTS,
    CMD_CLICK_ELEMENT, CMD_SCREENSHOT, CMD_START_APP, CMD_STOP_APP,
    CMD_GET_CURRENT_APP, CMD_GET_DEVICE_INFO, CMD_WAKE_UP,
    CMD_LOCK_SCREEN, CMD_GET_SCREEN_SIZE, CMD_INSTALL_APP, CMD_UNINSTALL_APP
)
from .utils import random_offset, random_duration

logger = logging.getLogger("qunkong")


class Device:
    """
    设备控制类
    
    提供统一的设备操作接口，支持无障碍、ADB、HID三种控制方式
    
    Example:
        >>> device = Device(ip="192.168.1.100")
        >>> device.click(500, 800)
        >>> device.swipe(500, 1500, 500, 500)
        >>> device.input_text("Hello")
    """
    
    def __init__(
        self,
        ip: str = None,
        port: int = DEFAULT_PORT,
        device_id: str = None,
        api_key: str = None,
        info: dict = None,
        timeout: int = DEFAULT_TIMEOUT,
        auto_connect: bool = True,
        use_http: bool = True,
        mode: str = "direct",  # direct=直连, relay=中继
        relay_server: str = None  # 中继服务器地址
    ):
        """
        初始化设备
        
        Args:
            ip: 设备IP地址（直连模式）
            port: 设备端口（直连模式）
            device_id: 设备ID
            api_key: API密钥
            info: 设备信息
            timeout: 操作超时时间
            auto_connect: 是否自动连接
            use_http: 是否使用HTTP模式
            mode: 连接模式 - direct(直连) 或 relay(中继)
            relay_server: 中继服务器地址
        """
        self.ip = ip
        self.port = port
        self.device_id = device_id or (f"{ip}:{port}" if ip else "unknown")
        self.api_key = api_key
        self.info = info or {}
        self.timeout = timeout
        self.use_http = use_http
        self.mode = mode
        self.relay_server = relay_server.rstrip("/") if relay_server else None
        
        self._ws: Optional[websocket.WebSocket] = None
        self._ws_lock = threading.Lock()
        self._connected = False
        self._screen_size = None
        
        # 根据模式设置URL
        if mode == "relay" and relay_server:
            self._base_url = f"{relay_server}/api/device/{device_id}"
        elif ip:
            self._base_url = f"http://{ip}:{port}"
        else:
            self._base_url = None
        
        # 可用的控制方式
        self._hid_available = False
        self._accessibility_available = True
        self._adb_available = True
        
        if auto_connect and (ip or mode == "relay"):
            self.connect()
    
    def connect(self) -> bool:
        """
        连接设备
        
        Returns:
            是否连接成功
        """
        if self._connected:
            return True
        
        # 中继模式：检查设备在线状态
        if self.mode == "relay":
            try:
                import requests
                response = requests.get(
                    f"{self.relay_server}/api/device/{self.device_id}/online",
                    timeout=self.timeout
                )
                if response.status_code == 200 and response.json().get("online"):
                    self._connected = True
                    logger.info(f"已连接设备(中继): {self.device_id}")
                    self._query_capabilities()
                    return True
                else:
                    raise ConnectionError(f"设备 {self.device_id} 不在线")
            except Exception as e:
                logger.error(f"连接中继失败: {e}")
                raise ConnectionError(f"无法连接中继服务器: {e}")
        
        # 直连模式 - HTTP
        if self.use_http:
            try:
                import requests
                response = requests.get(self._base_url, timeout=self.timeout)
                if response.status_code == 200:
                    self._connected = True
                    logger.info(f"已连接设备(HTTP): {self.ip}:{self.port}")
                    self._query_capabilities()
                    return True
            except Exception as e:
                logger.error(f"连接设备失败: {e}")
                raise ConnectionError(f"无法连接设备 {self.ip}:{self.port}: {e}")
        
        # 直连模式 - WebSocket
        if not HAS_WEBSOCKET:
            raise ImportError("请安装websocket-client: pip install websocket-client")
        
        try:
            ws_url = f"ws://{self.ip}:{self.port}/sdk"
            self._ws = websocket.create_connection(
                ws_url,
                timeout=self.timeout
            )
            self._connected = True
            logger.info(f"已连接设备(WS): {self.ip}:{self.port}")
            self._query_capabilities()
            return True
        except Exception as e:
            logger.error(f"连接设备失败: {e}")
            raise ConnectionError(f"无法连接设备 {self.ip}:{self.port}: {e}")
    
    def disconnect(self):
        """断开连接"""
        if self._ws:
            try:
                self._ws.close()
            except:
                pass
            self._ws = None
        self._connected = False
        logger.info(f"已断开设备: {self.device_id}")
    
    def _query_capabilities(self):
        """查询设备能力"""
        try:
            result = self._send_command({"action": "get_capabilities"})
            if result:
                self._hid_available = result.get("hid", False)
                self._accessibility_available = result.get("accessibility", True)
                self._adb_available = result.get("adb", True)
        except:
            pass
    
    def _send_command(self, command: dict) -> dict:
        """
        发送命令到设备
        
        Args:
            command: 命令字典
        
        Returns:
            设备响应
        """
        if not self._connected:
            self.connect()
        
        # 中继模式：通过中继服务器API发送
        if self.mode == "relay":
            try:
                import requests
                response = requests.post(
                    f"{self.relay_server}/api/device/{self.device_id}/command",
                    json=command,
                    headers={"Authorization": f"Bearer {self.api_key}"} if self.api_key else {},
                    timeout=self.timeout
                )
                return response.json()
            except Exception as e:
                self._connected = False
                raise ConnectionError(f"发送命令失败(中继): {e}")
        
        # 直连模式 - HTTP
        if self.use_http:
            try:
                import requests
                response = requests.post(
                    self._base_url,
                    json=command,
                    timeout=self.timeout
                )
                return response.json()
            except Exception as e:
                self._connected = False
                raise ConnectionError(f"发送命令失败: {e}")
        
        # 直连模式 - WebSocket
        with self._ws_lock:
            try:
                self._ws.send(json.dumps(command))
                response = self._ws.recv()
                return json.loads(response)
            except Exception as e:
                self._connected = False
                raise ConnectionError(f"发送命令失败: {e}")
    
    def _select_method(self, method: str, supported: List[str]) -> str:
        """选择控制方式"""
        if method != METHOD_AUTO:
            if method not in supported:
                raise MethodNotSupportedError(f"方法 {method} 不支持此操作")
            return method
        
        # 自动选择：HID > 无障碍 > ADB
        if METHOD_HID in supported and self._hid_available:
            return METHOD_HID
        elif METHOD_ACCESSIBILITY in supported and self._accessibility_available:
            return METHOD_ACCESSIBILITY
        elif METHOD_ADB in supported and self._adb_available:
            return METHOD_ADB
        else:
            return supported[0] if supported else METHOD_ACCESSIBILITY
    
    # ==================== 触摸操作 ====================
    
    def click(
        self,
        x: int,
        y: int,
        method: str = METHOD_AUTO,
        random_offset_px: int = 0
    ) -> bool:
        """
        点击屏幕坐标
        
        Args:
            x: X坐标
            y: Y坐标
            method: 控制方式 ("auto", "accessibility", "hid", "adb")
            random_offset_px: 随机偏移像素（防检测）
        
        Returns:
            是否成功
        
        Example:
            >>> device.click(500, 800)
            >>> device.click(500, 800, method="hid")  # 指定HID方式
        """
        if random_offset_px > 0:
            x, y = random_offset(x, y, random_offset_px)
        
        method = self._select_method(method, [METHOD_ACCESSIBILITY, METHOD_HID, METHOD_ADB])
        
        result = self._send_command({
            "action": CMD_CLICK,
            "x": x,
            "y": y,
            "method": method
        })
        
        return result.get("success", False)
    
    def double_click(self, x: int, y: int, interval: int = 100, method: str = METHOD_AUTO) -> bool:
        """
        双击
        
        Args:
            x: X坐标
            y: Y坐标
            interval: 两次点击间隔(毫秒)
            method: 控制方式
        """
        self.click(x, y, method=method)
        time.sleep(interval / 1000)
        return self.click(x, y, method=method)
    
    def long_press(
        self,
        x: int,
        y: int,
        duration: int = DEFAULT_LONG_PRESS_DURATION,
        method: str = METHOD_AUTO
    ) -> bool:
        """
        长按
        
        Args:
            x: X坐标
            y: Y坐标
            duration: 按压时长(毫秒)
            method: 控制方式
        
        Example:
            >>> device.long_press(500, 800, duration=2000)
        """
        method = self._select_method(method, [METHOD_ACCESSIBILITY, METHOD_HID, METHOD_ADB])
        
        result = self._send_command({
            "action": CMD_LONG_PRESS,
            "x": x,
            "y": y,
            "duration": duration,
            "method": method
        })
        
        return result.get("success", False)
    
    def swipe(
        self,
        x1: int,
        y1: int,
        x2: int,
        y2: int,
        duration: int = DEFAULT_SWIPE_DURATION,
        method: str = METHOD_AUTO
    ) -> bool:
        """
        滑动
        
        Args:
            x1, y1: 起点坐标
            x2, y2: 终点坐标
            duration: 滑动时长(毫秒)
            method: 控制方式
        
        Example:
            >>> device.swipe(500, 1500, 500, 500)  # 上滑
            >>> device.swipe(500, 500, 500, 1500)  # 下滑
        """
        method = self._select_method(method, [METHOD_ACCESSIBILITY, METHOD_HID, METHOD_ADB])
        
        result = self._send_command({
            "action": CMD_SWIPE,
            "x1": x1,
            "y1": y1,
            "x2": x2,
            "y2": y2,
            "duration": duration,
            "method": method
        })
        
        return result.get("success", False)
    
    def swipe_up(self, duration: int = DEFAULT_SWIPE_DURATION) -> bool:
        """上滑（从下往上）"""
        size = self.get_screen_size()
        cx, cy = size["width"] // 2, size["height"] // 2
        return self.swipe(cx, cy + 400, cx, cy - 400, duration)
    
    def swipe_down(self, duration: int = DEFAULT_SWIPE_DURATION) -> bool:
        """下滑（从上往下）"""
        size = self.get_screen_size()
        cx, cy = size["width"] // 2, size["height"] // 2
        return self.swipe(cx, cy - 400, cx, cy + 400, duration)
    
    def swipe_left(self, duration: int = DEFAULT_SWIPE_DURATION) -> bool:
        """左滑"""
        size = self.get_screen_size()
        cx, cy = size["width"] // 2, size["height"] // 2
        return self.swipe(cx + 300, cy, cx - 300, cy, duration)
    
    def swipe_right(self, duration: int = DEFAULT_SWIPE_DURATION) -> bool:
        """右滑"""
        size = self.get_screen_size()
        cx, cy = size["width"] // 2, size["height"] // 2
        return self.swipe(cx - 300, cy, cx + 300, cy, duration)
    
    def drag(self, x1: int, y1: int, x2: int, y2: int, duration: int = 500) -> bool:
        """
        拖拽
        
        Args:
            x1, y1: 起点坐标
            x2, y2: 终点坐标
            duration: 拖拽时长(毫秒)
        """
        return self.swipe(x1, y1, x2, y2, duration)
    
    # ==================== 输入操作 ====================
    
    def input_text(self, text: str, method: str = METHOD_AUTO) -> bool:
        """
        输入文字
        
        Args:
            text: 要输入的文字
            method: 控制方式
        
        Example:
            >>> device.input_text("Hello World")
        """
        method = self._select_method(method, [METHOD_ACCESSIBILITY, METHOD_HID, METHOD_ADB])
        
        result = self._send_command({
            "action": CMD_INPUT_TEXT,
            "text": text,
            "method": method
        })
        
        return result.get("success", False)
    
    def clear_text(self, length: int = 100) -> bool:
        """
        清空输入框
        
        Args:
            length: 删除字符数
        """
        for _ in range(length):
            self.press_key("delete")
        return True
    
    def press_key(self, key: Union[str, int], method: str = METHOD_AUTO) -> bool:
        """
        按下按键
        
        Args:
            key: 按键名称或keycode
            method: 控制方式
        
        Example:
            >>> device.press_key("back")      # 返回键
            >>> device.press_key("home")      # Home键
            >>> device.press_key("menu")      # 菜单键
            >>> device.press_key(66)          # Enter键(keycode)
        """
        if isinstance(key, str):
            keycode = KEYS.get(key.lower())
            if keycode is None:
                raise ValueError(f"未知按键: {key}")
        else:
            keycode = key
        
        method = self._select_method(method, [METHOD_ACCESSIBILITY, METHOD_HID, METHOD_ADB])
        
        result = self._send_command({
            "action": CMD_PRESS_KEY,
            "keycode": keycode,
            "method": method
        })
        
        return result.get("success", False)
    
    def press_back(self, method: str = METHOD_AUTO) -> bool:
        """按返回键"""
        return self.press_key("back", method=method)
    
    def press_home(self, method: str = METHOD_AUTO) -> bool:
        """按Home键"""
        return self.press_key("home", method=method)
    
    def press_menu(self, method: str = METHOD_AUTO) -> bool:
        """按菜单键"""
        return self.press_key("menu", method=method)
    
    def press_recent(self, method: str = METHOD_AUTO) -> bool:
        """按最近任务键"""
        return self.press_key("recent", method=method)
    
    # ==================== 元素操作 ====================
    
    def find_element(
        self,
        text: str = None,
        resource_id: str = None,
        class_name: str = None,
        content_desc: str = None,
        index: int = 0,
        timeout: float = 0
    ) -> Optional[Element]:
        """
        查找元素
        
        Args:
            text: 元素文字
            resource_id: 元素ID
            class_name: 类名
            content_desc: 内容描述
            index: 索引（多个匹配时）
            timeout: 超时时间（0表示不等待）
        
        Returns:
            Element对象，未找到返回None
        
        Example:
            >>> element = device.find_element(text="登录")
            >>> element = device.find_element(resource_id="com.xxx:id/btn")
        """
        start_time = time.time()
        
        while True:
            result = self._send_command({
                "action": CMD_FIND_ELEMENT,
                "text": text,
                "resource_id": resource_id,
                "class_name": class_name,
                "content_desc": content_desc,
                "index": index
            })
            
            if result.get("found"):
                return Element(
                    device=self,
                    info=result.get("element", {})
                )
            
            if timeout <= 0 or (time.time() - start_time) >= timeout:
                return None
            
            time.sleep(0.5)
    
    def find_elements(
        self,
        text: str = None,
        resource_id: str = None,
        class_name: str = None,
        content_desc: str = None
    ) -> List[Element]:
        """
        查找多个元素
        
        Returns:
            Element列表
        """
        result = self._send_command({
            "action": CMD_FIND_ELEMENTS,
            "text": text,
            "resource_id": resource_id,
            "class_name": class_name,
            "content_desc": content_desc
        })
        
        elements = []
        for elem_info in result.get("elements", []):
            elements.append(Element(device=self, info=elem_info))
        
        return elements
    
    def wait_element(
        self,
        text: str = None,
        resource_id: str = None,
        class_name: str = None,
        content_desc: str = None,
        timeout: float = 10
    ) -> Element:
        """
        等待元素出现
        
        Args:
            timeout: 超时时间(秒)
        
        Returns:
            Element对象
        
        Raises:
            ElementNotFoundError: 超时未找到
        
        Example:
            >>> element = device.wait_element(text="登录成功", timeout=10)
        """
        element = self.find_element(
            text=text,
            resource_id=resource_id,
            class_name=class_name,
            content_desc=content_desc,
            timeout=timeout
        )
        
        if not element:
            raise ElementNotFoundError(
                f"等待元素超时: text={text}, id={resource_id}"
            )
        
        return element
    
    def click_element(
        self,
        text: str = None,
        resource_id: str = None,
        class_name: str = None,
        content_desc: str = None,
        timeout: float = 0
    ) -> bool:
        """
        直接点击元素
        
        Example:
            >>> device.click_element(text="确定")
        """
        element = self.find_element(
            text=text,
            resource_id=resource_id,
            class_name=class_name,
            content_desc=content_desc,
            timeout=timeout
        )
        
        if element:
            return element.click()
        return False
    
    def element_exists(
        self,
        text: str = None,
        resource_id: str = None,
        class_name: str = None,
        content_desc: str = None
    ) -> bool:
        """判断元素是否存在"""
        element = self.find_element(
            text=text,
            resource_id=resource_id,
            class_name=class_name,
            content_desc=content_desc
        )
        return element is not None
    
    # ==================== 屏幕操作 ====================
    
    def screenshot(self) -> Any:
        """
        截图
        
        Returns:
            PIL.Image对象（如果安装了Pillow）或base64字符串
        
        Example:
            >>> img = device.screenshot()
            >>> img.save("screen.png")
        """
        result = self._send_command({"action": CMD_SCREENSHOT})
        
        if result.get("success"):
            img_data = base64.b64decode(result.get("data", ""))
            
            if HAS_PIL:
                return Image.open(BytesIO(img_data))
            else:
                return img_data
        
        return None
    
    def get_screen_size(self) -> dict:
        """
        获取屏幕尺寸
        
        Returns:
            {"width": 1080, "height": 1920}
        """
        if self._screen_size:
            return self._screen_size
        
        result = self._send_command({"action": CMD_GET_SCREEN_SIZE})
        
        if result.get("success"):
            self._screen_size = {
                "width": result.get("width", 1080),
                "height": result.get("height", 1920)
            }
        else:
            self._screen_size = {"width": 1080, "height": 1920}
        
        return self._screen_size
    
    def wake_up(self) -> bool:
        """唤醒屏幕"""
        result = self._send_command({"action": CMD_WAKE_UP})
        return result.get("success", False)
    
    def lock_screen(self) -> bool:
        """锁屏"""
        result = self._send_command({"action": CMD_LOCK_SCREEN})
        return result.get("success", False)
    
    def is_screen_on(self) -> bool:
        """屏幕是否亮着"""
        result = self._send_command({"action": "is_screen_on"})
        return result.get("screen_on", False)
    
    # ==================== 应用操作 ====================
    
    def start_app(self, package_name: str, activity: str = None) -> bool:
        """
        启动应用
        
        Args:
            package_name: 包名
            activity: Activity名（可选）
        
        Example:
            >>> device.start_app("com.tencent.mm")  # 启动微信
        """
        result = self._send_command({
            "action": CMD_START_APP,
            "package": package_name,
            "activity": activity
        })
        return result.get("success", False)
    
    def stop_app(self, package_name: str) -> bool:
        """
        停止应用
        
        Args:
            package_name: 包名
        """
        result = self._send_command({
            "action": CMD_STOP_APP,
            "package": package_name
        })
        return result.get("success", False)
    
    def get_current_app(self) -> dict:
        """
        获取当前前台应用
        
        Returns:
            {"package": "com.xxx", "activity": "MainActivity"}
        """
        result = self._send_command({"action": CMD_GET_CURRENT_APP})
        return {
            "package": result.get("package", ""),
            "activity": result.get("activity", "")
        }
    
    def install_app(self, apk_path: str) -> bool:
        """安装应用"""
        result = self._send_command({
            "action": CMD_INSTALL_APP,
            "path": apk_path
        })
        return result.get("success", False)
    
    def uninstall_app(self, package_name: str) -> bool:
        """卸载应用"""
        result = self._send_command({
            "action": CMD_UNINSTALL_APP,
            "package": package_name
        })
        return result.get("success", False)
    
    def clear_app_data(self, package_name: str) -> bool:
        """清除应用数据"""
        result = self._send_command({
            "action": "clear_app_data",
            "package": package_name
        })
        return result.get("success", False)
    
    def get_installed_apps(self) -> List[str]:
        """获取已安装应用列表"""
        result = self._send_command({"action": "get_installed_apps"})
        return result.get("packages", [])
    
    # ==================== 设备信息 ====================
    
    def get_info(self) -> dict:
        """
        获取设备信息
        
        Returns:
            设备详细信息
        """
        result = self._send_command({"action": CMD_GET_DEVICE_INFO})
        return result
    
    def get_battery(self) -> dict:
        """
        获取电池信息
        
        Returns:
            {"level": 80, "charging": True}
        """
        result = self._send_command({"action": "get_battery"})
        return {
            "level": result.get("level", 0),
            "charging": result.get("charging", False)
        }
    
    def is_online(self) -> bool:
        """检查设备是否在线"""
        try:
            self._send_command({"action": "ping"})
            return True
        except:
            return False
    
    # ==================== 图片识别（模板匹配） ====================
    
    def _get_screenshot_image(self):
        """获取截图的PIL Image对象"""
        if not HAS_PIL:
            raise ImportError("需要安装Pillow: pip install Pillow")
        
        result = self._send_command({"action": CMD_SCREENSHOT})
        
        if result.get("success") and result.get("data"):
            img_data = base64.b64decode(result.get("data", ""))
            return Image.open(BytesIO(img_data))
        
        return None
    
    def find_image(self, template_path: str, threshold: float = 0.8,
                   grayscale: bool = True) -> Optional[dict]:
        """
        在屏幕上查找图片（模板匹配）
        
        Args:
            template_path: 模板图片路径
            threshold: 匹配阈值 (0-1)，越高越严格
            grayscale: 是否转灰度匹配（更快）
        
        Returns:
            找到返回 {"center": (x,y), "box": [x1,y1,x2,y2], "confidence": 0.95}
            否则返回 None
        """
        try:
            import cv2
            import numpy as np
        except ImportError:
            raise ImportError("需要安装OpenCV: pip install opencv-python")
        
        screen = self._get_screenshot_image()
        if not screen:
            return None
        
        screen_array = np.array(screen)
        screen_cv = cv2.cvtColor(screen_array, cv2.COLOR_RGB2BGR)
        
        # 读取模板图片
        template = cv2.imdecode(np.fromfile(template_path, dtype=np.uint8), cv2.IMREAD_COLOR)
        if template is None:
            raise FileNotFoundError(f"模板图片不存在: {template_path}")
        
        if grayscale:
            screen_cv = cv2.cvtColor(screen_cv, cv2.COLOR_BGR2GRAY)
            template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
        
        result = cv2.matchTemplate(screen_cv, template, cv2.TM_CCOEFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
        
        if max_val >= threshold:
            h, w = template.shape[:2]
            x1, y1 = max_loc
            x2, y2 = x1 + w, y1 + h
            center = (x1 + w // 2, y1 + h // 2)
            
            return {
                "center": center,
                "box": [x1, y1, x2, y2],
                "confidence": max_val
            }
        
        return None
    
    def find_all_images(self, template_path: str, threshold: float = 0.8,
                        grayscale: bool = True, max_count: int = 10) -> List[dict]:
        """查找屏幕上所有匹配的图片"""
        try:
            import cv2
            import numpy as np
        except ImportError:
            raise ImportError("需要安装OpenCV: pip install opencv-python")
        
        screen = self._get_screenshot_image()
        if not screen:
            return []
        
        screen_array = np.array(screen)
        screen_cv = cv2.cvtColor(screen_array, cv2.COLOR_RGB2BGR)
        
        template = cv2.imdecode(np.fromfile(template_path, dtype=np.uint8), cv2.IMREAD_COLOR)
        if template is None:
            raise FileNotFoundError(f"模板图片不存在: {template_path}")
        
        if grayscale:
            screen_cv = cv2.cvtColor(screen_cv, cv2.COLOR_BGR2GRAY)
            template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
        
        h, w = template.shape[:2]
        result = cv2.matchTemplate(screen_cv, template, cv2.TM_CCOEFF_NORMED)
        
        locations = np.where(result >= threshold)
        matches = []
        
        for pt in zip(*locations[::-1]):
            x1, y1 = pt
            x2, y2 = x1 + w, y1 + h
            center = (x1 + w // 2, y1 + h // 2)
            conf = result[y1, x1]
            
            # 避免重复（NMS简化版）
            is_duplicate = False
            for m in matches:
                if abs(m['center'][0] - center[0]) < w // 2 and \
                   abs(m['center'][1] - center[1]) < h // 2:
                    is_duplicate = True
                    break
            
            if not is_duplicate:
                matches.append({
                    "center": center,
                    "box": [int(x1), int(y1), int(x2), int(y2)],
                    "confidence": float(conf)
                })
            
            if len(matches) >= max_count:
                break
        
        matches.sort(key=lambda x: x['confidence'], reverse=True)
        return matches
    
    def click_image(self, template_path: str, threshold: float = 0.8,
                    timeout: float = 0) -> bool:
        """点击匹配的图片"""
        import time
        start_time = time.time()
        
        while True:
            result = self.find_image(template_path, threshold=threshold)
            if result:
                self.click(*result['center'])
                logger.info(f"点击图片 {template_path} at {result['center']}")
                return True
            
            if timeout <= 0 or (time.time() - start_time) >= timeout:
                break
            
            time.sleep(0.5)
        
        logger.warning(f"未找到图片: {template_path}")
        return False
    
    def wait_for_image(self, template_path: str, timeout: float = 10,
                       threshold: float = 0.8) -> bool:
        """等待图片出现"""
        import time
        start_time = time.time()
        
        while (time.time() - start_time) < timeout:
            result = self.find_image(template_path, threshold=threshold)
            if result:
                logger.info(f"找到图片 {template_path} at {result['center']}")
                return True
            time.sleep(0.5)
        
        return False
    
    def image_exists(self, template_path: str, threshold: float = 0.8) -> bool:
        """检查图片是否存在于屏幕上"""
        return self.find_image(template_path, threshold=threshold) is not None
    
    def save_screenshot_region(self, box: tuple, save_path: str) -> bool:
        """
        保存屏幕区域作为模板图片
        
        Args:
            box: (x1, y1, x2, y2) 区域坐标
            save_path: 保存路径
        """
        if not HAS_PIL:
            raise ImportError("需要安装Pillow: pip install Pillow")
        
        screen = self._get_screenshot_image()
        if not screen:
            return False
        
        cropped = screen.crop(box)
        cropped.save(save_path)
        logger.info(f"保存截图区域: {save_path}")
        return True

    # ==================== OCR文字识别 ====================
    
    def ocr(self, region: tuple = None) -> List[Dict]:
        """
        OCR识别屏幕上的文字
        
        Args:
            region: 可选，识别区域 (x1, y1, x2, y2)
        
        Returns:
            识别到的文字列表，每项包含 text, box, confidence
        """
        try:
            from rapidocr_onnxruntime import RapidOCR
        except ImportError:
            raise ImportError("需要安装rapidocr: pip install rapidocr_onnxruntime")
        
        screen = self._get_screenshot_image()
        if not screen:
            return []
        
        if region:
            screen = screen.crop(region)
        
        # 初始化OCR（懒加载）
        if not hasattr(self, '_ocr_reader'):
            self._ocr_reader = RapidOCR()
        
        import numpy as np
        img_array = np.array(screen)
        results, _ = self._ocr_reader(img_array)
        
        ocr_results = []
        if results:
            for item in results:
                box, text, confidence = item
                x1, y1 = int(box[0][0]), int(box[0][1])
                x2, y2 = int(box[2][0]), int(box[2][1])
                ocr_results.append({
                    'text': text,
                    'box': [x1, y1, x2, y2],
                    'center': ((x1 + x2) // 2, (y1 + y2) // 2),
                    'confidence': confidence
                })
        
        logger.info(f"OCR识别到 {len(ocr_results)} 个文字区域")
        return ocr_results
    
    def find_text(self, text: str, region: tuple = None, 
                  partial: bool = True) -> Optional[Dict]:
        """
        查找屏幕上的文字
        
        Args:
            text: 要查找的文字
            region: 可选，搜索区域
            partial: 是否部分匹配
        
        Returns:
            找到的文字信息，包含 text, box, center
        """
        results = self.ocr(region)
        for r in results:
            if partial:
                if text in r['text']:
                    return r
            else:
                if text == r['text']:
                    return r
        return None
    
    def click_text(self, text: str, region: tuple = None,
                   partial: bool = True, timeout: float = 10) -> bool:
        """
        点击屏幕上的文字
        
        Args:
            text: 要点击的文字
            region: 可选，搜索区域
            partial: 是否部分匹配
            timeout: 超时时间
        
        Returns:
            是否成功点击
        """
        start_time = time.time()
        
        while (time.time() - start_time) < timeout:
            result = self.find_text(text, region, partial)
            if result:
                self.click(*result['center'])
                logger.info(f"点击文字 '{text}' at {result['center']}")
                return True
            time.sleep(0.5)
        
        logger.warning(f"未找到文字: {text}")
        return False
    
    def text_exists(self, text: str, region: tuple = None,
                    partial: bool = True) -> bool:
        """检查文字是否存在于屏幕上"""
        return self.find_text(text, region, partial) is not None
    
    def wait_text(self, text: str, timeout: float = 30,
                  region: tuple = None, partial: bool = True) -> bool:
        """
        等待文字出现
        
        Args:
            text: 等待的文字
            timeout: 超时时间
            region: 可选，搜索区域
            partial: 是否部分匹配
        
        Returns:
            是否找到文字
        """
        start_time = time.time()
        
        while (time.time() - start_time) < timeout:
            if self.find_text(text, region, partial):
                logger.info(f"找到文字: {text}")
                return True
            time.sleep(0.5)
        
        logger.warning(f"等待文字超时: {text}")
        return False

    # ==================== 上下文管理器 ====================
    
    def __enter__(self):
        self.connect()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.disconnect()
    
    def __repr__(self):
        return f"<Device {self.device_id} ({self.ip}:{self.port})>"
