"""
execution_engine.py
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Motor de execução híbrido completo.

Fluxo de decisão para cada ação:
  1. Verificar segurança (admin, permissões)
  2. Detectar tipo do app (Win32, WPF, Electron…)
  3. Executar pela melhor abordagem disponível
  4. Se falhar → tentar abordagem alternativa
  5. Verificar resultado
  6. Retornar status detalhado

Abordagens disponíveis:
  • UI Automation API    → apps nativos Win32 / WPF / UWP
  • Java Access Bridge   → apps Java Swing / AWT
  • Playwright / CDP     → navegadores e Electron
  • Visão computacional  → qualquer app (fallback via Grok Vision)
  • pyautogui            → último recurso (coordenadas brutas)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
"""

import os
import sys
import time
import base64
import io
import json
import subprocess
import psutil
import pyautogui
import uiautomation as auto
import mss
from PIL import Image
from pathlib import Path
from typing import Optional
from loguru import logger

from app_detector  import DetectorApp
from security_manager import (
    verificar_pode_automatizar_app,
    e_administrador,
)
from environment_mapper import (
    encontrar_executavel_app,
    normalizar_coordenadas,
    mapear_janelas_ativas,
)


# ══════════════════════════════════════════════════════
# CONSTANTES
# ══════════════════════════════════════════════════════

MAX_TENTATIVAS   = 3
TIMEOUT_JANELA   = 8    # segundos para aguardar janela abrir
TIMEOUT_ELEMENTO = 4    # segundos para aguardar elemento aparecer
DELAY_APOS_ABRIR = 2.0  # segundos de espera após abrir app


# ══════════════════════════════════════════════════════
# ABORDAGEM 1 — UI AUTOMATION API
# ══════════════════════════════════════════════════════

class AbordagemUIAutomation:
    """UI Automation API — apps nativos Win32, WPF e UWP."""

    def executar(self, janela_nome: str, acao: dict) -> dict:
        tipo = acao.get("tipo", "")

        try:
            janela = auto.WindowControl(Name=janela_nome, searchDepth=1)

            if not janela.Exists(maxSearchSeconds=TIMEOUT_JANELA):
                return {
                    "status": "janela_nao_encontrada",
                    "janela": janela_nome,
                    "dica":   "Verifique se o app está aberto e o nome está correto"
                }

            # Trazer janela para frente
            try:
                janela.SetFocus()
                time.sleep(0.15)
            except:
                pass

            return self._executar_acao(janela, tipo, acao)

        except Exception as e:
            return {
                "status":       "falha_ui_automation",
                "erro":         str(e),
                "requer_visao": True
            }

    def _executar_acao(self, janela, tipo: str, acao: dict) -> dict:
        """Executa a ação específica na janela."""

        if tipo == "clicar_botao":
            return self._clicar_botao(janela, acao)

        elif tipo == "digitar_texto":
            return self._digitar_texto(janela, acao)

        elif tipo == "clicar_menu":
            return self._clicar_menu(janela, acao)

        elif tipo == "selecionar_lista":
            return self._selecionar_lista(janela, acao)

        elif tipo == "selecionar_combobox":
            return self._selecionar_combobox(janela, acao)

        elif tipo == "pressionar_tecla":
            janela.SendKeys(acao["tecla"])
            return {"status": "ok", "acao": tipo}

        elif tipo == "marcar_checkbox":
            return self._marcar_checkbox(janela, acao)

        elif tipo == "rolar":
            return self._rolar(janela, acao)

        elif tipo == "ler_texto":
            return self._ler_texto(janela, acao)

        elif tipo == "ler_todos_textos":
            return self._ler_todos_textos(janela)

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

        elif tipo == "fechar_janela":
            janela.Close()
            return {"status": "ok", "acao": tipo}

        elif tipo == "maximizar":
            janela.Maximize()
            return {"status": "ok", "acao": tipo}

        elif tipo == "minimizar":
            janela.Minimize()
            return {"status": "ok", "acao": tipo}

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

    def _clicar_botao(self, janela, acao: dict) -> dict:
        nome = acao.get("elemento", "")
        # Tentar por nome primeiro
        btn = janela.ButtonControl(Name=nome)
        if not btn.Exists(maxSearchSeconds=TIMEOUT_ELEMENTO):
            # Tentar por AutomationId
            auto_id = acao.get("automation_id", "")
            if auto_id:
                btn = janela.ButtonControl(AutomationId=auto_id)
            if not btn.Exists(maxSearchSeconds=1):
                return {"status": "elemento_nao_encontrado",
                        "elemento": nome,
                        "requer_visao": True}
        btn.Click()
        logger.success(f"Botão '{nome}' clicado")
        return {"status": "ok", "acao": "clicar_botao", "elemento": nome}

    def _digitar_texto(self, janela, acao: dict) -> dict:
        nome  = acao.get("elemento", "")
        texto = acao.get("texto", "")
        limpar = acao.get("limpar_antes", True)

        campo = janela.EditControl(Name=nome) if nome else janela.EditControl()
        if not campo.Exists(maxSearchSeconds=TIMEOUT_ELEMENTO):
            return {"status": "campo_nao_encontrado",
                    "elemento": nome, "requer_visao": True}

        campo.Click()
        if limpar:
            campo.SendKeys("{CTRL}a")
            time.sleep(0.05)
        campo.SetValue(texto)
        logger.success(f"Texto digitado no campo '{nome}'")
        return {"status": "ok", "acao": "digitar_texto"}

    def _clicar_menu(self, janela, acao: dict) -> dict:
        menu_nome = acao.get("menu", "")
        item_nome = acao.get("item", "")
        sub_item  = acao.get("sub_item", "")

        menu = janela.MenuControl(Name=menu_nome)
        if not menu.Exists(maxSearchSeconds=TIMEOUT_ELEMENTO):
            # Tentar MenuItemControl
            menu = janela.MenuItemControl(Name=menu_nome)
        menu.Click()
        time.sleep(0.35)

        item = janela.MenuItemControl(Name=item_nome)
        item.Click()
        time.sleep(0.2)

        if sub_item:
            sub = janela.MenuItemControl(Name=sub_item)
            sub.Click()

        logger.success(f"Menu '{menu_nome} > {item_nome}' executado")
        return {"status": "ok", "acao": "clicar_menu"}

    def _selecionar_lista(self, janela, acao: dict) -> dict:
        lista = janela.ListControl(Name=acao.get("elemento", ""))
        item  = lista.ListItemControl(Name=acao["valor"])
        if not item.Exists(maxSearchSeconds=TIMEOUT_ELEMENTO):
            return {"status": "item_nao_encontrado",
                    "valor": acao["valor"]}
        item.Click()
        return {"status": "ok", "acao": "selecionar_lista"}

    def _selecionar_combobox(self, janela, acao: dict) -> dict:
        cb = janela.ComboBoxControl(Name=acao.get("elemento", ""))
        cb.Click()
        time.sleep(0.25)
        item = cb.ListItemControl(Name=acao["valor"])
        item.Click()
        return {"status": "ok", "acao": "selecionar_combobox"}

    def _marcar_checkbox(self, janela, acao: dict) -> dict:
        cb     = janela.CheckBoxControl(Name=acao["elemento"])
        marcar = acao.get("marcar", True)
        try:
            padrao = cb.GetTogglePattern()
            estado_atual = padrao.ToggleState  # 0=desmarcado, 1=marcado
            if (marcar and estado_atual != 1) or (not marcar and estado_atual == 1):
                cb.Click()
            return {"status": "ok", "acao": "marcar_checkbox",
                    "estado": "marcado" if marcar else "desmarcado"}
        except:
            cb.Click()
            return {"status": "ok", "acao": "marcar_checkbox"}

    def _rolar(self, janela, acao: dict) -> dict:
        direcao   = acao.get("direcao", "baixo")
        quantidade = acao.get("quantidade", 3)
        el_nome   = acao.get("elemento", "")

        el = janela.Control(Name=el_nome) if el_nome else janela
        rect = el.BoundingRectangle
        cx = rect.left + rect.width()  // 2
        cy = rect.top  + rect.height() // 2

        for _ in range(quantidade):
            delta = -120 if direcao == "baixo" else 120
            pyautogui.scroll(delta, x=cx, y=cy)
            time.sleep(0.1)

        return {"status": "ok", "acao": "rolar",
                "direcao": direcao, "quantidade": quantidade}

    def _ler_texto(self, janela, acao: dict) -> dict:
        el_nome = acao.get("elemento", "")
        el = janela.Control(Name=el_nome) if el_nome else janela
        if not el.Exists(maxSearchSeconds=TIMEOUT_ELEMENTO):
            return {"status": "elemento_nao_encontrado"}

        texto = el.Name or ""
        try:
            padrao = el.GetValuePattern()
            if padrao:
                texto = padrao.Value or texto
        except:
            pass
        return {"status": "ok", "acao": "ler_texto", "texto": texto}

    def _ler_todos_textos(self, janela) -> dict:
        """Lê todos os textos visíveis da janela."""
        textos = []

        def coletar(controle, nivel=0):
            try:
                nome = controle.Name
                if nome and nome.strip():
                    textos.append({
                        "texto": nome,
                        "tipo":  controle.ControlTypeName,
                        "nivel": nivel
                    })
                for filho in controle.GetChildren():
                    coletar(filho, nivel + 1)
            except:
                pass

        coletar(janela)
        return {"status": "ok", "acao": "ler_todos_textos", "textos": textos}


# ══════════════════════════════════════════════════════
# ABORDAGEM 2 — JAVA ACCESS BRIDGE
# ══════════════════════════════════════════════════════

class AbordagemJAB:
    """Java Access Bridge — apps Java Swing/AWT."""

    def executar(self, janela_nome: str, acao: dict) -> dict:
        try:
            from pywinauto import Application
            app = Application(backend="jabwrapper").connect(title=janela_nome)
            win = app.window(title=janela_nome)

            tipo = acao.get("tipo", "")

            if tipo == "clicar_botao":
                win.child_window(title=acao["elemento"],
                                 control_type="PushButton").click()
                return {"status": "ok", "acao": tipo}

            elif tipo == "digitar_texto":
                campo = win.child_window(title=acao.get("elemento", ""),
                                         control_type="Text")
                campo.set_text(acao["texto"])
                return {"status": "ok", "acao": tipo}

            else:
                return {"status": "acao_jab_nao_suportada", "tipo": tipo,
                        "requer_visao": True}

        except Exception as e:
            logger.warning(f"JAB falhou: {e}")
            return {"status": "falha_jab", "erro": str(e), "requer_visao": True}


# ══════════════════════════════════════════════════════
# ABORDAGEM 3 — VISÃO COMPUTACIONAL (FALLBACK)
# ══════════════════════════════════════════════════════

class AbordagemVisao:
    """
    Fallback visual: captura screenshot e executa por coordenadas.
    As coordenadas vêm do Grok Vision após análise do screenshot.
    """

    def capturar(self, monitor: int = 1) -> str:
        """Captura screenshot e retorna base64."""
        try:
            with mss.mss() as sct:
                idx  = min(monitor, len(sct.monitors) - 1)
                shot = sct.grab(sct.monitors[idx])
                img  = Image.frombytes("RGB", shot.size,
                                       shot.bgra, "raw", "BGRX")
                buf  = io.BytesIO()
                img.save(buf, format="PNG", optimize=True)
                return base64.b64encode(buf.getvalue()).decode()
        except Exception as e:
            logger.error(f"Erro ao capturar: {e}")
            return ""

    def clicar(self, x: int, y: int, tipo: str = "simples") -> dict:
        """Clica nas coordenadas fornecidas pelo Grok Vision."""
        try:
            coords = normalizar_coordenadas(x, y)
            xn = coords["x_normalizado"]
            yn = coords["y_normalizado"]

            pyautogui.moveTo(xn, yn, duration=0.2)
            time.sleep(0.05)

            if tipo == "duplo":
                pyautogui.doubleClick()
            elif tipo == "direito":
                pyautogui.rightClick()
            else:
                pyautogui.click()

            logger.success(f"Visão — clique {tipo} em ({xn}, {yn})")
            return {"status": "ok", "acao": f"clique_visual_{tipo}",
                    "coords": [xn, yn]}

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

    def digitar(self, texto: str, x: int = None, y: int = None) -> dict:
        """Digita texto, opcionalmente clicando antes numa posição."""
        try:
            if x is not None and y is not None:
                coords = normalizar_coordenadas(x, y)
                pyautogui.click(coords["x_normalizado"],
                                coords["y_normalizado"])
                time.sleep(0.15)
            pyautogui.typewrite(texto, interval=0.04)
            return {"status": "ok", "acao": "digitar_visual"}
        except Exception as e:
            return {"status": "falha_visual", "erro": str(e)}


# ══════════════════════════════════════════════════════
# MOTOR PRINCIPAL — ORQUESTRADOR COMPLETO
# ══════════════════════════════════════════════════════

class MotorExecucao:
    """
    Orquestra todas as abordagens de automação.
    Decide automaticamente qual usar baseado no tipo do app.
    """

    def __init__(self):
        self.uia    = AbordagemUIAutomation()
        self.jab    = AbordagemJAB()
        self.visao  = AbordagemVisao()
        self.det    = DetectorApp()

    # ── ABRIR APLICATIVO ─────────────────────────────

    def abrir_app(self, nome: str, aguardar: bool = True) -> dict:
        """
        Abre um aplicativo com verificação completa:
          1. Verifica se já está aberto
          2. Verifica permissões
          3. Encontra o executável
          4. Abre e aguarda carregamento
        """
        logger.info(f"Abrindo app: '{nome}'")

        # 1. Já está aberto?
        for proc in psutil.process_iter(["pid", "name", "exe"]):
            try:
                if nome.lower().replace(".exe", "") in proc.info["name"].lower():
                    logger.info(f"'{nome}' já está em execução (PID: {proc.info['pid']})")
                    return {
                        "status": "ja_aberto",
                        "app":    nome,
                        "pid":    proc.info["pid"]
                    }
            except:
                continue

        # 2. Verificar permissões
        perm = verificar_pode_automatizar_app(nome)
        if not perm["pode_automatizar"]:
            return {
                "status":  "sem_permissao",
                "motivo":  perm["motivo"],
                "dica":    "Reinicie o agente como administrador"
            }

        # 3. Encontrar executável
        exe = encontrar_executavel_app(nome) or nome

        # 4. Abrir
        try:
            subprocess.Popen(exe, shell=True)
            logger.info(f"App iniciado: {exe}")

            if aguardar:
                # Aguardar janela aparecer
                for i in range(int(TIMEOUT_JANELA / 0.5)):
                    time.sleep(0.5)
                    for janela in auto.GetRootControl().GetChildren():
                        try:
                            if nome.lower() in (janela.Name or "").lower():
                                logger.success(
                                    f"✅ Janela de '{nome}' detectada "
                                    f"em {(i+1)*0.5:.1f}s"
                                )
                                return {
                                    "status":  "ok",
                                    "app":     nome,
                                    "janela":  janela.Name,
                                    "tempo_s": (i+1) * 0.5
                                }
                        except:
                            continue

                # Não encontrou janela mas o processo foi criado
                time.sleep(DELAY_APOS_ABRIR)

            return {"status": "ok", "app": nome, "exe": exe}

        except Exception as e:
            logger.error(f"Erro ao abrir '{nome}': {e}")
            return {"status": "erro", "detalhe": str(e)}

    # ── EXECUTAR AÇÃO ────────────────────────────────

    def executar(self, janela_nome: str, acao: dict,
                 tentativas: int = 0) -> dict:
        """
        Executa uma ação usando a melhor abordagem disponível.
        Tenta automaticamente abordagens alternativas em caso de falha.
        """
        if tentativas >= MAX_TENTATIVAS:
            return {
                "status":  "falha_todas_abordagens",
                "detalhe": f"Todas as {MAX_TENTATIVAS} tentativas falharam"
            }

        tipo_acao = acao.get("tipo", "")
        logger.info(
            f"Executando '{tipo_acao}' em '{janela_nome}' "
            f"(tentativa {tentativas+1}/{MAX_TENTATIVAS})"
        )

        # Detectar tipo do app
        tipo_app = self._detectar_tipo_app(janela_nome)
        logger.debug(f"Tipo do app detectado: {tipo_app}")

        # Escolher abordagem
        if tipo_app in ("WIN32", "WPF", "UWP", "UNKNOWN"):
            resultado = self.uia.executar(janela_nome, acao)

        elif tipo_app == "JAVA":
            resultado = self.jab.executar(janela_nome, acao)
            if resultado.get("requer_visao"):
                resultado = self.uia.executar(janela_nome, acao)

        elif tipo_app in ("ELECTRON", "BROWSER"):
            # Tentar UIA básica primeiro
            resultado = self.uia.executar(janela_nome, acao)

        else:
            resultado = self.uia.executar(janela_nome, acao)

        # Se falhou e precisa de visão → capturar screenshot e retornar
        if resultado.get("requer_visao") or resultado.get("status") in (
            "falha_ui_automation", "janela_nao_encontrada",
            "elemento_nao_encontrado"
        ):
            logger.warning(
                f"Abordagem principal falhou — capturando tela para Grok Vision"
            )
            screenshot = self.visao.capturar()
            resultado["screenshot"]  = screenshot
            resultado["tipo_app"]    = tipo_app
            resultado["requer_visao"] = True
            resultado["janela_buscada"] = janela_nome

        return resultado

    # ── EXECUTAR POR COORDENADAS (pós Grok Vision) ──

    def executar_visual(self, x: int, y: int,
                         tipo: str = "simples",
                         texto: str = "") -> dict:
        """
        Executa ação visual com coordenadas fornecidas pelo Grok Vision.
        """
        if texto:
            return self.visao.digitar(texto, x, y)
        return self.visao.clicar(x, y, tipo)

    # ── VERIFICAR RESULTADO ──────────────────────────

    def verificar_resultado(self, janela_nome: str,
                             elemento: str = "",
                             tipo_verificacao: str = "existencia") -> dict:
        """
        Verifica se a ação anterior foi bem-sucedida.
        """
        try:
            if tipo_verificacao == "existencia":
                if elemento:
                    janela = auto.WindowControl(Name=janela_nome)
                    el     = janela.Control(Name=elemento)
                    existe = el.Exists(maxSearchSeconds=2)
                    return {"status": "ok", "existe": existe,
                            "elemento": elemento}
                else:
                    janela = auto.WindowControl(Name=janela_nome)
                    existe = janela.Exists(maxSearchSeconds=2)
                    return {"status": "ok", "existe": existe,
                            "janela": janela_nome}

            elif tipo_verificacao == "screenshot":
                screenshot = self.visao.capturar()
                return {"status": "ok", "screenshot": screenshot,
                        "tipo": "screenshot_verificacao"}

        except Exception as e:
            return {"status": "erro_verificacao", "detalhe": str(e)}

    # ── HELPER INTERNO ───────────────────────────────

    def _detectar_tipo_app(self, janela_nome: str) -> str:
        try:
            janela = auto.WindowControl(Name=janela_nome, searchDepth=1)
            if not janela.Exists(maxSearchSeconds=2):
                return "UNKNOWN"
            pid  = janela.ProcessId
            proc = psutil.Process(pid)
            info = self.det.detectar(
                nome_janela   = janela_nome,
                classe_janela = janela.ClassName or "",
                nome_processo = proc.name(),
                exe_path      = proc.exe() or ""
            )
            return info.tipo
        except:
            return "UNKNOWN"


# Instância global do motor
motor = MotorExecucao()


# Teste direto
if __name__ == "__main__":
    import json as _json

    # Testar abertura do Bloco de Notas
    resultado = motor.abrir_app("notepad")
    print(_json.dumps(resultado, indent=2, ensure_ascii=False))

    # Testar digitação
    if resultado["status"] in ("ok", "ja_aberto"):
        janela = resultado.get("janela", "Sem Título - Bloco de Notas")
        r = motor.executar(janela, {
            "tipo":  "digitar_texto",
            "texto": "Olá! Agente funcionando."
        })
        print(_json.dumps(r, indent=2, ensure_ascii=False))
