#!/usr/bin/env python3
"""
RemotePortal — Agente de gestão remota (MVP didático).

Modos:
  (duplo-clique / sem argumentos) → INSTALA como serviço:
       pede consentimento, copia-se para C:\\ProgramData\\RemotePortal,
       cria uma tarefa agendada que roda no boot (como SYSTEM) e inicia.
  --run        → executa o loop do agente (usado pela tarefa agendada).
  --uninstall  → remove a tarefa e os arquivos.

Transparente: na instalação informa que a máquina passará a ser
gerenciada remotamente e exige consentimento. Uso legítimo apenas,
em máquinas próprias/administradas, com conhecimento do usuário.
"""

import json
import os
import platform
import shutil
import socket
import subprocess
import sys
import time
import uuid
import urllib.request

# Garante acentuação correta no console do Windows (UTF-8).
if platform.system() == "Windows":
    try:
        import ctypes
        ctypes.windll.kernel32.SetConsoleOutputCP(65001)
        ctypes.windll.kernel32.SetConsoleCP(65001)
    except Exception:
        pass
try:
    sys.stdout.reconfigure(encoding="utf-8")
    sys.stdin.reconfigure(encoding="utf-8")
except Exception:
    pass

# >>> URL do portal (injetada no build/download) <<<
PORTAL_URL = 'https://suporte.xphsolution.com.br'

POLL_INTERVAL = 5
HEARTBEAT_INTERVAL = 20

TASK_NAME = "RemotePortalAgent"
INSTALL_DIR = os.path.join(os.environ.get("ProgramData", r"C:\ProgramData"), "RemotePortal")
CONFIG_FILE = os.path.join(INSTALL_DIR, "config.json")
INSTALLED_EXE = os.path.join(INSTALL_DIR, "RemotePortalAgent.exe")
LOG_FILE = os.path.join(INSTALL_DIR, "agent.log")


# ----------------------------- HTTP -----------------------------
def http_post(path, payload):
    data = json.dumps(payload).encode()
    req = urllib.request.Request(PORTAL_URL + path, data=data,
                                 headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=15) as r:
        return json.loads(r.read().decode())


def http_get(path):
    with urllib.request.urlopen(PORTAL_URL + path, timeout=15) as r:
        return json.loads(r.read().decode())


# --------------------------- utilidades -------------------------
def is_admin():
    try:
        return ctypes.windll.shell32.IsUserAnAdmin() != 0
    except Exception:
        return False


def log(msg):
    line = time.strftime("%Y-%m-%d %H:%M:%S") + "  " + msg
    try:
        with open(LOG_FILE, "a", encoding="utf-8") as f:
            f.write(line + "\n")
    except Exception:
        pass
    print(line)


def machine_info(machine_id):
    return {
        "id": machine_id,
        "hostname": socket.gethostname(),
        "os": f"{platform.system()} {platform.release()}",
    }


def run_command(command):
    try:
        result = subprocess.run(command, shell=True, capture_output=True,
                                text=True, timeout=60,
                                creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0))
        out = (result.stdout or "") + (result.stderr or "")
        return out if out else "(sem saída)"
    except subprocess.TimeoutExpired:
        return "(erro: tempo limite de 60s excedido)"
    except Exception as e:
        return f"(erro: {e})"


# --------------------------- instalação -------------------------
def do_install():
    print("=" * 62)
    print(" RemotePortal — Instalação do agente de gestão remota")
    print("=" * 62)
    print("Esta máquina passará a ser GERENCIADA REMOTAMENTE pelo portal:")
    print("   " + PORTAL_URL)
    print("O administrador poderá ver o status da máquina e executar")
    print("comandos remotamente. O agente iniciará sozinho a cada boot.")
    resp = input("\nVocê autoriza a instalação? (digite SIM para confirmar): ").strip()
    if resp.upper() != "SIM":
        print("Instalação cancelada.")
        pause_exit(0)

    if platform.system() != "Windows":
        print("A instalação como serviço só é suportada no Windows.")
        print("Em Linux/Mac, rode com:  agent --run")
        pause_exit(1)

    if not getattr(sys, "frozen", False):
        print("A instalação como serviço deve ser feita pelo executável (.exe).")
        pause_exit(1)

    if not is_admin():
        print("\n[!] Permissão de administrador necessária.")
        print("    Feche e execute novamente como administrador")
        print("    (clique com o botão direito → Executar como administrador).")
        pause_exit(1)

    os.makedirs(INSTALL_DIR, exist_ok=True)

    # Copia o próprio executável para a pasta de instalação.
    src = sys.executable
    if os.path.abspath(src) != os.path.abspath(INSTALLED_EXE):
        shutil.copy2(src, INSTALLED_EXE)
        print(f"Copiado para: {INSTALLED_EXE}")

    # Cria o config com ID e consentimento, se ainda não existir.
    if not os.path.exists(CONFIG_FILE):
        machine_id = str(uuid.uuid4())
        with open(CONFIG_FILE, "w", encoding="utf-8") as f:
            json.dump({"id": machine_id, "consent_at": time.time()}, f)
        print(f"Máquina registrada com ID: {machine_id}")

    # Cria/atualiza a tarefa agendada: roda no boot, como SYSTEM.
    cmd = [
        "schtasks", "/Create", "/TN", TASK_NAME,
        "/TR", f'"{INSTALLED_EXE}" --run',
        "/SC", "ONSTART", "/RU", "SYSTEM", "/RL", "HIGHEST", "/F",
    ]
    r = subprocess.run(cmd, capture_output=True, text=True)
    if r.returncode != 0:
        print("Erro ao criar a tarefa agendada:")
        print(r.stdout + r.stderr)
        pause_exit(1)
    print("Tarefa agendada criada (inicia a cada boot).")

    # Inicia agora mesmo.
    subprocess.run(["schtasks", "/Run", "/TN", TASK_NAME], capture_output=True, text=True)
    print("\n✔ Instalação concluída. O agente já está em execução e")
    print("  iniciará automaticamente sempre que o computador ligar.")
    print("  Abra o portal e veja a máquina online.")
    pause_exit(0)


def do_uninstall():
    if platform.system() == "Windows":
        if not is_admin():
            print("Execute como administrador para desinstalar.")
            pause_exit(1)
        subprocess.run(["schtasks", "/End", "/TN", TASK_NAME], capture_output=True, text=True)
        subprocess.run(["schtasks", "/Delete", "/TN", TASK_NAME, "/F"], capture_output=True, text=True)
        print("Tarefa agendada removida.")
    try:
        if os.path.exists(CONFIG_FILE):
            os.remove(CONFIG_FILE)
    except Exception:
        pass
    print("Desinstalado. (A pasta " + INSTALL_DIR + " pode ser removida manualmente.)")
    pause_exit(0)


def pause_exit(code):
    # Dá tempo do usuário ler a mensagem quando aberto por duplo-clique.
    try:
        if sys.stdin and sys.stdin.isatty():
            input("\nPressione ENTER para fechar...")
    except Exception:
        pass
    sys.exit(code)


# ------------------------------ loop ----------------------------
def do_run():
    if not os.path.exists(CONFIG_FILE):
        log("config não encontrado; execute a instalação primeiro.")
        sys.exit(1)
    with open(CONFIG_FILE, encoding="utf-8") as f:
        machine_id = json.load(f)["id"]

    info = machine_info(machine_id)
    last_heartbeat = 0
    log(f"Agente iniciado. ID={machine_id} Portal={PORTAL_URL}")

    while True:
        try:
            now = time.time()
            if now - last_heartbeat >= HEARTBEAT_INTERVAL:
                http_post("/api/register.php", info)
                last_heartbeat = now

            data = http_get(f"/api/poll.php?id={machine_id}")
            for c in data.get("commands", []):
                log(f"executando: {c['command']}")
                output = run_command(c["command"])
                http_post("/api/result.php", {"cmd_id": c["cmd_id"], "output": output})
        except Exception as e:
            log(f"[aviso] sem conexão com o portal: {e}")

        time.sleep(POLL_INTERVAL)


# ------------------------------ main ----------------------------
def main():
    args = [a.lower() for a in sys.argv[1:]]
    if "--run" in args:
        do_run()
    elif "--uninstall" in args:
        do_uninstall()
    else:
        do_install()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\nAgente encerrado.")
