"""DesktopRuntime — controls a desktop via pyautogui + Pillow.

Supports screenshot capture, mouse/keyboard control, and app launching.
Works on macOS, Windows, and Linux with X11.

For cloud desktops accessed via Guacamole, this runtime runs inside
the VNC/RDP target machine and is controlled via the DeviceAgent.
"""

from __future__ import annotations

import asyncio
import io
import logging
import os
import platform
import subprocess
from typing import AsyncIterator

from .base import (
    DeviceCapability,
    DeviceInfo,
    DeviceRuntime,
    DeviceType,
)

logger = logging.getLogger(__name__)


def _detect_device_type() -> DeviceType:
    system = platform.system().lower()
    if system == "darwin":
        return DeviceType.DESKTOP_MACOS
    elif system == "windows":
        return DeviceType.DESKTOP_WINDOWS
    return DeviceType.DESKTOP_LINUX


class DesktopRuntime(DeviceRuntime):
    """Control a local desktop session using pyautogui + Pillow."""

    def __init__(
        self,
        device_type: DeviceType | None = None,
        display: str | None = None,
    ) -> None:
        self._device_type = device_type or _detect_device_type()
        self._display = display
        self._pyautogui = None
        self._screen_size = (1920, 1080)

    async def connect(self) -> None:
        if self._display:
            os.environ["DISPLAY"] = self._display

        try:
            import pyautogui
            pyautogui.FAILSAFE = False
            pyautogui.PAUSE = 0.05
            self._pyautogui = pyautogui
            self._screen_size = pyautogui.size()
            logger.info(
                "DesktopRuntime connected: %dx%d (%s)",
                self._screen_size[0],
                self._screen_size[1],
                self._device_type.value,
            )
        except ImportError:
            logger.warning("pyautogui not installed — desktop control unavailable")
        except Exception as e:
            logger.warning("pyautogui init failed: %s", e)

    async def disconnect(self) -> None:
        self._pyautogui = None

    async def get_info(self) -> DeviceInfo:
        caps = [DeviceCapability.SCREENSHOT, DeviceCapability.KEYBOARD]
        if self._pyautogui:
            caps.extend([DeviceCapability.TOUCH, DeviceCapability.SHELL])

        return DeviceInfo(
            type=self._device_type,
            name=f"{platform.node()} ({platform.system()})",
            os=platform.system(),
            os_version=platform.release(),
            width=self._screen_size[0],
            height=self._screen_size[1],
            capabilities=caps,
            metadata={
                "hostname": platform.node(),
                "arch": platform.machine(),
                "python": platform.python_version(),
            },
        )

    async def screenshot(self) -> bytes:
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(None, self._screenshot_sync)

    def _screenshot_sync(self) -> bytes:
        if self._pyautogui:
            img = self._pyautogui.screenshot()
        else:
            try:
                from PIL import ImageGrab
                img = ImageGrab.grab()
            except Exception:
                return b""

        buf = io.BytesIO()
        img.save(buf, format="PNG")
        return buf.getvalue()

    async def get_ui_tree(self) -> list[dict]:
        # Desktop UI tree is platform-specific; return empty for now
        # Future: integrate with AT-SPI2 (Linux), UIAutomation (Windows), or AXUIElement (macOS)
        return []

    async def tap(self, x: int, y: int) -> None:
        if not self._pyautogui:
            return
        loop = asyncio.get_event_loop()
        await loop.run_in_executor(None, self._pyautogui.click, x, y)

    async def long_press(self, x: int, y: int, duration_ms: int = 1000) -> None:
        if not self._pyautogui:
            return
        loop = asyncio.get_event_loop()

        def _do():
            self._pyautogui.moveTo(x, y)
            self._pyautogui.mouseDown()
            import time
            time.sleep(duration_ms / 1000.0)
            self._pyautogui.mouseUp()

        await loop.run_in_executor(None, _do)

    async def swipe(
        self, x1: int, y1: int, x2: int, y2: int, duration_ms: int = 300
    ) -> None:
        if not self._pyautogui:
            return
        loop = asyncio.get_event_loop()

        def _do():
            self._pyautogui.moveTo(x1, y1)
            self._pyautogui.mouseDown()
            self._pyautogui.moveTo(x2, y2, duration=duration_ms / 1000.0)
            self._pyautogui.mouseUp()

        await loop.run_in_executor(None, _do)

    async def type_text(self, text: str) -> None:
        if not self._pyautogui:
            return
        loop = asyncio.get_event_loop()
        await loop.run_in_executor(None, self._pyautogui.typewrite, text)

    async def press_key(self, keycode: str) -> None:
        if not self._pyautogui:
            return
        key_map = {
            "KEYCODE_BACK": "backspace",
            "KEYCODE_HOME": "win" if platform.system() == "Windows" else "command",
            "KEYCODE_ENTER": "enter",
            "KEYCODE_ESCAPE": "escape",
            "KEYCODE_TAB": "tab",
            "KEYCODE_DEL": "delete",
            "KEYCODE_SPACE": "space",
        }
        key = key_map.get(keycode, keycode.lower().replace("keycode_", ""))
        loop = asyncio.get_event_loop()
        await loop.run_in_executor(None, self._pyautogui.press, key)

    async def open_app(self, package_or_path: str) -> None:
        loop = asyncio.get_event_loop()
        system = platform.system().lower()

        def _do():
            if system == "darwin":
                subprocess.run(["open", "-a", package_or_path], check=True)
            elif system == "windows":
                os.startfile(package_or_path)
            else:
                subprocess.Popen([package_or_path], start_new_session=True)

        await loop.run_in_executor(None, _do)

    async def install_app(self, apk_path: str) -> None:
        logger.warning("install_app not applicable for desktop")

    async def execute_shell(self, command: str) -> str:
        loop = asyncio.get_event_loop()

        def _do():
            result = subprocess.run(
                command, shell=True, capture_output=True, text=True, timeout=30
            )
            return result.stdout + result.stderr

        return await loop.run_in_executor(None, _do)

    async def start_screen_stream(self) -> AsyncIterator[bytes]:
        """Yield screenshots at ~10fps for basic streaming."""
        while True:
            frame = await self.screenshot()
            if frame:
                yield frame
            await asyncio.sleep(0.1)
