"""Computer use tool — desktop automation via mouse, keyboard, and screen capture.

Hybrid approach: Electron HTTP delegation (preferred) + pyautogui fallback.
Follows the same delegation pattern as screenshot.py.
"""

from __future__ import annotations

import asyncio
import json
import platform
import sys
import urllib.error
import urllib.request
from pathlib import Path
from typing import Any

from loguru import logger

from flowly.agent.tools.base import Tool

# Electron desktop app API discovery file (shared with screenshot tool)
_ELECTRON_API_FILE = Path.home() / ".flowly" / "electron-api.json"

_PLATFORM = sys.platform  # "darwin", "linux", "win32"


# ---------------------------------------------------------------------------
# Key combo parsing
# ---------------------------------------------------------------------------

# Platform-aware aliases: key_name → {platform: native_name} or str (universal)
_KEY_ALIASES: dict[str, dict[str, str] | str] = {
    # Modifier aliases
    "cmd": {"darwin": "command", "linux": "ctrl", "win32": "ctrl"},
    "command": {"darwin": "command", "linux": "ctrl", "win32": "ctrl"},
    "ctrl": "ctrl",
    "control": "ctrl",
    "alt": "alt",
    "option": {"darwin": "option", "linux": "alt", "win32": "alt"},
    "shift": "shift",
    "super": {"darwin": "command", "linux": "super", "win32": "win"},
    "win": {"darwin": "command", "linux": "super", "win32": "win"},
    # Common keys
    "enter": "return",
    "return": "return",
    "esc": "escape",
    "escape": "escape",
    "del": "delete",
    "delete": "delete",
    "backspace": "backspace",
    "tab": "tab",
    "space": "space",
    "up": "up",
    "down": "down",
    "left": "left",
    "right": "right",
    "home": "home",
    "end": "end",
    "pageup": "pageup",
    "pagedown": "pagedown",
}


def _parse_key_combo(keys: str) -> list[str]:
    """Parse 'cmd+shift+c' into platform-native key list ['command', 'shift', 'c']."""
    parts = [k.strip().lower() for k in keys.split("+") if k.strip()]
    result = []
    for part in parts:
        alias = _KEY_ALIASES.get(part)
        if alias is None:
            result.append(part)
        elif isinstance(alias, str):
            result.append(alias)
        else:
            result.append(alias.get(_PLATFORM, part))
    return result


# ---------------------------------------------------------------------------
# ComputerTool
# ---------------------------------------------------------------------------

class ComputerTool(Tool):
    """Desktop automation: mouse, keyboard, and screen capture."""

    def __init__(self, config: Any = None, screenshot_tool: Any = None):
        from flowly.config.schema import ComputerConfig
        self._config = config or ComputerConfig()
        self._screenshot_tool = screenshot_tool

    @property
    def name(self) -> str:
        return "computer"

    @property
    def description(self) -> str:
        return (
            "Control the desktop — mouse, keyboard, and screen capture. "
            "Use this to interact with any application as a human would.\n\n"
            "Actions:\n"
            "- screenshot: Capture the screen (reuses screenshot tool)\n"
            "- click: Click at coordinates (x, y, button=left/right/middle)\n"
            "- double_click: Double-click at coordinates (x, y)\n"
            "- move: Move mouse cursor (x, y)\n"
            "- type: Type text string (text)\n"
            "- key: Press key combination (keys, e.g. 'cmd+c', 'enter', 'alt+tab')\n"
            "- scroll: Scroll at position (direction=up/down, amount=3, x?, y?)\n"
            "- drag: Drag from A to B (start_x, start_y, end_x, end_y)\n"
            "- cursor_position: Get current mouse position\n"
            "- screen_size: Get screen dimensions\n\n"
            "Workflow: screenshot → analyze → act → screenshot → verify. Repeat until done."
        )

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "description": "The action to perform",
                    "enum": [
                        "screenshot", "click", "double_click", "move",
                        "type", "key", "scroll", "drag",
                        "cursor_position", "screen_size",
                    ],
                },
                "x": {"type": "integer", "description": "X coordinate"},
                "y": {"type": "integer", "description": "Y coordinate"},
                "button": {
                    "type": "string",
                    "enum": ["left", "right", "middle"],
                    "description": "Mouse button (default: left)",
                },
                "text": {"type": "string", "description": "Text to type"},
                "keys": {
                    "type": "string",
                    "description": "Key combo: 'cmd+c', 'enter', 'alt+tab', 'f1'",
                },
                "direction": {
                    "type": "string",
                    "enum": ["up", "down", "left", "right"],
                    "description": "Scroll direction (default: down)",
                },
                "amount": {
                    "type": "integer",
                    "description": "Scroll clicks (default: 3)",
                },
                "start_x": {"type": "integer", "description": "Drag start X"},
                "start_y": {"type": "integer", "description": "Drag start Y"},
                "end_x": {"type": "integer", "description": "Drag end X"},
                "end_y": {"type": "integer", "description": "Drag end Y"},
                "display": {"type": "integer", "description": "Display index for screenshot (default: 0)"},
                "filename": {"type": "string", "description": "Screenshot filename"},
            },
            "required": ["action"],
        }

    async def execute(self, action: str = "", **kwargs: Any) -> str:
        if not self._config.enabled:
            return json.dumps({"error": "Computer use is disabled. Enable in config: tools.computer.enabled = true"})

        handlers = {
            "screenshot": self._screenshot,
            "click": self._click,
            "double_click": self._double_click,
            "move": self._move,
            "type": self._type,
            "key": self._key,
            "scroll": self._scroll,
            "drag": self._drag,
            "cursor_position": self._cursor_position,
            "screen_size": self._screen_size,
        }
        handler = handlers.get(action)
        if not handler:
            return json.dumps({"error": f"Unknown action: {action}. Valid: {list(handlers)}"})

        try:
            return await handler(**kwargs)
        except Exception as exc:
            logger.error("Computer {} error: {}", action, exc)
            return json.dumps({"error": str(exc), "action": action})

    # -- Actions -----------------------------------------------------------

    async def _screenshot(self, **kw: Any) -> str:
        if self._screenshot_tool:
            return await self._screenshot_tool.execute(
                display=kw.get("display", 0),
                filename=kw.get("filename"),
                format=kw.get("format", "png"),
            )
        return json.dumps({"error": "Screenshot tool not available"})

    async def _click(self, x: int = 0, y: int = 0, button: str = "left", **_: Any) -> str:
        if not x and not y:
            return json.dumps({"error": "x and y coordinates required"})
        return await self._dispatch("click", {"x": int(x), "y": int(y), "button": button})

    async def _double_click(self, x: int = 0, y: int = 0, **_: Any) -> str:
        if not x and not y:
            return json.dumps({"error": "x and y coordinates required"})
        return await self._dispatch("double_click", {"x": int(x), "y": int(y)})

    async def _move(self, x: int = 0, y: int = 0, **_: Any) -> str:
        return await self._dispatch("move", {"x": int(x), "y": int(y)})

    async def _type(self, text: str = "", **_: Any) -> str:
        if not text:
            return json.dumps({"error": "text is required"})
        return await self._dispatch("type", {"text": text})

    async def _key(self, keys: str = "", **_: Any) -> str:
        if not keys:
            return json.dumps({"error": "keys is required (e.g. 'cmd+c', 'enter')"})
        return await self._dispatch("key", {"keys": keys})

    async def _scroll(
        self, direction: str = "down", amount: int = 3,
        x: int | None = None, y: int | None = None, **_: Any,
    ) -> str:
        params: dict[str, Any] = {"direction": direction, "amount": int(amount)}
        if x is not None:
            params["x"] = int(x)
        if y is not None:
            params["y"] = int(y)
        return await self._dispatch("scroll", params)

    async def _drag(
        self, start_x: int = 0, start_y: int = 0,
        end_x: int = 0, end_y: int = 0, **_: Any,
    ) -> str:
        if not (start_x or start_y or end_x or end_y):
            return json.dumps({"error": "start_x, start_y, end_x, end_y required"})
        return await self._dispatch("drag", {
            "start_x": int(start_x), "start_y": int(start_y),
            "end_x": int(end_x), "end_y": int(end_y),
        })

    async def _cursor_position(self, **_: Any) -> str:
        return await self._dispatch("cursor_position", {})

    async def _screen_size(self, **_: Any) -> str:
        return await self._dispatch("screen_size", {})

    # -- Dispatch: Electron first, pyautogui fallback ----------------------

    async def _dispatch(self, action: str, params: dict) -> str:
        # Try Electron delegation first
        if self._config.prefer_electron:
            result = await asyncio.to_thread(self._execute_electron, action, params)
            if result is not None:
                return json.dumps({"action": action, **result})

        # Fallback to pyautogui
        result = await asyncio.to_thread(self._execute_pyautogui, action, params)
        return json.dumps({"action": action, **result})

    # -- Electron delegation -----------------------------------------------

    def _execute_electron(self, action: str, params: dict) -> dict | None:
        """Send action to Electron HTTP API. Returns result dict or None if unavailable."""
        if not _ELECTRON_API_FILE.exists():
            return None

        try:
            api_data = json.loads(_ELECTRON_API_FILE.read_text())
            port = int(api_data["port"])
            token = str(api_data["token"])
        except (ValueError, KeyError, json.JSONDecodeError, OSError):
            return None

        payload = json.dumps({"action": action, **params}).encode()
        req = urllib.request.Request(
            f"http://127.0.0.1:{port}/input",
            data=payload,
            headers={
                "Content-Type": "application/json",
                "Authorization": f"Bearer {token}",
            },
            method="POST",
        )

        try:
            with urllib.request.urlopen(req, timeout=10) as resp:
                return json.loads(resp.read())
        except urllib.error.HTTPError as e:
            if e.code == 401:
                logger.debug("Electron input auth failed")
            elif e.code == 404:
                logger.debug("Electron /input endpoint not found (upgrade Electron app)")
            return None
        except (urllib.error.URLError, OSError):
            return None
        except Exception as e:
            logger.debug("Electron input delegation failed: {}", e)
            return None

    # -- pyautogui fallback ------------------------------------------------

    def _execute_pyautogui(self, action: str, params: dict) -> dict:
        """Execute action via pyautogui. Returns result dict."""
        try:
            import pyautogui
        except ImportError:
            return {
                "error": (
                    "pyautogui not installed. "
                    "Run: pip install pyautogui "
                    "OR enable Electron desktop app for delegation."
                )
            }

        pyautogui.FAILSAFE = self._config.failsafe
        pyautogui.PAUSE = self._config.action_delay_ms / 1000

        try:
            if action == "click":
                pyautogui.click(params["x"], params["y"],
                                button=params.get("button", "left"))
            elif action == "double_click":
                pyautogui.doubleClick(params["x"], params["y"])
            elif action == "move":
                pyautogui.moveTo(params["x"], params["y"])
            elif action == "type":
                pyautogui.write(params["text"], interval=0.02)
            elif action == "key":
                keys = _parse_key_combo(params["keys"])
                pyautogui.hotkey(*keys)
            elif action == "scroll":
                direction = params.get("direction", "down")
                amount = params.get("amount", 3)
                clicks = -amount if direction == "down" else amount
                pyautogui.scroll(clicks,
                                 x=params.get("x"), y=params.get("y"))
            elif action == "drag":
                pyautogui.moveTo(params["start_x"], params["start_y"])
                pyautogui.drag(
                    params["end_x"] - params["start_x"],
                    params["end_y"] - params["start_y"],
                    duration=0.5,
                )
            elif action == "cursor_position":
                pos = pyautogui.position()
                return {"success": True, "x": pos.x, "y": pos.y}
            elif action == "screen_size":
                size = pyautogui.size()
                return {"success": True, "width": size.width, "height": size.height}
            else:
                return {"error": f"Unknown pyautogui action: {action}"}

            return {"success": True}

        except pyautogui.FailSafeException:
            return {"error": "Failsafe triggered — mouse moved to screen corner"}
        except Exception as e:
            return {"error": f"pyautogui error: {e}"}
