"""
╔══════════════════════════════════════════════════════════════╗
║          AGENTE LOCAL DE AUTOMAÇÃO DESKTOP                   ║
║          UI Automation API + Grok xAI                        ║
║          Comando principal: seu-agente                       ║
╚══════════════════════════════════════════════════════════════╝

Comandos disponíveis:
  seu-agente start          → Inicia o agente em background
  seu-agente stop           → Para o agente
  seu-agente status         → Mostra o status atual
  seu-agente config         → Configura URL do servidor e chave API
  seu-agente logs           → Mostra os logs em tempo real
  seu-agente scan           → Escaneia e mostra os apps instalados
  seu-agente restart        → Reinicia o agente
"""

import typer
import asyncio
import websockets
import json
import os
import sys
import time
import signal
import threading
import subprocess
import platform
from pathlib import Path
from typing import Optional
from loguru import logger

# Corrige encoding no terminal Windows (evita erro com emojis/unicode)
if sys.platform == "win32":
    import io
    if hasattr(sys.stdout, "buffer"):
        sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
    if hasattr(sys.stderr, "buffer"):
        sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
    os.environ.setdefault("PYTHONIOENCODING", "utf-8")

# ─────────────────────────────────────────────
# IMPORTS CONDICIONAIS (apenas Windows)
# ─────────────────────────────────────────────
IS_WINDOWS = platform.system() == "Windows"

if IS_WINDOWS:
    import psutil
    import winreg
    import uiautomation as auto
    import pywinauto
    import pyautogui
    import mss
    from PIL import Image
else:
    print("⚠️  AVISO: Este agente foi projetado para Windows 10/11.")
    print("    Algumas funcionalidades não estarão disponíveis nesta plataforma.")

# ─────────────────────────────────────────────
# CONFIGURAÇÃO DE PATHS E ARQUIVOS
# ─────────────────────────────────────────────
APP_DIR     = Path.home() / ".seu-agente"        # ~/.seu-agente/
CONFIG_FILE = APP_DIR / "config.json"             # configurações
PID_FILE    = APP_DIR / "agent.pid"               # PID do processo
LOG_FILE    = APP_DIR / "agent.log"               # arquivo de log

# Garantir que a pasta existe
APP_DIR.mkdir(parents=True, exist_ok=True)

# ─────────────────────────────────────────────
# CONFIGURAÇÃO DO LOGGER
# ─────────────────────────────────────────────
logger.remove()
logger.add(
    LOG_FILE,
    rotation="5 MB",
    retention="7 days",
    format="{time:YYYY-MM-DD HH:mm:ss} | {level:<8} | {message}",
    level="DEBUG"
)
logger.add(
    sys.stderr,
    format="<green>{time:HH:mm:ss}</green> | <level>{level:<8}</level> | {message}",
    level="INFO"
)

# ─────────────────────────────────────────────
# CLI — PONTO DE ENTRADA
# ─────────────────────────────────────────────
app = typer.Typer(
    name="ad06-agent",
    help="Ad06 Agent -- Agente de automacao desktop (UI Automation + Grok xAI)",
    add_completion=False,
    rich_markup_mode=None,
    pretty_exceptions_enable=False,
)


# ══════════════════════════════════════════════
# UTILITÁRIOS
# ══════════════════════════════════════════════

def carregar_config() -> dict:
    """Lê o arquivo de configuração."""
    if CONFIG_FILE.exists():
        with open(CONFIG_FILE, "r") as f:
            return json.load(f)
    return {
        "server_url": "",
        "api_key": "",
        "porta_local": 8765,
        "autostart": False,
        "log_level": "INFO"
    }


def salvar_config(config: dict):
    """Salva o arquivo de configuração."""
    with open(CONFIG_FILE, "w") as f:
        json.dump(config, f, indent=2)


def ler_pid() -> Optional[int]:
    """Lê o PID do agente salvo em arquivo."""
    if PID_FILE.exists():
        try:
            return int(PID_FILE.read_text().strip())
        except:
            return None
    return None


def salvar_pid(pid: int):
    """Salva o PID do processo do agente."""
    PID_FILE.write_text(str(pid))


def remover_pid():
    """Remove o arquivo de PID."""
    if PID_FILE.exists():
        PID_FILE.unlink()


def agente_esta_rodando() -> bool:
    """Verifica se o agente está em execução."""
    if not IS_WINDOWS:
        return False
    pid = ler_pid()
    if pid is None:
        return False
    try:
        proc = psutil.Process(pid)
        return proc.is_running() and proc.status() != psutil.STATUS_ZOMBIE
    except psutil.NoSuchProcess:
        remover_pid()
        return False


def configuracao_valida(config: dict) -> bool:
    """Verifica se a configuração mínima está presente."""
    return bool(config.get("server_url"))


# ══════════════════════════════════════════════
# MAPEAMENTO DO AMBIENTE
# ══════════════════════════════════════════════

def mapear_apps_instalados() -> list:
    """Escaneia o registro do Windows e retorna apps instalados."""
    if not IS_WINDOWS:
        return []

    apps = []
    chaves = [
        r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
        r"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
    ]

    for chave_path in chaves:
        try:
            chave = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, chave_path)
            total = winreg.QueryInfoKey(chave)[0]
            for i in range(total):
                try:
                    sub_nome = winreg.EnumKey(chave, i)
                    sub = winreg.OpenKey(chave, sub_nome)
                    nome, _ = winreg.QueryValueEx(sub, "DisplayName")
                    try:
                        path, _ = winreg.QueryValueEx(sub, "InstallLocation")
                    except:
                        path = ""
                    apps.append({"nome": nome, "path": path})
                except:
                    continue
        except:
            continue

    return apps


def mapear_processos() -> list:
    """Lista todos os processos em execução."""
    if not IS_WINDOWS:
        return []

    processos = []
    for p in psutil.process_iter(["pid", "name", "exe", "status"]):
        try:
            processos.append({
                "pid":    p.info["pid"],
                "nome":   p.info["name"],
                "exe":    p.info["exe"] or "",
                "status": p.info["status"]
            })
        except:
            continue
    return processos


def mapear_janelas() -> list:
    """Lista todas as janelas abertas com seus elementos de acessibilidade."""
    if not IS_WINDOWS:
        return []

    janelas = []
    try:
        for janela in auto.GetRootControl().GetChildren():
            try:
                rect = janela.BoundingRectangle
                janelas.append({
                    "nome":    janela.Name,
                    "tipo":    janela.ControlTypeName,
                    "classe":  janela.ClassName,
                    "pid":     janela.ProcessId,
                    "rect": {
                        "x":      rect.left,
                        "y":      rect.top,
                        "largura": rect.width(),
                        "altura":  rect.height()
                    }
                })
            except:
                continue
    except Exception as e:
        logger.warning(f"Erro ao mapear janelas: {e}")

    return janelas


def mapear_tela() -> dict:
    """Retorna informações da resolução e monitores."""
    if not IS_WINDOWS:
        return {}

    import ctypes
    user32 = ctypes.windll.user32
    return {
        "largura": user32.GetSystemMetrics(0),
        "altura":  user32.GetSystemMetrics(1),
        "dpi":     user32.GetDpiForSystem(),
        "monitores": len(psutil.disk_partitions())
    }


def mapear_ambiente_completo() -> dict:
    """Monta o mapa completo do ambiente do computador."""
    logger.info("Iniciando mapeamento completo do ambiente...")

    ambiente = {
        "sistema": {
            "os":           platform.system(),
            "versao":       platform.version(),
            "arquitetura":  platform.architecture()[0],
            "hostname":     platform.node(),
            "python":       platform.python_version()
        },
        "tela":              mapear_tela(),
        "apps_instalados":   mapear_apps_instalados(),
        "processos":         mapear_processos(),
        "janelas_abertas":   mapear_janelas(),
        "timestamp":         time.strftime("%Y-%m-%d %H:%M:%S")
    }

    logger.info(
        f"Mapeamento concluído — "
        f"{len(ambiente['apps_instalados'])} apps | "
        f"{len(ambiente['processos'])} processos | "
        f"{len(ambiente['janelas_abertas'])} janelas"
    )
    return ambiente


# ══════════════════════════════════════════════
# MOTOR DE EXECUÇÃO — UI AUTOMATION API
# ══════════════════════════════════════════════

class MotorExecucao:
    """Motor híbrido: UI Automation como principal, visão como fallback."""

    def abrir_aplicativo(self, app: str) -> dict:
        """Abre um aplicativo pelo nome ou caminho."""
        if not IS_WINDOWS:
            return {"status": "erro", "detalhe": "Requer Windows"}

        # Verificar se já está rodando
        for proc in psutil.process_iter(["name"]):
            try:
                if app.lower().replace(".exe", "") in proc.info["name"].lower():
                    logger.info(f"App '{app}' já está em execução")
                    return {"status": "ja_aberto", "app": app}
            except:
                continue

        # Abrir o app
        try:
            subprocess.Popen(app, shell=True)
            time.sleep(2)
            logger.success(f"App '{app}' aberto com sucesso")
            return {"status": "ok", "app": app}
        except Exception as e:
            logger.error(f"Erro ao abrir '{app}': {e}")
            return {"status": "erro", "detalhe": str(e)}

    def executar_acao(self, janela_nome: str, acao: dict) -> dict:
        """
        Executa ação via UI Automation API.
        Se falhar, sinaliza para usar fallback visual.
        """
        if not IS_WINDOWS:
            return {"status": "erro", "detalhe": "Requer Windows"}

        try:
            # Encontrar a janela
            janela = auto.WindowControl(Name=janela_nome, searchDepth=1)
            if not janela.Exists(maxSearchSeconds=5):
                return {"status": "janela_nao_encontrada", "janela": janela_nome}

            tipo = acao.get("tipo")
            logger.debug(f"Executando '{tipo}' na janela '{janela_nome}'")

            # ── Clicar em botão
            if tipo == "clicar_botao":
                btn = janela.ButtonControl(Name=acao["elemento"])
                if not btn.Exists(maxSearchSeconds=3):
                    return {"status": "elemento_nao_encontrado", "elemento": acao["elemento"]}
                btn.Click()
                logger.success(f"Clique em botão '{acao['elemento']}' — OK")
                return {"status": "ok", "acao": tipo, "elemento": acao["elemento"]}

            # ── Digitar texto
            elif tipo == "digitar_texto":
                campo = janela.EditControl(Name=acao.get("elemento", ""))
                campo.Click()
                campo.SetValue(acao["texto"])
                logger.success(f"Texto digitado — OK")
                return {"status": "ok", "acao": tipo}

            # ── Navegar em menu
            elif tipo == "clicar_menu":
                menu = janela.MenuControl(Name=acao["menu"])
                menu.Click()
                time.sleep(0.4)
                item = janela.MenuItemControl(Name=acao["item"])
                item.Click()
                logger.success(f"Menu '{acao['menu']} > {acao['item']}' — OK")
                return {"status": "ok", "acao": tipo}

            # ── Selecionar item de lista
            elif tipo == "selecionar_lista":
                lista = janela.ListControl(Name=acao.get("elemento", ""))
                item = lista.ListItemControl(Name=acao["valor"])
                item.Click()
                logger.success(f"Item '{acao['valor']}' selecionado — OK")
                return {"status": "ok", "acao": tipo}

            # ── Pressionar tecla
            elif tipo == "pressionar_tecla":
                janela.SendKeys(acao["tecla"])
                logger.success(f"Tecla '{acao['tecla']}' pressionada — OK")
                return {"status": "ok", "acao": tipo}

            # ── Verificar se elemento existe
            elif tipo == "verificar_elemento":
                el = janela.Control(Name=acao["elemento"])
                existe = el.Exists(maxSearchSeconds=3)
                return {"status": "ok", "existe": existe, "elemento": acao["elemento"]}

            else:
                return {"status": "tipo_desconhecido", "tipo": tipo}

        except Exception as e:
            logger.warning(f"UI Automation falhou: {e} — Acionando fallback visual")
            return {
                "status": "falha_ui_automation",
                "erro": str(e),
                "requer_visao": True
            }

    def capturar_tela(self) -> str:
        """Captura screenshot e retorna em base64."""
        if not IS_WINDOWS:
            return ""

        import base64
        import io

        with mss.mss() as sct:
            shot = sct.grab(sct.monitors[1])
            img = Image.frombytes("RGB", shot.size, shot.bgra, "raw", "BGRX")
            buf = io.BytesIO()
            img.save(buf, format="PNG")
            encoded = base64.b64encode(buf.getvalue()).decode()
            logger.debug("Screenshot capturada")
            return encoded

    def fallback_visual(self, x: int, y: int, acao: str = "clicar") -> dict:
        """Fallback por coordenadas quando UI Automation falha."""
        if not IS_WINDOWS:
            return {"status": "erro", "detalhe": "Requer Windows"}

        try:
            if acao == "clicar":
                pyautogui.moveTo(x, y, duration=0.3)
                pyautogui.click()
                logger.success(f"Clique visual em ({x}, {y}) — OK")
                return {"status": "ok", "acao": "clique_visual", "coords": [x, y]}

            elif acao == "duplo_clique":
                pyautogui.doubleClick(x, y)
                return {"status": "ok", "acao": "duplo_clique_visual"}

            elif acao == "clicar_direito":
                pyautogui.rightClick(x, y)
                return {"status": "ok", "acao": "clique_direito_visual"}

        except Exception as e:
            logger.error(f"Fallback visual falhou: {e}")
            return {"status": "falha_total", "erro": str(e)}


# ══════════════════════════════════════════════
# WEBSOCKET — BRIDGE COM O SERVIDOR
# ══════════════════════════════════════════════

motor = MotorExecucao()


async def websocket_handler(websocket):
    """Gerencia a conexão WebSocket com o servidor."""
    logger.info(f"Servidor conectado: {websocket.remote_address}")

    # Enviar mapa do ambiente ao conectar
    try:
        ambiente = mapear_ambiente_completo()
        await websocket.send(json.dumps({
            "tipo":  "ambiente",
            "dados": ambiente
        }))
        logger.info("Mapa do ambiente enviado ao servidor")
    except Exception as e:
        logger.error(f"Erro ao enviar ambiente: {e}")

    # Loop de recebimento de comandos
    try:
        async for mensagem in websocket:
            try:
                cmd = json.loads(mensagem)
                tipo = cmd.get("tipo")
                logger.info(f"Comando recebido: {tipo}")

                # ── Abrir aplicativo
                if tipo == "abrir_app":
                    resultado = motor.abrir_aplicativo(cmd.get("app", ""))

                # ── Executar ação via UI Automation
                elif tipo == "executar_acao":
                    resultado = motor.executar_acao(
                        cmd.get("janela", ""),
                        cmd.get("acao", {})
                    )
                    # Se falhou → capturar tela para o Grok analisar
                    if resultado.get("requer_visao"):
                        resultado["screenshot"] = motor.capturar_tela()

                # ── Capturar tela sob demanda
                elif tipo == "capturar_tela":
                    resultado = {
                        "tipo":    "screenshot",
                        "imagem":  motor.capturar_tela()
                    }

                # ── Re-escanear ambiente
                elif tipo == "mapear_ambiente":
                    resultado = {
                        "tipo":  "ambiente",
                        "dados": mapear_ambiente_completo()
                    }

                # ── Fallback visual com coordenadas do Grok
                elif tipo == "fallback_visual":
                    resultado = motor.fallback_visual(
                        cmd.get("x", 0),
                        cmd.get("y", 0),
                        cmd.get("acao_visual", "clicar")
                    )

                # ── Ping de keepalive
                elif tipo == "ping":
                    resultado = {"tipo": "pong", "ts": time.time()}

                else:
                    resultado = {"status": "tipo_desconhecido", "tipo": tipo}

                await websocket.send(json.dumps(resultado))

            except json.JSONDecodeError as e:
                logger.error(f"JSON inválido recebido: {e}")
                await websocket.send(json.dumps({
                    "status": "erro",
                    "detalhe": "JSON inválido"
                }))

    except websockets.exceptions.ConnectionClosed:
        logger.warning("Conexão com o servidor encerrada")
    except Exception as e:
        logger.error(f"Erro no handler WebSocket: {e}")


async def http_handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
    """
    Servidor HTTP mínimo para receber POST /execute do backend.
    Aceita: POST /execute {"command": "abrir o bloco de notas"}
    Retorna: {"success": true/false, "message": "..."}
    """
    try:
        # Ler cabeçalhos linha a linha
        headers_raw = b""
        content_length = 0
        metodo = "GET"
        path = "/"

        while True:
            linha = await asyncio.wait_for(reader.readline(), timeout=10)
            if not linha or linha == b"\r\n":
                break
            headers_raw += linha
            linha_str = linha.decode("utf-8", errors="replace").strip()
            if linha_str.startswith("POST ") or linha_str.startswith("GET "):
                partes = linha_str.split(" ")
                metodo = partes[0]
                path   = partes[1] if len(partes) > 1 else "/"
            elif linha_str.lower().startswith("content-length:"):
                try:
                    content_length = int(linha_str.split(":", 1)[1].strip())
                except ValueError:
                    pass

        # Ler body com base no Content-Length
        body_bytes = b""
        if content_length > 0:
            body_bytes = await asyncio.wait_for(reader.readexactly(content_length), timeout=10)

        corpo_raw = body_bytes.decode("utf-8", errors="replace").strip()

        if metodo == "OPTIONS":
            # CORS preflight
            resp_body = ""
            status = "204 No Content"
        elif metodo == "POST" and path == "/execute":
            try:
                corpo = json.loads(corpo_raw) if corpo_raw else {}
                command = str(corpo.get("command", "")).strip()
                logger.info(f"[HTTP] Executando comando: '{command}'")
                resultado = await asyncio.to_thread(_executar_comando_nlp, command)
                resp_body = json.dumps(resultado, ensure_ascii=False)
                status = "200 OK"
            except (json.JSONDecodeError, Exception) as e:
                resp_body = json.dumps({"success": False, "message": str(e)})
                status = "400 Bad Request"
        elif metodo == "GET" and path == "/health":
            resp_body = json.dumps({"status": "ok", "agent": "ad06-agent", "version": "1.0.4"})
            status = "200 OK"
        else:
            resp_body = json.dumps({"error": "Not Found"})
            status = "404 Not Found"

        resp_bytes = resp_body.encode("utf-8") if resp_body else b""
        response = (
            f"HTTP/1.1 {status}\r\n"
            f"Content-Type: application/json\r\n"
            f"Access-Control-Allow-Origin: *\r\n"
            f"Content-Length: {len(resp_bytes)}\r\n"
            f"Connection: close\r\n\r\n"
        ).encode("utf-8") + resp_bytes

        writer.write(response)
        await writer.drain()
    except Exception as e:
        logger.error(f"[HTTP] Erro no handler: {e}")
    finally:
        try:
            writer.close()
        except Exception:
            pass


def _executar_comando_nlp(command: str) -> dict:
    """
    Processa comando em linguagem natural e executa a automação diretamente.
    Mapeamento heurístico rápido + chamada ao motor.
    """
    cmd = command.lower().strip()

    # ── Abrir aplicativo
    mapa_apps = {
        "bloco de notas": "notepad",
        "notepad": "notepad",
        "calculadora": "calc",
        "calc": "calc",
        "paint": "mspaint",
        "word": "winword",
        "excel": "excel",
        "powerpoint": "powerpnt",
        "chrome": "chrome",
        "google chrome": "chrome",
        "firefox": "firefox",
        "edge": "msedge",
        "explorador": "explorer",
        "explorador de arquivos": "explorer",
        "task manager": "taskmgr",
        "gerenciador de tarefas": "taskmgr",
        "cmd": "cmd",
        "powershell": "powershell",
        "spotify": "spotify",
        "discord": "discord",
        "vscode": "code",
        "visual studio code": "code",
        "youtube": "https://youtube.com",
        "google": "https://google.com",
        "gmail": "https://gmail.com",
    }

    # Detectar intenção de abrir app
    palavras_abrir = ["abrir", "abre", "abra", "iniciar", "inicia", "inicialize", "abrir o", "launch", "open"]
    eh_abrir = any(p in cmd for p in palavras_abrir)

    if eh_abrir:
        app_encontrado = None
        for nome, executavel in mapa_apps.items():
            if nome in cmd:
                app_encontrado = executavel
                break

        if not app_encontrado:
            # Tentar extrair o nome após "abrir"
            for palavra in palavras_abrir:
                if palavra in cmd:
                    resto = cmd.split(palavra, 1)[-1].strip()
                    # Remover artigos
                    for artigo in ["o ", "a ", "os ", "as ", "um ", "uma "]:
                        if resto.startswith(artigo):
                            resto = resto[len(artigo):]
                    app_encontrado = resto
                    break

        if app_encontrado:
            if app_encontrado.startswith("http"):
                import subprocess
                subprocess.Popen(f'start "" "{app_encontrado}"', shell=True)
                return {"success": True, "message": f"Abrindo {app_encontrado} no navegador padrão"}
            else:
                resultado = motor.abrir_aplicativo(app_encontrado)
                ok = resultado.get("status") in ("ok", "ja_aberto")
                return {
                    "success": ok,
                    "message": f"App '{app_encontrado}' {'aberto com sucesso' if ok else 'falhou: ' + str(resultado)}"
                }

    # ── Pesquisar no Google/YouTube via browser
    palavras_pesquisa = ["pesquisar", "pesquisa", "buscar", "busca", "search", "procurar"]
    eh_pesquisa = any(p in cmd for p in palavras_pesquisa)

    if eh_pesquisa:
        query = cmd
        for palavra in palavras_pesquisa:
            query = query.replace(palavra, "").strip()
        for prefixo in ["no google", "no youtube", "no bing", "na web", "na internet"]:
            if prefixo in query:
                if "youtube" in prefixo:
                    url = f"https://youtube.com/results?search_query={query.replace(prefixo, '').strip().replace(' ', '+')}"
                else:
                    url = f"https://google.com/search?q={query.replace(prefixo, '').strip().replace(' ', '+')}"
                break
        else:
            url = f"https://google.com/search?q={query.replace(' ', '+')}"

        import subprocess
        subprocess.Popen(f'start "" "{url}"', shell=True)
        return {"success": True, "message": f"Pesquisa aberta: {query}"}

    # ── Digitar texto em janela ativa
    palavras_digitar = ["digitar", "digita", "escrever", "escreva", "escreve", "digite", "type"]
    eh_digitar = any(p in cmd for p in palavras_digitar)

    if eh_digitar:
        texto = cmd
        for palavra in palavras_digitar:
            texto = texto.replace(palavra, "").strip()
        for artigo in [" o texto ", " o ", " a "]:
            texto = texto.replace(artigo, " ").strip()
        if texto:
            import pyautogui
            import time
            time.sleep(0.5)
            pyautogui.typewrite(texto, interval=0.05)
            return {"success": True, "message": f"Texto digitado: '{texto}'"}

    # ── Screenshot
    if any(p in cmd for p in ["screenshot", "print da tela", "capturar tela", "tirar print", "printscreen"]):
        img_b64 = motor.capturar_tela()
        return {"success": True, "message": "Screenshot capturado com sucesso", "screenshot": img_b64[:100] + "..."}

    # ── Fechar app
    if any(p in cmd for p in ["fechar", "feche", "close", "encerrar"]):
        import pyautogui
        pyautogui.hotkey("alt", "F4")
        return {"success": True, "message": "Encerrando janela ativa"}

    # ── Comando não reconhecido — tenta abrir diretamente
    logger.warning(f"[NLP] Comando nao reconhecido: '{command}' — tentando exec direto")
    resultado = motor.abrir_aplicativo(command)
    ok = resultado.get("status") in ("ok", "ja_aberto")
    return {
        "success": ok,
        "message": f"{'Executado' if ok else 'Nao reconhecido'}: {command}"
    }


async def iniciar_servidores(porta: int, server_url: str):
    """Inicia WebSocket + HTTP na mesma porta, discriminando pelo protocolo."""
    logger.info(f"Agente iniciando na porta {porta} (WebSocket + HTTP)...")

    # Servidor HTTP na porta porta+1 (ex: 8766)
    porta_http = porta + 1

    async def _http_server():
        srv = await asyncio.start_server(http_handler, "0.0.0.0", porta_http)
        logger.success(f"[OK] API HTTP ativa em http://localhost:{porta_http}")
        async with srv:
            await srv.serve_forever()

    async with websockets.serve(websocket_handler, "localhost", porta):
        logger.success(f"[OK] Agente WebSocket ativo em ws://localhost:{porta}")
        logger.success(f"[OK] Conectado ao servidor: {server_url}")
        await asyncio.gather(
            asyncio.Future(),   # WebSocket roda para sempre
            _http_server(),     # HTTP também
        )


def _run_agent(config: dict):
    """Roda o agente em thread separada."""
    asyncio.run(iniciar_servidores(
        config.get("porta_local", 8765),
        config.get("server_url", "")
    ))


# ══════════════════════════════════════════════
# REGISTRO NO STARTUP DO WINDOWS
# ══════════════════════════════════════════════

def registrar_startup():
    """Registra o agente para iniciar automaticamente com o Windows."""
    if not IS_WINDOWS:
        return
    try:
        chave = winreg.OpenKey(
            winreg.HKEY_CURRENT_USER,
            r"Software\Microsoft\Windows\CurrentVersion\Run",
            0,
            winreg.KEY_SET_VALUE
        )
        caminho_exe = sys.executable
        winreg.SetValueEx(chave, "SeuAgenteDesktop", 0, winreg.REG_SZ,
                          f'"{caminho_exe}" -m seu_agente.main start')
        logger.info("Agente registrado no startup do Windows")
    except Exception as e:
        logger.warning(f"Não foi possível registrar no startup: {e}")


def remover_startup():
    """Remove o agente do startup do Windows."""
    if not IS_WINDOWS:
        return
    try:
        chave = winreg.OpenKey(
            winreg.HKEY_CURRENT_USER,
            r"Software\Microsoft\Windows\CurrentVersion\Run",
            0,
            winreg.KEY_SET_VALUE
        )
        winreg.DeleteValue(chave, "SeuAgenteDesktop")
        logger.info("Agente removido do startup do Windows")
    except:
        pass


# ══════════════════════════════════════════════
# COMANDOS CLI
# ══════════════════════════════════════════════

@app.command()
def start(
    background: bool = typer.Option(False, "--background", "-b",
                                    help="Iniciar em background (modo silencioso)"),
    autostart:  bool = typer.Option(False, "--autostart", "-a",
                                    help="Iniciar automaticamente com o Windows")
):
    """
    ▶  Inicia o agente de automação desktop.
    """
    typer.echo("\n🤖 Agente de Automação Desktop — UI Automation API + Grok xAI")
    typer.echo("━" * 60)

    # Verificar se já está rodando
    if agente_esta_rodando():
        typer.echo("⚠️  O agente já está em execução.")
        typer.echo(f"   PID: {ler_pid()}")
        typer.echo("   Use 'seu-agente status' para mais detalhes.\n")
        raise typer.Exit(1)

    # Verificar configuração
    config = carregar_config()
    if not configuracao_valida(config):
        typer.echo("❌ Configuração incompleta!")
        typer.echo("   Execute primeiro: seu-agente config --url https://seusite.com\n")
        raise typer.Exit(1)

    # Verificar Windows
    if not IS_WINDOWS:
        typer.echo("❌ Este agente requer Windows 10 ou Windows 11.")
        raise typer.Exit(1)

    typer.echo(f"   Servidor: {config['server_url']}")
    typer.echo(f"   Porta local: {config.get('porta_local', 8765)}")
    typer.echo(f"   Log: {LOG_FILE}")

    # Registrar startup se solicitado
    if autostart:
        registrar_startup()
        typer.echo("   ✅ Registrado no startup do Windows")

    # Salvar PID
    salvar_pid(os.getpid())

    typer.echo("\n✅ Agente iniciado com sucesso!")
    typer.echo("   Pressione Ctrl+C para parar.\n")

    # Handler para Ctrl+C
    def ao_encerrar(sig, frame):
        typer.echo("\n⛔ Encerrando agente...")
        remover_pid()
        logger.info("Agente encerrado pelo usuário")
        sys.exit(0)

    signal.signal(signal.SIGINT, ao_encerrar)
    if IS_WINDOWS:
        signal.signal(signal.SIGTERM, ao_encerrar)

    # Iniciar WebSocket
    logger.info("="*50)
    logger.info("AGENTE INICIADO")
    logger.info("="*50)

    try:
        _run_agent(config)
    except Exception as e:
        logger.error(f"Erro fatal: {e}")
        remover_pid()
        raise


@app.command()
def stop():
    """
    ⏹  Para o agente em execução.
    """
    typer.echo("\n⏹  Parando o agente...")

    if not agente_esta_rodando():
        typer.echo("⚠️  O agente não está em execução.\n")
        raise typer.Exit(0)

    pid = ler_pid()
    try:
        proc = psutil.Process(pid)
        proc.terminate()
        proc.wait(timeout=5)
        remover_pid()
        remover_startup()
        typer.echo(f"✅ Agente parado com sucesso (PID: {pid})\n")
        logger.info(f"Agente encerrado via comando stop (PID: {pid})")
    except psutil.TimeoutExpired:
        proc.kill()
        remover_pid()
        typer.echo("✅ Agente forçado a parar.\n")
    except Exception as e:
        typer.echo(f"❌ Erro ao parar o agente: {e}\n")
        remover_pid()


@app.command()
def restart():
    """
    🔄  Reinicia o agente.
    """
    typer.echo("\n🔄 Reiniciando o agente...")
    stop()
    time.sleep(1)
    start()


@app.command()
def status():
    """
    📊  Mostra o status atual do agente.
    """
    typer.echo("\n📊 Status do Agente de Automação")
    typer.echo("━" * 45)

    config = carregar_config()
    rodando = agente_esta_rodando()

    # Status geral
    if rodando:
        pid = ler_pid()
        typer.echo(f"  Status     : ✅ RODANDO (PID: {pid})")
        if IS_WINDOWS:
            try:
                proc = psutil.Process(pid)
                mem = proc.memory_info().rss / 1024 / 1024
                cpu = proc.cpu_percent(interval=0.5)
                typer.echo(f"  CPU        : {cpu:.1f}%")
                typer.echo(f"  Memória    : {mem:.1f} MB")
                typer.echo(f"  Iniciado   : {time.strftime('%H:%M:%S', time.localtime(proc.create_time()))}")
            except:
                pass
    else:
        typer.echo(f"  Status     : ⛔ PARADO")

    # Configuração
    typer.echo(f"\n  Servidor   : {config.get('server_url') or '⚠️  não configurado'}")
    typer.echo(f"  Porta local: {config.get('porta_local', 8765)}")
    typer.echo(f"  Log level  : {config.get('log_level', 'INFO')}")

    # Sistema
    typer.echo(f"\n  OS         : {platform.system()} {platform.version()[:20]}")
    typer.echo(f"  Python     : {platform.python_version()}")
    typer.echo(f"  Config     : {CONFIG_FILE}")
    typer.echo(f"  Log        : {LOG_FILE}")

    # Ambiente (se rodando)
    if rodando and IS_WINDOWS:
        try:
            n_processos = len(list(psutil.process_iter()))
            n_janelas   = len(auto.GetRootControl().GetChildren())
            typer.echo(f"\n  Processos  : {n_processos}")
            typer.echo(f"  Janelas    : {n_janelas}")
        except:
            pass

    typer.echo("")


@app.command()
def config(
    url:    Optional[str] = typer.Option(None, "--url",    "-u", help="URL do servidor (ex: https://seusite.com)"),
    key:    Optional[str] = typer.Option(None, "--key",    "-k", help="Chave API do servidor"),
    porta:  Optional[int] = typer.Option(None, "--porta",  "-p", help="Porta local do WebSocket (padrão: 8765)"),
    nivel:  Optional[str] = typer.Option(None, "--nivel",  "-n", help="Nível de log: DEBUG, INFO, WARNING"),
    reset:  bool          = typer.Option(False, "--reset",        help="Resetar todas as configurações"),
    mostrar:bool          = typer.Option(False, "--mostrar",      help="Mostrar configuração atual")
):
    """
    ⚙️  Configura o agente (URL do servidor, chave API, porta local).
    """
    cfg = carregar_config()

    if reset:
        cfg = {
            "server_url":   "",
            "api_key":      "",
            "porta_local":  8765,
            "autostart":    False,
            "log_level":    "INFO"
        }
        salvar_config(cfg)
        typer.echo("✅ Configurações resetadas para o padrão.\n")
        return

    if mostrar:
        typer.echo("\n⚙️  Configuração atual:")
        typer.echo("━" * 40)
        typer.echo(f"  URL servidor : {cfg.get('server_url') or '(não definida)'}")
        chave = cfg.get("api_key", "")
        typer.echo(f"  API Key      : {'*' * len(chave) if chave else '(não definida)'}")
        typer.echo(f"  Porta local  : {cfg.get('porta_local', 8765)}")
        typer.echo(f"  Log level    : {cfg.get('log_level', 'INFO')}")
        typer.echo(f"  Autostart    : {'Sim' if cfg.get('autostart') else 'Não'}\n")
        return

    # Aplicar mudanças
    alterado = False

    if url:
        cfg["server_url"] = url.rstrip("/")
        typer.echo(f"✅ URL do servidor: {url}")
        alterado = True

    if key:
        cfg["api_key"] = key
        typer.echo("✅ Chave API configurada")
        alterado = True

    if porta:
        cfg["porta_local"] = porta
        typer.echo(f"✅ Porta local: {porta}")
        alterado = True

    if nivel:
        nivel = nivel.upper()
        if nivel in ["DEBUG", "INFO", "WARNING", "ERROR"]:
            cfg["log_level"] = nivel
            typer.echo(f"✅ Log level: {nivel}")
            alterado = True
        else:
            typer.echo("❌ Nível inválido. Use: DEBUG, INFO, WARNING ou ERROR")

    if alterado:
        salvar_config(cfg)
        typer.echo(f"\n💾 Configurações salvas em: {CONFIG_FILE}\n")
    else:
        typer.echo("ℹ️  Nenhuma configuração alterada.")
        typer.echo("   Use --mostrar para ver a configuração atual.")
        typer.echo("   Use --help para ver as opções disponíveis.\n")


@app.command()
def logs(
    linhas: int  = typer.Option(50,    "--linhas",  "-n", help="Número de linhas a mostrar"),
    seguir: bool = typer.Option(False, "--seguir",  "-f", help="Seguir log em tempo real (como tail -f)")
):
    """
    📋  Mostra os logs do agente.
    """
    if not LOG_FILE.exists():
        typer.echo("⚠️  Nenhum log encontrado ainda.\n")
        raise typer.Exit(0)

    if seguir:
        typer.echo(f"📋 Seguindo logs em tempo real (Ctrl+C para sair)...\n")
        try:
            with open(LOG_FILE, "r", encoding="utf-8") as f:
                f.seek(0, 2)  # ir para o final
                while True:
                    linha = f.readline()
                    if linha:
                        typer.echo(linha, nl=False)
                    else:
                        time.sleep(0.3)
        except KeyboardInterrupt:
            typer.echo("\n")
    else:
        with open(LOG_FILE, "r", encoding="utf-8") as f:
            todas = f.readlines()
        ultimas = todas[-linhas:]
        typer.echo(f"\n📋 Últimas {len(ultimas)} linhas do log:\n")
        typer.echo("━" * 60)
        for linha in ultimas:
            typer.echo(linha, nl=False)
        typer.echo("━" * 60 + "\n")


@app.command()
def scan(
    salvar: bool = typer.Option(False, "--salvar", "-s",
                                help="Salvar resultado em ambiente.json")
):
    """
    🔍  Escaneia o computador e mostra apps, processos e janelas abertas.
    """
    typer.echo("\n🔍 Escaneando ambiente do computador...")
    typer.echo("━" * 50)

    if not IS_WINDOWS:
        typer.echo("❌ Requer Windows 10 ou 11.\n")
        raise typer.Exit(1)

    ambiente = mapear_ambiente_completo()

    typer.echo(f"\n📦 Apps instalados    : {len(ambiente['apps_instalados'])}")
    typer.echo(f"⚙️  Processos rodando  : {len(ambiente['processos'])}")
    typer.echo(f"🪟 Janelas abertas    : {len(ambiente['janelas_abertas'])}")

    tela = ambiente.get("tela", {})
    typer.echo(f"🖥️  Resolução          : {tela.get('largura')}x{tela.get('altura')} @ {tela.get('dpi')} DPI")

    # Mostrar janelas abertas
    typer.echo("\n🪟 Janelas abertas agora:")
    for j in ambiente["janelas_abertas"]:
        if j["nome"]:
            typer.echo(f"   • {j['nome']} [{j['classe']}]")

    if salvar:
        saida = APP_DIR / "ambiente.json"
        with open(saida, "w", encoding="utf-8") as f:
            json.dump(ambiente, f, ensure_ascii=False, indent=2)
        typer.echo(f"\n💾 Resultado salvo em: {saida}")

    typer.echo("")


# ══════════════════════════════════════════════
# COMANDO RUN — envia tarefa direto pelo terminal
# ══════════════════════════════════════════════

@app.command()
def run(
    tarefa: str = typer.Argument(..., help="Tarefa a executar (ex: 'abrir youtube e pesquisar python')"),
    timeout: int = typer.Option(60, "--timeout", "-t", help="Timeout em segundos para resposta"),
):
    """
    Envia uma tarefa ao agente via backend (linguagem natural).

    Exemplos:
      ad06-agent run \"abrir o youtube\"
      ad06-agent run \"pesquisar receita de bolo no google\"
      ad06-agent run \"abrir o bloco de notas e escrever ola mundo\"
      ad06-agent run \"qual a hora atual\"
    """
    config = carregar_config()
    server_url = config.get("server_url", "http://localhost:3001").rstrip("/")

    typer.echo(f"\n[RUN] Enviando tarefa ao backend: {server_url}")
    typer.echo(f"      Tarefa: {tarefa}\n")

    try:
        import urllib.request
        import urllib.error

        payload = json.dumps({
            "messages": [{"role": "user", "content": tarefa}]
        }).encode("utf-8")

        req = urllib.request.Request(
            f"{server_url}/api/chat",
            data=payload,
            headers={"Content-Type": "application/json", "Accept": "text/event-stream"},
            method="POST",
        )

        typer.echo("[AGENTE] ", nl=False)
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            buffer = ""
            for linha_raw in resp:
                linha = linha_raw.decode("utf-8").rstrip("\n")
                if linha.startswith("data: "):
                    data_str = linha[6:]
                    if data_str == "[DONE]":
                        break
                    try:
                        chunk = json.loads(data_str)
                        # Formato xAI streaming
                        choices = chunk.get("choices", [])
                        for choice in choices:
                            delta = choice.get("delta", {})
                            content = delta.get("content", "")
                            if content:
                                typer.echo(content, nl=False)
                                buffer += content
                    except json.JSONDecodeError:
                        # Pode ser texto simples
                        if data_str.strip():
                            typer.echo(data_str, nl=False)
                            buffer += data_str

        typer.echo("\n")
        if not buffer.strip():
            typer.echo("[AVISO] Sem resposta do backend. Verifique se o servidor esta rodando.")
            typer.echo("        Inicie com: npm run start (na pasta do projeto)\n")

    except urllib.error.URLError as e:
        typer.echo(f"\n[ERRO] Nao foi possivel conectar ao backend: {server_url}")
        typer.echo(f"       Detalhe: {e.reason}")
        typer.echo("       Inicie o backend com: npm run start\n")
        raise typer.Exit(1)
    except Exception as e:
        typer.echo(f"\n[ERRO] {e}\n")
        raise typer.Exit(1)


# ══════════════════════════════════════════════
# ENTRY POINT
# ══════════════════════════════════════════════

def cli():
    """Entry point registrado no setup.py."""
    app()


if __name__ == "__main__":
    cli()
