"""
Servidor HTTP compatível com o front (POST /command), usando o mesmo ambiente
que `pip install ad06-agent`.

Além dos comandos básicos, este bridge expõe as capacidades avançadas do pacote:
mapeamento de ambiente, janelas, processos, permissões, tipos de app e ações
estruturadas via UI Automation.

Parte do pacote PyPI `ad06-agent` (submódulo `agente_ad06.http_bridge`).
"""

from __future__ import annotations

import argparse
import base64
import ctypes
import os
import shutil
import subprocess
import sys
import time
import webbrowser
from dataclasses import asdict
from io import BytesIO
from pathlib import Path
from typing import Any


def _project_root() -> Path:
    """`.env`: ORB_LOGIC_PROJECT_ROOT, cwd, ou raiz do repo (checkout …/agente_ad06/http_bridge/)."""
    r = os.environ.get("ORB_LOGIC_PROJECT_ROOT", "").strip()
    if r:
        return Path(r).resolve()
    cwd = Path.cwd()
    if (cwd / ".env").is_file():
        return cwd
    p = Path(__file__).resolve()
    cand = p.parent.parent.parent  # http_bridge → agente_ad06 → raiz do repositório
    if (cand / ".env").is_file():
        return cand
    return cwd


def _load_dotenv_root() -> None:
    """Carrega `.env` na raiz do projeto (override=False: OS já definido ganha)."""
    try:
        from dotenv import load_dotenv
    except ImportError:
        return
    p = _project_root() / ".env"
    if p.is_file():
        load_dotenv(p, override=False)


_load_dotenv_root()

try:
    from .playwright_tools import list_clickable_elements, perform_click, perform_fill
except ImportError:
    list_clickable_elements = None  # type: ignore[misc, assignment]
    perform_click = None  # type: ignore[misc, assignment]
    perform_fill = None  # type: ignore[misc, assignment]

try:
    from .agent_computer_use import COMPUTER_HANDLERS, browser_session_active, vision_available
except ImportError:
    COMPUTER_HANDLERS = {}  # type: ignore[misc, assignment]

    def browser_session_active() -> bool:
        return False

    def vision_available() -> bool:
        return False

import psutil
import pyautogui
import uiautomation as auto
import uvicorn
from agente_ad06.app_detector import DetectorApp, mapear_tipos_apps_abertos
from agente_ad06.environment_mapper import (
    app_esta_aberto,
    encontrar_elemento_na_janela,
    encontrar_executavel_app,
    mapear_ambiente_completo,
    mapear_janelas_ativas,
    mapear_processos,
    normalizar_coordenadas,
)
from agente_ad06.security_manager import (
    e_administrador,
    verificar_pode_automatizar_app,
    verificar_seguranca_completa,
)
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

pyautogui.FAILSAFE = True
pyautogui.PAUSE = 0.2

if sys.platform == "win32":

    class _WINRECT(ctypes.Structure):
        _fields_ = [
            ("left", ctypes.c_long),
            ("top", ctypes.c_long),
            ("right", ctypes.c_long),
            ("bottom", ctypes.c_long),
        ]

else:
    _WINRECT = None  # type: ignore[misc, assignment]

APP_MAP = {
    "notepad": "notepad.exe",
    "bloco de notas": "notepad.exe",
    "calc": "calc.exe",
    "calculadora": "calc.exe",
    "calculator": "calc.exe",
    "explorer": "explorer.exe",
    "explorador": "explorer.exe",
    "file explorer": "explorer.exe",
    "cmd": "cmd.exe",
    "terminal": "cmd.exe",
    "command prompt": "cmd.exe",
    "powershell": "powershell.exe",
    "paint": "mspaint.exe",
    "task manager": "taskmgr.exe",
    "gerenciador de tarefas": "taskmgr.exe",
    "settings": "ms-settings:",
    "configurações": "ms-settings:",
    "configuracoes": "ms-settings:",
    "browser": None,
    "navegador": None,
    "chrome": "chrome.exe",
    "firefox": "firefox.exe",
    "edge": "msedge.exe",
    "word": "winword.exe",
    "excel": "excel.exe",
    "powerpoint": "powerpnt.exe",
    "vscode": "code",
    "visual studio code": "code",
}

detector = DetectorApp()

app = FastAPI(title="Ad06 HTTP Command (ad06-agent env)", version="2.0.0")
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)


class ClickRequest(BaseModel):
    x: int
    y: int
    button: str = "left"
    clicks: int = 1
    move_duration: float = 0.12
    click_interval: float = 0.08


class TypeRequest(BaseModel):
    text: str
    interval: float = 0.02


class HotkeyRequest(BaseModel):
    keys: list[str]


class OpenAppRequest(BaseModel):
    app_name: str


class BrowseRequest(BaseModel):
    url: str


class MouseMoveRequest(BaseModel):
    x: int
    y: int
    duration: float = 0.3


class ScrollRequest(BaseModel):
    """clicks = intensidade (passos da roda). direction up/down ajusta o sinal (PyAutoGUI: + = cima, - = baixo)."""

    clicks: int = 3
    x: int | None = None
    y: int | None = None
    direction: str | None = None
    move_duration: float = 0.1


class FileActionRequest(BaseModel):
    action: str
    path: str
    destination: str | None = None
    content: str | None = None


class DesktopCommandRequest(BaseModel):
    action: str
    params: dict = {}


def _pick(params: dict[str, Any], *keys: str, default: Any = None) -> Any:
    for key in keys:
        if key in params and params[key] is not None:
            return params[key]
    return default


def _physical_point(x: int, y: int, *, skip_normalize: bool) -> tuple[int, int, dict[str, Any]]:
    """Converte coordenadas; com skip_normalize=True usa (x,y) diretos (ex.: mesma origem que a captura enviada ao modelo)."""
    if skip_normalize:
        return int(x), int(y), {"fonte": "raw", "escala_usada": 1.0, "skip_normalize": True}
    try:
        c = normalizar_coordenadas(int(x), int(y))
        return (
            int(c["x_normalizado"]),
            int(c["y_normalizado"]),
            {
                "fonte": "normalizado",
                "escala_usada": c.get("escala_usada", 1.0),
            },
        )
    except Exception as exc:
        return int(x), int(y), {"fonte": "raw", "escala_usada": 1.0, "aviso": str(exc)}


def _truthy_skip_normalize(params: dict[str, Any]) -> bool:
    v = _pick(params, "skip_normalize", "raw_coords", "use_raw_coordinates", default=False)
    if isinstance(v, bool):
        return v
    if isinstance(v, str):
        return v.lower() in ("1", "true", "yes", "sim")
    return bool(v)


def _wants_browser_focus(params: dict[str, Any]) -> bool:
    v = _pick(params, "focus_browser", "trazer_navegador_frente", default=False)
    if isinstance(v, bool):
        return v
    if isinstance(v, str):
        return v.lower() not in ("0", "false", "no", "nao", "não")
    return bool(v)


def _try_focus_browser_window() -> dict[str, Any]:
    """Windows: traz para primeiro plano a janela visível maior de Chrome/Edge/Firefox/etc."""
    if sys.platform != "win32":
        return {"ok": False, "reason": "not_windows"}

    user32 = ctypes.windll.user32
    kernel32 = ctypes.windll.kernel32

    browser_exes = frozenset(
        {
            "chrome.exe",
            "msedge.exe",
            "firefox.exe",
            "brave.exe",
            "opera.exe",
            "vivaldi.exe",
            "arc.exe",
            "chromium.exe",
        }
    )

    candidates: list[tuple[int, int, float]] = []

    @ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p)
    def _enum(hwnd_ptr, _lparam):
        hwnd = int(hwnd_ptr)
        if not user32.IsWindowVisible(hwnd):
            return True
        pid = ctypes.c_ulong()
        tid = user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
        if not tid:
            return True
        try:
            proc_name = psutil.Process(pid.value).name().lower()
            if proc_name not in browser_exes:
                return True
        except (psutil.Error, ValueError):
            return True

        rect = _WINRECT()
        if not user32.GetWindowRect(hwnd, ctypes.byref(rect)):
            return True
        w = rect.right - rect.left
        h = rect.bottom - rect.top
        area = w * h
        if area < 6400:
            return True
        try:
            ctime = psutil.Process(pid.value).create_time()
        except (psutil.Error, ValueError):
            ctime = 0.0
        candidates.append((hwnd, area, ctime))
        return True

    user32.EnumWindows(_enum, 0)
    if not candidates:
        return {"ok": False, "reason": "no_browser_window"}

    candidates.sort(key=lambda t: (-t[1], -t[2]))
    hwnd = candidates[0][0]

    SW_RESTORE = 9
    if user32.IsIconic(hwnd):
        user32.ShowWindow(hwnd, SW_RESTORE)
    user32.ShowWindow(hwnd, SW_RESTORE)
    time.sleep(0.06)

    pid = ctypes.c_ulong()
    tid_target = user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
    cur_tid = kernel32.GetCurrentThreadId()

    fg = user32.GetForegroundWindow()
    tid_fg = 0
    if fg:
        tid_fg = user32.GetWindowThreadProcessId(fg, None)

    try:
        if tid_fg and tid_fg != tid_target:
            user32.AttachThreadInput(cur_tid, tid_fg, True)
        user32.AttachThreadInput(cur_tid, tid_target, True)
        user32.BringWindowToTop(hwnd)
        user32.SetForegroundWindow(hwnd)
    except OSError:
        pass
    finally:
        try:
            user32.AttachThreadInput(cur_tid, tid_target, False)
            if tid_fg and tid_fg != tid_target:
                user32.AttachThreadInput(cur_tid, tid_fg, False)
        except OSError:
            pass

    time.sleep(0.15)
    return {"ok": True, "pid": int(pid.value)}


def _resolve_scroll_amount(clicks: int, direction: str | None) -> int:
    """Compatível com LLM: direction=down/baixo força rolagem para baixo (valor negativo no PyAutoGUI)."""
    raw = int(clicks) if clicks else 3
    d = (direction or "").strip().lower()
    mag = abs(raw) if raw != 0 else 3
    if d in ("down", "baixo", "descer"):
        return -mag
    if d in ("up", "cima", "subir"):
        return mag
    return raw


def _normalize_window_payload(params: dict[str, Any]) -> tuple[str, dict[str, Any]]:
    window_name = _pick(params, "janela", "window", "window_title", "windowName", default="")
    action_payload = _pick(params, "acao", "action_payload", "ui_action", default={}) or {}
    return str(window_name), dict(action_payload)


def _detect_window_type(window_name: str) -> dict[str, Any]:
    janela = auto.WindowControl(Name=window_name, searchDepth=1)
    if not janela.Exists(maxSearchSeconds=2):
        return {
            "janela": window_name,
            "status": "janela_nao_encontrada",
            "tipo": "UNKNOWN",
        }

    proc = psutil.Process(janela.ProcessId)
    info = detector.detectar(
        nome_janela=janela.Name or window_name,
        classe_janela=janela.ClassName or "",
        nome_processo=proc.name(),
        exe_path=proc.exe() or "",
    )
    return {
        "janela": janela.Name or window_name,
        "classe": janela.ClassName or "",
        "processo": proc.name(),
        "pid": janela.ProcessId,
        "tipo": info.tipo,
        "abordagem": info.abordagem,
        "confianca": info.confianca,
        "detalhes": info.detalhes,
        "fallback": info.fallback,
        "pode_uia": info.pode_usar_uia,
        "requer_visao": info.requer_visao,
        "requer_playwright": info.requer_playwright,
        "dicas": info.dicas,
    }


def _execute_via_uia(window_name: str, action_payload: dict[str, Any]) -> dict[str, Any]:
    try:
        janela = auto.WindowControl(Name=window_name, searchDepth=1)
        if not janela.Exists(maxSearchSeconds=5):
            return {"status": "janela_nao_encontrada", "janela": window_name}

        action_type = action_payload.get("tipo", "")

        if action_type == "clicar_botao":
            element = janela.ButtonControl(Name=action_payload["elemento"])
            if not element.Exists(maxSearchSeconds=3):
                return {"status": "elemento_nao_encontrado", "elemento": action_payload["elemento"]}
            element.Click()
            return {"status": "ok", "acao": action_type}

        if action_type == "digitar_texto":
            field_name = action_payload.get("elemento", "")
            field = janela.EditControl(Name=field_name) if field_name else janela.EditControl()
            field.Click()
            field.SetValue(action_payload["texto"])
            return {"status": "ok", "acao": action_type}

        if action_type == "clicar_menu":
            menu = janela.MenuControl(Name=action_payload["menu"])
            menu.Click()
            time.sleep(0.35)
            item = janela.MenuItemControl(Name=action_payload["item"])
            item.Click()
            return {"status": "ok", "acao": action_type}

        if action_type == "selecionar_lista":
            lista = janela.ListControl(Name=action_payload.get("elemento", ""))
            item = lista.ListItemControl(Name=action_payload["valor"])
            item.Click()
            return {"status": "ok", "acao": action_type}

        if action_type == "pressionar_tecla":
            janela.SendKeys(action_payload["tecla"])
            return {"status": "ok", "acao": action_type}

        if action_type == "marcar_checkbox":
            checkbox = janela.CheckBoxControl(Name=action_payload["elemento"])
            if checkbox.GetTogglePattern().ToggleState != 1:
                checkbox.Click()
            return {"status": "ok", "acao": action_type}

        if action_type == "selecionar_combobox":
            combo = janela.ComboBoxControl(Name=action_payload.get("elemento", ""))
            combo.Click()
            time.sleep(0.2)
            item = combo.ListItemControl(Name=action_payload["valor"])
            item.Click()
            return {"status": "ok", "acao": action_type}

        if action_type == "rolar_scroll":
            direction = action_payload.get("direcao", "baixo")
            element = janela.Control(Name=action_payload["elemento"]) if action_payload.get("elemento") else janela
            rect = element.BoundingRectangle
            center_x = rect.left + rect.width() // 2
            center_y = rect.top + rect.height() // 2
            delta = -120 if direction == "baixo" else 120
            pyautogui.scroll(delta, x=center_x, y=center_y)
            return {"status": "ok", "acao": action_type}

        if action_type == "ler_texto":
            element = janela.Control(Name=action_payload.get("elemento", ""))
            text = element.Name or ""
            try:
                value_pattern = element.GetValuePattern()
                if value_pattern:
                    text = value_pattern.Value or text
            except Exception:
                pass
            return {"status": "ok", "acao": action_type, "texto": text}

        if action_type == "verificar_elemento":
            element = janela.Control(Name=action_payload["elemento"])
            exists = element.Exists(maxSearchSeconds=2)
            return {"status": "ok", "existe": exists, "elemento": action_payload["elemento"]}

        return {"status": "tipo_desconhecido", "tipo": action_type}
    except Exception as exc:
        return {
            "status": "falha_ui_automation",
            "erro": str(exc),
            "requer_visao": True,
        }


def _capture_screenshot(monitor: int = 1) -> dict[str, Any]:
    image = pyautogui.screenshot()
    buffer = BytesIO()
    image.save(buffer, format="PNG")
    return {
        "success": True,
        "width": image.width,
        "height": image.height,
        "monitor": monitor,
        "image_base64": base64.b64encode(buffer.getvalue()).decode("utf-8"),
    }


def _execute_visual(x: int, y: int, visual_action: str = "clicar", text: str = "") -> dict[str, Any]:
    coords = normalizar_coordenadas(x, y)
    norm_x = coords["x_normalizado"]
    norm_y = coords["y_normalizado"]

    if visual_action == "clicar":
        pyautogui.moveTo(norm_x, norm_y, duration=0.25)
        pyautogui.click()
    elif visual_action == "duplo_clique":
        pyautogui.doubleClick(norm_x, norm_y)
    elif visual_action == "clicar_direito":
        pyautogui.rightClick(norm_x, norm_y)
    elif visual_action == "digitar":
        pyautogui.click(norm_x, norm_y)
        pyautogui.typewrite(text, interval=0.05)
    else:
        raise HTTPException(status_code=400, detail=f"Ação visual desconhecida: {visual_action}")

    return {
        "status": "ok",
        "acao": f"visual_{visual_action}",
        "coords": [norm_x, norm_y],
        "escala_usada": coords.get("escala_usada", 1.0),
    }


@app.get("/")
async def root():
    return {
        "agent": "ad06-agent HTTP bridge",
        "status": "online",
        "platform": sys.platform,
        "advanced_commands": True,
        "note": "Compatível com POST /command do Orb Logic",
    }


@app.get("/health")
async def health():
    screen = pyautogui.size()
    security = verificar_seguranca_completa()
    try:
        from .playwright_tools import _PLAYWRIGHT_IMPORT_OK as _pw_ok
    except ImportError:
        _pw_ok = False
    return {
        "status": "healthy",
        "screen_width": screen.width,
        "screen_height": screen.height,
        "platform": sys.platform,
        "is_admin": security.e_admin,
        "integrity_level": security.nivel_integridade,
        "playwright_import_ok": _pw_ok,
        "computer_use_browser_session": browser_session_active(),
        "xai_vision_configured": vision_available(),
    }


@app.post("/command")
async def execute_command(req: DesktopCommandRequest):
    action = req.action.lower().strip()
    params = req.params or {}

    try:
        if action in {"open_app", "abrir_app"}:
            name = _pick(params, "app_name", "app", "nome", default="")
            return await _open_app(OpenAppRequest(app_name=str(name)))

        if action == "close_app":
            return await _close_app(str(_pick(params, "app_name", "app", default="")))

        if action == "click":
            focus_meta = _try_focus_browser_window() if _wants_browser_focus(params) else None
            if focus_meta is not None and not focus_meta.get("ok"):
                time.sleep(0.25)
            out = await _mouse_click(
                ClickRequest(
                    x=int(_pick(params, "x", default=0)),
                    y=int(_pick(params, "y", default=0)),
                    button=str(_pick(params, "button", default="left")),
                    clicks=int(_pick(params, "clicks", default=1)),
                    move_duration=float(_pick(params, "move_duration", "duration", default=0.12)),
                    click_interval=float(_pick(params, "click_interval", "interval", default=0.08)),
                ),
                skip_normalize=_truthy_skip_normalize(params),
            )
            if focus_meta is not None:
                out["browser_focus"] = focus_meta
            return out

        if action == "move_mouse":
            return await _mouse_move(
                MouseMoveRequest(
                    x=int(_pick(params, "x", default=0)),
                    y=int(_pick(params, "y", default=0)),
                    duration=float(_pick(params, "duration", default=0.3)),
                )
            )

        if action == "type":
            return await _keyboard_type(
                TypeRequest(
                    text=str(_pick(params, "text", "texto", default="")),
                    interval=float(_pick(params, "interval", default=0.02)),
                )
            )

        if action == "hotkey":
            return await _keyboard_hotkey(HotkeyRequest(keys=list(_pick(params, "keys", default=[]))))

        if action == "press_key":
            return await _keyboard_press(str(_pick(params, "key", "tecla", default="")))

        if action == "scroll":
            focus_meta = _try_focus_browser_window() if _wants_browser_focus(params) else None
            if focus_meta is not None and not focus_meta.get("ok"):
                time.sleep(0.25)
            out = await _mouse_scroll(
                ScrollRequest(
                    clicks=int(_pick(params, "clicks", "amount", "passos", default=3)),
                    x=_pick(params, "x"),
                    y=_pick(params, "y"),
                    direction=_pick(params, "direction", "direcao"),
                    move_duration=float(_pick(params, "move_duration", "duration", default=0.1)),
                ),
                skip_normalize=_truthy_skip_normalize(params),
            )
            if focus_meta is not None:
                out["browser_focus"] = focus_meta
            return out

        if action == "open_url":
            return await _browser_open(BrowseRequest(url=str(_pick(params, "url", default=""))))

        if action == "file":
            return await _file_action(
                FileActionRequest(
                    action=str(_pick(params, "file_action", "action", default="list")),
                    path=str(_pick(params, "path", default=".")),
                    destination=_pick(params, "destination"),
                    content=_pick(params, "content"),
                )
            )

        if action in {"screenshot", "capturar_tela"}:
            return _capture_screenshot(int(_pick(params, "monitor", default=1)))

        if action == "screen_info":
            return await _screen_info()

        if action == "mouse_position":
            return await _mouse_position()

        if action in {"scan", "scan_environment", "map_environment", "mapear_ambiente"}:
            include_children = bool(_pick(params, "com_elementos_filhos", "include_children", default=False))
            return {
                "success": True,
                "action": action,
                "dados": mapear_ambiente_completo(com_elementos_filhos=include_children),
            }

        if action in {"list_windows", "mapear_janelas"}:
            include_children = bool(_pick(params, "com_filhos", "include_children", default=False))
            max_depth = int(_pick(params, "max_profundidade", "max_depth", default=3))
            return {
                "success": True,
                "action": action,
                "janelas": mapear_janelas_ativas(
                    com_elementos_filhos=include_children,
                    max_profundidade=max_depth,
                ),
            }

        if action in {"find_element", "encontrar_elemento"}:
            window_name = str(_pick(params, "janela", "window", "window_title", default=""))
            element_name = str(_pick(params, "elemento", "element", default=""))
            element_type = _pick(params, "tipo_elemento", "element_type")
            element = encontrar_elemento_na_janela(window_name, element_name, element_type)
            return {
                "success": True,
                "action": action,
                "encontrado": element is not None,
                "dados": element,
            }

        if action in {"list_processes", "listar_processos"}:
            return {"success": True, "action": action, "processos": mapear_processos()}

        if action in {"app_types", "tipos_apps", "detect_open_app_types"}:
            return {"success": True, "action": action, "apps": mapear_tipos_apps_abertos()}

        if action in {"security_status", "verificar_seguranca", "get_security_context"}:
            return {"success": True, "action": action, "dados": asdict(verificar_seguranca_completa())}

        if action in {"verify_permission", "verificar_permissao"}:
            process_name = str(_pick(params, "processo", "process", "app_name", default=""))
            return {
                "success": True,
                "action": action,
                "dados": verificar_pode_automatizar_app(process_name),
            }

        if action in {"execute_action", "executar_acao"}:
            window_name, action_payload = _normalize_window_payload(params)
            result = _execute_via_uia(window_name, action_payload)
            if result.get("requer_visao"):
                result["screenshot"] = _capture_screenshot().get("image_base64", "")
                result["janela_info"] = _detect_window_type(window_name)
            else:
                result["janela_info"] = _detect_window_type(window_name)
            result["success"] = result.get("status") == "ok"
            return result

        if action in {"visual_fallback", "fallback_visual"}:
            return _execute_visual(
                int(_pick(params, "x", default=0)),
                int(_pick(params, "y", default=0)),
                str(_pick(params, "acao_visual", "visual_action", default="clicar")),
                str(_pick(params, "texto", "text", default="")),
            )

        if action in {"detect_window_type", "inspect_window"}:
            window_name = str(_pick(params, "janela", "window", "window_title", default=""))
            return {"success": True, "action": action, "dados": _detect_window_type(window_name)}

        if action == "ping":
            return {
                "success": True,
                "action": "ping",
                "ts": time.time(),
                "admin": e_administrador(),
            }

        if action in {"focus_browser", "trazer_navegador", "focusar_navegador"}:
            info = _try_focus_browser_window()
            return {"success": bool(info.get("ok")), "action": "focus_browser", **info}

        if action in {
            "playwright_elements",
            "web_page_elements",
            "list_web_elements",
            "playwright_snapshot",
        }:
            if list_clickable_elements is None:
                return {
                    "success": False,
                    "action": action,
                    "error": "playwright_module_missing",
                    "hint": "Módulo playwright_tools não disponível (pip install -U ad06-agent ou playwright).",
                }
            return await list_clickable_elements(
                str(_pick(params, "url", default="")),
                max_elements=int(_pick(params, "max_elements", "limit", default=80)),
                timeout_ms=int(_pick(params, "timeout_ms", "timeout", default=35_000)),
                wait_until=str(_pick(params, "wait_until", default="domcontentloaded")),
                viewport_width=int(_pick(params, "viewport_width", default=1365)),
                viewport_height=int(_pick(params, "viewport_height", default=900)),
            )

        if action in {"playwright_click", "web_click"}:
            if perform_click is None:
                return {
                    "success": False,
                    "action": action,
                    "error": "playwright_module_missing",
                    "hint": "Módulo playwright_tools não disponível.",
                }
            return await perform_click(
                str(_pick(params, "url", default="")),
                selector=_pick(params, "selector", default=None),
                href=_pick(params, "href", default=None),
                match_text=_pick(params, "match_text", "text", "name", default=None),
                role=_pick(params, "role", default=None),
                timeout_ms=int(_pick(params, "timeout_ms", "timeout", default=25_000)),
                wait_until=str(_pick(params, "wait_until", default="domcontentloaded")),
                viewport_width=int(_pick(params, "viewport_width", default=1365)),
                viewport_height=int(_pick(params, "viewport_height", default=900)),
            )

        if action in {"playwright_fill", "web_fill"}:
            if perform_fill is None:
                return {
                    "success": False,
                    "action": action,
                    "error": "playwright_module_missing",
                    "hint": "Módulo playwright_tools não disponível.",
                }
            return await perform_fill(
                str(_pick(params, "url", default="")),
                selector=_pick(params, "selector", default=None),
                match_placeholder=_pick(params, "match_placeholder", "placeholder", default=None),
                text=str(_pick(params, "text", "value", default="")),
                timeout_ms=int(_pick(params, "timeout_ms", "timeout", default=25_000)),
                wait_until=str(_pick(params, "wait_until", default="domcontentloaded")),
                viewport_width=int(_pick(params, "viewport_width", default=1365)),
                viewport_height=int(_pick(params, "viewport_height", default=900)),
                clear_first=bool(_pick(params, "clear_first", default=True)),
            )

        if COMPUTER_HANDLERS and action in COMPUTER_HANDLERS:
            return await COMPUTER_HANDLERS[action](params)

        raise HTTPException(status_code=400, detail=f"Ação desconhecida: {action}")
    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc


async def _mouse_click(req: ClickRequest, *, skip_normalize: bool = False):
    nx, ny, meta = _physical_point(req.x, req.y, skip_normalize=skip_normalize)
    btn = (req.button or "left").lower()
    if btn not in ("left", "right", "middle"):
        btn = "left"
    n = max(1, min(5, int(req.clicks)))
    dur = max(0.0, float(req.move_duration))
    gap = max(0.02, float(req.click_interval))
    pyautogui.moveTo(nx, ny, duration=dur)
    time.sleep(0.03)
    pyautogui.click(button=btn, clicks=n, interval=gap)
    return {
        "success": True,
        "action": "click",
        "position": {"x": nx, "y": ny},
        "pedido": {"x": req.x, "y": req.y},
        "normalizacao": meta,
        "button": btn,
        "clicks": n,
    }


async def _mouse_move(req: MouseMoveRequest):
    pyautogui.moveTo(req.x, req.y, duration=req.duration)
    return {"success": True, "action": "move", "position": {"x": req.x, "y": req.y}}


async def _mouse_scroll(req: ScrollRequest, *, skip_normalize: bool = False):
    amount = _resolve_scroll_amount(req.clicks, req.direction)
    mag = min(25, max(1, abs(amount))) * (1 if amount >= 0 else -1)
    meta: dict[str, Any] | None = None
    md = max(0.0, float(req.move_duration))
    if req.x is not None and req.y is not None:
        nx, ny, meta = _physical_point(int(req.x), int(req.y), skip_normalize=skip_normalize)
        pyautogui.moveTo(nx, ny, duration=md)
        time.sleep(0.025)
        pyautogui.scroll(mag, x=nx, y=ny)
    else:
        sz = pyautogui.size()
        cx, cy = int(sz.width) // 2, int(sz.height) // 2
        pyautogui.moveTo(cx, cy, duration=max(0.08, md))
        time.sleep(0.05)
        pyautogui.scroll(mag, x=cx, y=cy)
    return {
        "success": True,
        "action": "scroll",
        "clicks_enviados": mag,
        "direction": req.direction,
        "normalizacao": meta,
    }


async def _mouse_position():
    pos = pyautogui.position()
    return {"x": pos.x, "y": pos.y}


async def _keyboard_type(req: TypeRequest):
    try:
        pyautogui.typewrite(req.text, interval=req.interval)
        return {"success": True, "action": "type", "length": len(req.text)}
    except Exception:
        try:
            import pyperclip

            pyperclip.copy(req.text)
            pyautogui.hotkey("ctrl", "v")
            return {"success": True, "action": "type_clipboard", "length": len(req.text)}
        except Exception as exc:
            raise HTTPException(status_code=500, detail=str(exc)) from exc


async def _keyboard_hotkey(req: HotkeyRequest):
    pyautogui.hotkey(*req.keys)
    return {"success": True, "action": "hotkey", "keys": req.keys}


async def _keyboard_press(key: str):
    pyautogui.press(key)
    return {"success": True, "action": "press", "key": key}


async def _open_app(req: OpenAppRequest):
    app_name = req.app_name.strip()
    if not app_name:
        raise HTTPException(status_code=400, detail="Nome do app é obrigatório")

    already_open = app_esta_aberto(app_name)
    if already_open:
        return {
            "success": True,
            "status": "ja_aberto",
            "app": app_name,
            "processo": already_open,
        }

    app_key = app_name.lower()
    mapped_command = APP_MAP.get(app_key)

    if mapped_command is None and app_key in APP_MAP:
        webbrowser.open("https://www.google.com")
        return {"success": True, "status": "ok", "app": app_name, "method": "webbrowser"}

    command = mapped_command or encontrar_executavel_app(app_name) or app_name

    try:
        if ":" in str(command) and not str(command).endswith(".exe"):
            if sys.platform == "win32":
                os.startfile(str(command))
            else:
                subprocess.Popen(["xdg-open", str(command)])
            return {"success": True, "status": "ok", "app": app_name, "method": "uri_scheme", "command": command}

        subprocess.Popen(str(command), shell=True)
        time.sleep(2)

        confirmed = app_esta_aberto(app_name) or app_esta_aberto(Path(str(command)).stem)
        if confirmed:
            return {
                "success": True,
                "status": "ok",
                "app": app_name,
                "command": str(command),
                "processo": confirmed,
            }

        return {
            "success": False,
            "status": "launch_not_confirmed",
            "app": app_name,
            "command": str(command),
            "detail": "O comando foi disparado, mas o processo não foi detectado em seguida.",
        }
    except Exception as exc:
        raise HTTPException(status_code=500, detail=f"App não encontrado: {app_name}. Erro: {exc}") from exc


async def _close_app(app_name: str):
    if not app_name:
        raise HTTPException(status_code=400, detail="Nome do app é obrigatório")
    if sys.platform == "win32":
        result = subprocess.run(["taskkill", "/IM", app_name, "/F"], capture_output=True, text=True)
    else:
        result = subprocess.run(["pkill", "-f", app_name], capture_output=True, text=True)
    return {
        "success": result.returncode == 0,
        "action": "close",
        "app": app_name,
        "stdout": result.stdout.strip(),
        "stderr": result.stderr.strip(),
    }


async def _browser_open(req: BrowseRequest):
    if not req.url:
        raise HTTPException(status_code=400, detail="URL é obrigatória")
    webbrowser.open(req.url)
    return {"success": True, "url": req.url}


async def _file_action(req: FileActionRequest):
    path = Path(req.path).expanduser()
    if req.action == "list":
        if not path.exists():
            raise HTTPException(status_code=404, detail=f"Caminho não existe: {req.path}")
        items = []
        for item in path.iterdir():
            items.append(
                {
                    "name": item.name,
                    "is_dir": item.is_dir(),
                    "size": item.stat().st_size if item.is_file() else None,
                    "modified": item.stat().st_mtime,
                }
            )
        return {"success": True, "path": str(path), "items": items}
    if req.action == "read":
        if not path.is_file():
            raise HTTPException(status_code=404, detail=f"Arquivo não encontrado: {req.path}")
        content = path.read_text(encoding="utf-8", errors="replace")
        return {"success": True, "path": str(path), "content": content[:10000]}
    if req.action == "write":
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(req.content or "", encoding="utf-8")
        return {"success": True, "path": str(path), "action": "write"}
    if req.action == "copy":
        if not req.destination:
            raise HTTPException(status_code=400, detail="Destino necessário para cópia")
        dest = Path(req.destination).expanduser()
        if path.is_dir():
            shutil.copytree(str(path), str(dest))
        else:
            shutil.copy2(str(path), str(dest))
        return {"success": True, "from": str(path), "to": str(dest)}
    if req.action == "move":
        if not req.destination:
            raise HTTPException(status_code=400, detail="Destino necessário para mover")
        dest = Path(req.destination).expanduser()
        shutil.move(str(path), str(dest))
        return {"success": True, "from": str(path), "to": str(dest)}
    if req.action == "delete":
        if path.is_dir():
            shutil.rmtree(str(path))
        elif path.is_file():
            path.unlink()
        else:
            raise HTTPException(status_code=404, detail="Caminho não encontrado")
        return {"success": True, "deleted": str(path)}
    if req.action == "mkdir":
        path.mkdir(parents=True, exist_ok=True)
        return {"success": True, "created": str(path)}
    raise HTTPException(status_code=400, detail=f"Ação desconhecida: {req.action}")


async def _screen_info():
    size = pyautogui.size()
    return {"width": size.width, "height": size.height}


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--host", default="127.0.0.1")
    parser.add_argument("--port", type=int, default=8765)
    args = parser.parse_args()
    print(f"Ad06 HTTP /command em http://{args.host}:{args.port}  (docs: /docs)")
    uvicorn.run(app, host=args.host, port=args.port, log_level="info")


if __name__ == "__main__":
    main()
