"""
工作手机SDK v3.0 - ADB设备控制服务
直接通过ADB命令控制设备（兼容模拟器）
"""

import asyncio
import subprocess
import base64
import logging
import shlex
import time
import io
from concurrent.futures import ThreadPoolExecutor
from typing import Dict, Any, List, Optional
from datetime import datetime

logger = logging.getLogger(__name__)

_adb_executor = ThreadPoolExecutor(max_workers=4, thread_name_prefix="adb")

import os as _os
_adb_env = _os.environ.copy()
if not _adb_env.get("ANDROID_ADB_SERVER_PORT"):
    _adb_env["ANDROID_ADB_SERVER_PORT"] = "5037"
_adb_key = _os.path.expanduser("~/.android/adbkey")
if _os.path.exists(_adb_key):
    _adb_env["ADB_VENDOR_KEYS"] = _adb_key


class ADBDevice:
    """ADB设备控制"""
    
    def __init__(self, serial: str):
        self.serial = serial
        self.last_active = datetime.now()
    
    def _adb(self, *args, timeout: int = 30) -> subprocess.CompletedProcess:
        """执行ADB命令"""
        cmd = ["adb", "-s", self.serial] + list(args)
        return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, env=_adb_env)
    
    def _shell(self, cmd: str, timeout: int = 30) -> str:
        """执行shell命令"""
        result = self._adb("shell", cmd, timeout=timeout)
        input_command = cmd.lstrip().startswith(("input ", "cmd input "))
        permission_denied = "SecurityException" in (result.stderr or "") or "INJECT_EVENTS" in (result.stderr or "")
        if input_command and (result.returncode != 0 or permission_denied):
            root_result = self._adb("shell", f"su -c {shlex.quote(cmd)}", timeout=timeout)
            if root_result.returncode == 0:
                return root_result.stdout.strip()
            logger.warning(
                "ADB 输入注入失败，Root 重试仍未通过: serial=%s cmd=%s stderr=%s",
                self.serial,
                cmd.split()[0:2],
                (root_result.stderr or result.stderr or "")[:200],
            )
        return result.stdout.strip()
    
    # ========== 设备信息 ==========
    
    def get_info(self) -> Dict[str, Any]:
        """获取设备信息"""
        try:
            model = self._shell("getprop ro.product.model")
            brand = self._shell("getprop ro.product.brand")
            version = self._shell("getprop ro.build.version.release")
            sdk = self._shell("getprop ro.build.version.sdk")
            size = self._shell("wm size")
            
            # 解析分辨率
            width, height = 1080, 2400
            if "Physical size:" in size:
                dims = size.split("Physical size:")[-1].strip()
                if "x" in dims:
                    width, height = map(int, dims.split("x"))
            
            return {
                "serial": self.serial,
                "model": model or "Unknown",
                "brand": brand or "Unknown",
                "android_version": version or "Unknown",
                "sdk_version": sdk or "Unknown",
                "display": {
                    "width": width,
                    "height": height
                }
            }
        except Exception as e:
            logger.error(f"获取设备信息失败: {e}")
            return {"serial": self.serial, "error": str(e)}
    
    def is_online(self) -> bool:
        """检查设备是否在线"""
        try:
            result = subprocess.run(
                ["adb", "devices"],
                capture_output=True,
                text=True,
                timeout=5,
                env=_adb_env,
            )
            for line in result.stdout.strip().splitlines()[1:]:
                parts = line.split()
                if len(parts) >= 2 and parts[0] == self.serial:
                    return parts[1] == "device"
            return False
        except:
            return False
    
    # ========== 基础操作 ==========
    
    def _ensure_awake_unlocked(self) -> Dict[str, Any]:
        """截图或微信控制前的亮屏/解锁兜底，避免历史黑屏证据。"""
        state = {
            "display_power": "unknown",
            "dreaming_lockscreen": "unknown",
            "actions": [],
        }
        try:
            power = self._shell("dumpsys power | grep -E 'Display Power|mWakefulness|mHoldingDisplaySuspendBlocker'", timeout=5)
            window = self._shell("dumpsys window | grep -E 'mDreamingLockscreen|mShowingLockscreen|mCurrentFocus'", timeout=5)
            state["display_power"] = power.replace("\n", " | ")[:500]
            state["dreaming_lockscreen"] = window.replace("\n", " | ")[:500]

            if "state=OFF" in power or "Asleep" in power or "mWakefulness=Asleep" in power:
                self._shell("input keyevent KEYCODE_WAKEUP", timeout=3)
                state["actions"].append("KEYCODE_WAKEUP")
                time.sleep(0.6)

            # best-effort 解锁；无密码/已授权设备可直接拉起，密码机不会绕过用户安全策略。
            self._shell("wm dismiss-keyguard", timeout=3)
            state["actions"].append("wm dismiss-keyguard")
            time.sleep(0.3)

            window2 = self._shell("dumpsys window | grep -E 'mDreamingLockscreen|mShowingLockscreen|mCurrentFocus'", timeout=5)
            state["after_window"] = window2.replace("\n", " | ")[:500]
            state["success"] = "mDreamingLockscreen=true" not in window2
        except Exception as e:
            state["success"] = False
            state["error"] = str(e)
        return state

    def _png_brightness(self, png_bytes: bytes) -> Dict[str, Any]:
        """返回截图质量指标；Pillow不可用时只返回文件大小。"""
        metrics = {"size": len(png_bytes), "is_black": False, "mean_luma": None, "std_luma": None}
        try:
            from PIL import Image, ImageStat
            img = Image.open(io.BytesIO(png_bytes)).convert("L")
            stat = ImageStat.Stat(img)
            mean_luma = float(stat.mean[0])
            std_luma = float(stat.stddev[0])
            metrics.update({"mean_luma": round(mean_luma, 2), "std_luma": round(std_luma, 2)})
            metrics["is_black"] = mean_luma < 8 and std_luma < 4
        except Exception as e:
            metrics["quality_warning"] = f"brightness_check_failed: {e}"
        return metrics

    def screenshot(self) -> Dict[str, Any]:
        """截图：先亮屏/解锁，再检测黑屏，黑屏不再返回成功证据。"""
        try:
            wake_state = self._ensure_awake_unlocked()
            result = subprocess.run(
                ["adb", "-s", self.serial, "exec-out", "screencap", "-p"],
                capture_output=True,
                timeout=8,
                env=_adb_env,
            )
            
            if result.returncode == 0 and result.stdout and len(result.stdout) > 100:
                quality = self._png_brightness(result.stdout)
                if quality.get("is_black"):
                    return {
                        "code": 409,
                        "message": "截图为黑屏，已判定为无效证据",
                        "detail": {"wake_state": wake_state, "quality": quality},
                    }
                b64 = base64.b64encode(result.stdout).decode('utf-8')
                info = self.get_info()
                return {
                    "code": 200,
                    "data": {
                        "base64": b64,
                        "width": info.get("display", {}).get("width", 1080),
                        "height": info.get("display", {}).get("height", 2400),
                        "size": len(result.stdout),
                        "format": "png",
                        "quality": quality,
                        "wake_state": wake_state,
                    }
                }
            return {"code": 500, "message": "截图失败", "detail": f"returncode={result.returncode}, stdout_len={len(result.stdout)}"}
        except Exception as e:
            return {"code": 500, "message": str(e)}
    
    def click(self, x: int, y: int) -> Dict[str, Any]:
        """点击坐标：执行前亮屏/解锁，避免锁屏点击落空。"""
        try:
            self._ensure_awake_unlocked()
            self._shell(f"input tap {x} {y}")
            self.last_active = datetime.now()
            return {"code": 200, "data": {"success": True, "x": x, "y": y}}
        except Exception as e:
            return {"code": 500, "message": str(e)}
    
    def click_text(self, text: str, timeout: float = 15) -> Dict[str, Any]:
        """点击文字：先亮屏/解锁，再通过UI树查找，支持text/content-desc/模糊匹配。"""
        try:
            self._ensure_awake_unlocked()
            import re
            try:
                self._shell("uiautomator dump /sdcard/ui_tree.xml", timeout=10)
                xml = self._shell("cat /sdcard/ui_tree.xml", timeout=5)
            except (subprocess.TimeoutExpired, Exception) as te:
                return {"code": 408, "message": f"UI树获取超时，跳过点击: {text}"}
            
            if not xml or len(xml) < 50:
                return {"code": 404, "message": f"UI树为空，无法查找: {text}"}
            
            # 1. 精确匹配 text 属性
            pattern = f'text="{re.escape(text)}"[^>]*bounds="\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]"'
            match = re.search(pattern, xml)
            if match:
                x1, y1, x2, y2 = map(int, match.groups())
                return self.click((x1 + x2) // 2, (y1 + y2) // 2)
            
            # 2. 精确匹配 content-desc 属性
            pattern_desc = f'content-desc="{re.escape(text)}"[^>]*bounds="\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]"'
            match = re.search(pattern_desc, xml)
            if match:
                x1, y1, x2, y2 = map(int, match.groups())
                return self.click((x1 + x2) // 2, (y1 + y2) // 2)
            
            # 3. 模糊匹配：包含目标文字
            pattern_fuzzy = f'text="[^"]*{re.escape(text)}[^"]*"[^>]*bounds="\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]"'
            match = re.search(pattern_fuzzy, xml)
            if match:
                x1, y1, x2, y2 = map(int, match.groups())
                return self.click((x1 + x2) // 2, (y1 + y2) // 2)
            
            # 4. 不区分大小写匹配
            pattern_ci = f'(?i)text="[^"]*{re.escape(text)}[^"]*"[^>]*bounds="\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]"'
            match = re.search(pattern_ci, xml)
            if match:
                x1, y1, x2, y2 = map(int, match.groups())
                return self.click((x1 + x2) // 2, (y1 + y2) // 2)
            
            return {"code": 404, "message": f"未找到文字: {text}"}
        except subprocess.TimeoutExpired:
            return {"code": 408, "message": f"操作超时: {text}"}
        except Exception as e:
            return {"code": 500, "message": str(e)}
    
    def input_text(self, text: str, clear: bool = False) -> Dict[str, Any]:
        """输入文字：先亮屏/解锁，默认追加，不清除已有内容。"""
        try:
            self._ensure_awake_unlocked()
            if clear:
                # 全选并删除（合并为一条命令减少开销）
                self._shell("input keyevent KEYCODE_CTRL_A && input keyevent KEYCODE_DEL", timeout=10)
            
            escaped = text.replace(" ", "%s").replace("'", "\\'").replace('"', '\\"')
            self._shell(f"input text '{escaped}'", timeout=15)
            
            self.last_active = datetime.now()
            return {"code": 200, "data": {"success": True, "text": text}}
        except Exception as e:
            return {"code": 500, "message": str(e)}
    
    def swipe(self, direction: str, scale: float = 0.5) -> Dict[str, Any]:
        """滑动：先亮屏/解锁，避免锁屏滑动落空。"""
        try:
            self._ensure_awake_unlocked()
            info = self.get_info()
            w = info.get("display", {}).get("width", 1080)
            h = info.get("display", {}).get("height", 2400)
            
            cx, cy = w // 2, h // 2
            dist = int(h * scale * 0.4)
            
            if direction == "up":
                x1, y1, x2, y2 = cx, cy + dist, cx, cy - dist
            elif direction == "down":
                x1, y1, x2, y2 = cx, cy - dist, cx, cy + dist
            elif direction == "left":
                dist = int(w * scale * 0.4)
                x1, y1, x2, y2 = cx + dist, cy, cx - dist, cy
            elif direction == "right":
                dist = int(w * scale * 0.4)
                x1, y1, x2, y2 = cx - dist, cy, cx + dist, cy
            else:
                return {"code": 400, "message": f"无效方向: {direction}"}
            
            self._shell(f"input swipe {x1} {y1} {x2} {y2} 500")
            self.last_active = datetime.now()
            return {"code": 200, "data": {"success": True, "direction": direction}}
        except Exception as e:
            return {"code": 500, "message": str(e)}
    
    def press_key(self, key: str) -> Dict[str, Any]:
        """按键"""
        key_map = {
            "back": "KEYCODE_BACK",
            "home": "KEYCODE_HOME",
            "recent": "KEYCODE_APP_SWITCH",
            "enter": "KEYCODE_ENTER",
            "search": "KEYCODE_SEARCH",
            "tab": "KEYCODE_TAB",
            "del": "KEYCODE_DEL",
            "delete": "KEYCODE_DEL",
            "volume_up": "KEYCODE_VOLUME_UP",
            "volume_down": "KEYCODE_VOLUME_DOWN",
            "power": "KEYCODE_POWER",
            "menu": "KEYCODE_MENU",
            "space": "KEYCODE_SPACE",
        }
        
        keycode = key_map.get(key, key)
        try:
            self._shell(f"input keyevent {keycode}")
            return {"code": 200, "data": {"success": True, "key": key}}
        except Exception as e:
            return {"code": 500, "message": str(e)}
    
    # ========== APP操作 ==========
    
    def start_app(self, package: str) -> Dict[str, Any]:
        """启动APP：先亮屏/解锁，再 resolve launcher activity → am start。"""
        try:
            self._ensure_awake_unlocked()
            activity = self._shell(
                f"cmd package resolve-activity --brief {package} 2>/dev/null | tail -1",
                timeout=10,
            ).strip()
            if activity and "/" in activity:
                result = self._shell(f"am start -n {activity}", timeout=10)
            else:
                result = self._shell(
                    f"monkey -p {package} -c android.intent.category.LAUNCHER 1",
                    timeout=20,
                )
            self.last_active = datetime.now()
            return {"code": 200, "data": {"success": True, "package": package, "activity": activity}}
        except Exception as e:
            return {"code": 500, "message": str(e)}
    
    def stop_app(self, package: str) -> Dict[str, Any]:
        """停止APP"""
        try:
            self._shell(f"am force-stop {package}")
            return {"code": 200, "data": {"success": True, "package": package}}
        except Exception as e:
            return {"code": 500, "message": str(e)}
    
    def current_app(self) -> Dict[str, Any]:
        """获取当前APP"""
        try:
            import re
            output = self._shell("dumpsys activity activities | grep mResumedActivity", timeout=20)
            match = re.search(r'(\w+\.\w+[\.\w]*)/[\.\w]+', output)
            if not match:
                output = self._shell("dumpsys window | grep -E 'mCurrentFocus'", timeout=20)
                match = re.search(r'(\w+\.\w+[\.\w]*)/[\.\w]+', output)
            package = match.group(1) if match else "unknown"
            return {
                "code": 200,
                "data": {
                    "package": package,
                    "raw": output
                }
            }
        except Exception as e:
            return {"code": 500, "message": str(e)}
    
    def installed_apps(self) -> Dict[str, Any]:
        """获取已安装APP"""
        try:
            output = self._shell("pm list packages -3")
            packages = [
                line.replace("package:", "")
                for line in output.split("\n")
                if line.startswith("package:")
            ]
            return {"code": 200, "data": {"packages": packages}}
        except Exception as e:
            return {"code": 500, "message": str(e)}
    
    # ========== UI树 ==========
    
    def get_ui_tree(self) -> Dict[str, Any]:
        """获取UI树"""
        try:
            self._shell("uiautomator dump /sdcard/ui_tree.xml", timeout=15)
            xml = self._shell("cat /sdcard/ui_tree.xml", timeout=10)
            
            if not xml or len(xml) < 50:
                return {"code": 500, "message": "获取UI树失败"}
            
            xml_safe = xml.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
            return {"code": 200, "data": {"xml": xml_safe, "length": len(xml)}}
        except Exception as e:
            return {"code": 500, "message": str(e)}


class ADBDeviceManager:
    """ADB设备管理器"""
    
    _SCAN_CACHE_TTL = 15  # 远程 ADB 空扫描约 3 秒；缓存需覆盖页面与 BFF 连续轮询

    def __init__(self):
        self.devices: Dict[str, ADBDevice] = {}
        self._deviceid_to_serial: Dict[str, str] = {}
        self._scan_cache: Optional[List[str]] = None
        self._scan_cache_time: float = 0
        self._scan_all_cache: Optional[List[Dict[str, str]]] = None
        self._scan_all_cache_time: float = 0
    
    def scan_devices(self) -> List[str]:
        """扫描在线设备（仅已授权），带 TTL 缓存"""
        now = time.time()
        if self._scan_cache is not None and (now - self._scan_cache_time) < self._SCAN_CACHE_TTL:
            return self._scan_cache
        try:
            result = subprocess.run(
                ["adb", "devices"],
                capture_output=True,
                text=True,
                timeout=5,
                env=_adb_env,
            )
            
            serials = []
            for line in result.stdout.strip().split("\n")[1:]:
                parts = line.split()
                if len(parts) >= 2 and parts[1] == "device":
                    serial = parts[0]
                    serials.append(serial)
                    
                    if serial not in self.devices:
                        self.devices[serial] = ADBDevice(serial)
                    self._build_deviceid_map(serial)
            for serial in list(self.devices.keys()):
                if serial not in serials:
                    self.devices.pop(serial, None)
            
            self._scan_cache = serials
            self._scan_cache_time = time.time()
            return serials
        except Exception as e:
            logger.error(f"扫描设备失败: {e}")
            return self._scan_cache or []

    def scan_all_devices(self) -> List[Dict[str, str]]:
        """扫描全部 ADB 设备（含 unauthorized / offline），带 TTL 缓存"""
        now = time.time()
        if self._scan_all_cache is not None and (now - self._scan_all_cache_time) < self._SCAN_CACHE_TTL:
            return self._scan_all_cache
        try:
            result = subprocess.run(
                ["adb", "devices", "-l"],
                capture_output=True, text=True, timeout=5, env=_adb_env,
            )
            devices = []
            for line in result.stdout.strip().split("\n")[1:]:
                line = line.strip()
                if not line:
                    continue
                parts = line.split()
                if len(parts) < 2:
                    continue
                serial = parts[0]
                adb_status = parts[1]
                props = {}
                for p in parts[2:]:
                    if ":" in p:
                        k, v = p.split(":", 1)
                        props[k] = v
                entry = {
                    "serial": serial,
                    "adb_status": adb_status,
                    "model": props.get("model", ""),
                    "device": props.get("device", ""),
                    "transport_id": props.get("transport_id", ""),
                    "usb": props.get("usb", ""),
                    "product": props.get("product", ""),
                    "controllable": adb_status == "device",
                }
                if adb_status == "device" and serial not in self.devices:
                    self.devices[serial] = ADBDevice(serial)
                    self._build_deviceid_map(serial)
                devices.append(entry)
            self._scan_all_cache = devices
            self._scan_all_cache_time = time.time()
            return devices
        except Exception as e:
            logger.warning(f"扫描全部设备失败: {e}")
            return self._scan_all_cache or []

    def _build_deviceid_map(self, serial: str):
        """为 serial 构建 device_id→serial 映射（android_id 哈希 + ro.serialno）"""
        import hashlib
        try:
            r = subprocess.run(
                ["adb", "-s", serial, "shell", "settings", "get", "secure", "android_id"],
                capture_output=True, text=True, timeout=5, env=_adb_env,
            )
            android_id = r.stdout.strip()
            if android_id and android_id != "null":
                device_id = hashlib.md5(android_id.encode()).hexdigest()
                self._deviceid_to_serial[device_id] = serial
        except Exception:
            pass
        try:
            r2 = subprocess.run(
                ["adb", "-s", serial, "shell", "getprop", "ro.serialno"],
                capture_output=True, text=True, timeout=5, env=_adb_env,
            )
            ro_serial = (r2.stdout or "").strip()
            if ro_serial:
                self._deviceid_to_serial[ro_serial] = serial
        except Exception:
            pass

    def register_mapping(self, device_id: str, serial: str):
        """注册 device_id → serial 映射（由 connection_priority 等外部服务调用）"""
        if device_id and serial:
            self._deviceid_to_serial[device_id] = serial

    def resolve_serial(self, identifier: str) -> Optional[str]:
        """将 serial 或 device_id 解析为 ADB serial"""
        self.scan_devices()
        if identifier in self.devices:
            return identifier
        serial = self._deviceid_to_serial.get(identifier)
        if serial in self.devices:
            return serial
        return None

    def get_device(self, identifier: str) -> Optional[ADBDevice]:
        """获取设备（同时接受 serial 和 device_id）"""
        serial = self.resolve_serial(identifier)
        if not serial:
            return None
        if serial not in self.devices:
            if self._check_device(serial):
                self.devices[serial] = ADBDevice(serial)
        return self.devices.get(serial)
    
    def _check_device(self, serial: str) -> bool:
        """检查设备是否存在"""
        try:
            result = subprocess.run(
                ["adb", "-s", serial, "get-state"],
                capture_output=True,
                text=True,
                timeout=5,
                env=_adb_env,
            )
            return result.stdout.strip() == "device"
        except:
            return False
    
    def list_devices(self) -> List[Dict[str, Any]]:
        """列出所有设备"""
        self.scan_devices()
        
        devices = []
        for serial, device in self.devices.items():
            info = device.get_info()
            info["status"] = "online" if device.is_online() else "offline"
            devices.append(info)
        
        return devices

    # ── 异步包装（不阻塞 FastAPI 事件循环） ──

    async def async_scan_devices(self) -> List[str]:
        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(_adb_executor, self.scan_devices)

    async def async_scan_all_devices(self) -> List[Dict[str, str]]:
        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(_adb_executor, self.scan_all_devices)

    async def async_list_devices(self) -> List[Dict[str, Any]]:
        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(_adb_executor, self.list_devices)


# 全局实例
adb_manager = ADBDeviceManager()
