# pip3 install psutil mss soundcard numpy keyboard pyautogui python-socketio requests pyserial Pillow pygetwindow
# python3 -m pip install  psutil mss soundcard numpy keyboard pyautogui python-socketio requests pyserial Pillow pygetwindow

# winget install ffmpeg
# Verificar :   python3 -c "import pyautogui, socketio, PIL, requests, websocket; print('✅ Todas as dependências estão instaladas')"
# Testar se a porta 3000 está acessível
# curl -v https://173.249.31.98:3000
# cd /iot
# 1. Se não existir, instalar
# curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
# python get-pip.py

# 2. Instalar as dependências do projeto
# Instalar python via homebrew // se nao tiver instalado
# brew install python3
# O pip será instalado automaticamente

# se nao tiver instalado :   /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# ls -la /opt/homebrew/bin/brew

# echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zshrc eval "$(/opt/homebrew/bin/brew shellenv)"

# brew install ffmpeg // instalar no make
# winget install ffmpeg  // instalar no windows

# python maquina.py 1..2..3. quantas forem
# instalar os arquivos acima 3 atraz ai burro
# testar porta  :  curl -v https://173.249.31.98:3000  /  https://www.xkbitcoin.com/iot/  https://www.xkbitcoin.com:3000

# Testar se o caminho /iot/ está funcionando
# curl -v https://www.xkbitcoin.com/iot/

# Testar se o domínio com porta 3000 está funcionando
# curl -v https://www.xkbitcoin.com:3000

#!/usr/bin/env python3
"""
Sistema de Streaming e Controle Remoto - v4.1
Fluxo de comandos claramente separado
"""
import sys
import subprocess
import threading
import base64
import io
import time
import platform
import os
import requests
import signal
import psutil
import pyautogui
from PIL import Image
import mss
import soundcard as sc
import socketio

pyautogui.FAILSAFE = False

try:
    import keyboard
    TECLADO_DISPONIVEL = True
except ImportError:
    TECLADO_DISPONIVEL = False

# ═══════════════════════════════════════════
# CONFIGURAÇÕES
# ═══════════════════════════════════════════
CONFIG = {
    'maquina_id': sys.argv[1] if len(sys.argv) > 1 else None,
    'server_url': 'https://xkbitcoin.com',
    'server_port': 3000, 'max_reconexoes': 10, 'delay_reconexao_min': 3, 'delay_reconexao_max': 30,
    'fps': 7, 'qualidade_jpeg': 35, 'largura_alvo': 1024, 'porta_serial': 'COM3', 'baud_rate': 115200,
}

MAPA_TECLAS = {
    "a":"btn_aposta","s":"btn_jogar","n":"btn_ver_cartoes","c":"btn_cartelas","e":"btn_extra","h":"btn_ajuda","b":"btn_b","d":"btn_d","f":"btn_f",
    "g":"btn_g","i":"btn_i","j":"btn_j","k":"btn_k","l":"btn_l","m":"btn_m",  "o":"btn_o","p":"btn_p","q":"btn_q","r":"btn_r","t":"btn_t","u":"btn_u",
    "v":"btn_v","w":"btn_w","x":"btn_x","y":"btn_y","z":"btn_z","0":"btn_0","1":"btn_1","2":"btn_2","3":"btn_3","4":"btn_4",
    "5":"btn_5","6":"btn_6","7":"btn_7","8":"btn_8","9":"btn_9","-":"btn_menos","=":"btn_igual","\t":"btn_tab","\b":"btn_backspace",
    " ":"btn_espaco","\n":"btn_enter","I":"btn_insert",
}

if not CONFIG['maquina_id']:
    print("Uso: python script.py <MAQUINA_ID>")
    sys.exit(1)

# ═══════════════════════════════════════════
# ESTADO GLOBAL
# ═══════════════════════════════════════════
estado = {
    'online': False, 'rodando': True, 'arduino': None, 'sistema': platform.system(),
}

threads = []
sio = socketio.Client(logger=False, engineio_logger=False, reconnection=False)

# ═══════════════════════════════════════════
# INICIALIZAÇÃO
# ═══════════════════════════════════════════

def inicializar_hardware():
    try:
        import serial
        estado['arduino'] = serial.Serial(CONFIG['porta_serial'], CONFIG['baud_rate'], timeout=1)
        print(f'[HW] Conectado: {CONFIG["porta_serial"]}')
    except Exception:
        estado['arduino'] = None

def obter_token():
    """Busca token de autenticação"""
    try:
        url = f'{CONFIG["server_url"]}:{CONFIG["server_port"]}/api/maquina/token/{CONFIG["maquina_id"]}'
        r = requests.get(url, timeout=10)
        return r.json().get('token') if r.status_code == 200 else None
    except Exception:
        return None

def verificar_instancia_duplicada():
    """Mata instâncias anteriores"""
    pid_atual = os.getpid()
    script = os.path.basename(__file__)
    for proc in psutil.process_iter(['pid', 'cmdline']):
        try:
            cmd = proc.info['cmdline'] or []
            if proc.info['pid'] != pid_atual and script in ' '.join(cmd) and CONFIG['maquina_id'] in cmd:
                proc.terminate()
                proc.wait(timeout=5)
        except (psutil.NoSuchProcess, psutil.TimeoutExpired):
            pass

# ═══════════════════════════════════════════
# SOCKET.IO - HANDLERS
# ═══════════════════════════════════════════

def connect():
    estado['online'] = True
    print(f'[STATUS] Máquina {CONFIG["maquina_id"]} - Status: ONLINE ✅')
    sio.emit('entrar_maquina', {'maquina_id': CONFIG['maquina_id']}, callback=on_registro)

    def on_registro(resp):
        if resp and resp.get('status') == 'success':
            print(f'[ONLINE] Máquina {CONFIG["maquina_id"]}')
        else:
            print('[ERRO] Registro negado')
            estado['rodando'] = False

    @sio.on('conexao_duplicada')
    def on_dup(data):
        print('[INFO] Conexão duplicada')
        estado['rodando'] = False
        sio.disconnect()

    @sio.event
    def disconnect():
        estado['online'] = False
        print(f'[STATUS] Máquina {CONFIG["maquina_id"]} - Status: OFFLINE ❌')

# ═══════════════════════════════════════════
# CAPTURA DE VÍDEO E ÁUDIO
# ═══════════════════════════════════════════

@sio.event
def connect():
    estado['online'] = True
    print(f'[STATUS] Máquina {CONFIG["maquina_id"]} - Status: ONLINE ✅')
    sio.emit('entrar_maquina', {'maquina_id': CONFIG['maquina_id']}, callback=on_registro)

def processar_frame(screenshot):
    w, h = screenshot.size
    if w > CONFIG['largura_alvo']:
        ratio = h / w
        screenshot = screenshot.resize((CONFIG['largura_alvo'], int(CONFIG['largura_alvo'] * ratio)), Image.Resampling.BILINEAR)
    buf = io.BytesIO()
    screenshot.save(buf, format='JPEG', quality=CONFIG['qualidade_jpeg'])
    return base64.b64encode(buf.getvalue()).decode()

def capturar_video():
    """Thread: Captura e envia frames de tela"""
    try:
        with mss.MSS() as sct:
            monitor = sct.monitors[1] if len(sct.monitors) > 1 else sct.monitors[0]
            while estado['rodando']:
                try:
                    img = sct.grab(monitor)
                    frame = Image.frombytes('RGB', img.size, img.bgra, 'raw', 'BGRX')
                    b64 = processar_frame(frame)
                    if sio.connected:
                        sio.emit('tela_maquina', {'maquina_id': CONFIG['maquina_id'], 'imagem': b64})
                    time.sleep(1 / CONFIG['fps'])
                except Exception:
                    time.sleep(1)
    except Exception as e:
        print(f'[ERRO] Vídeo: {e}')

def capturar_audio():
    """Thread: Captura e envia áudio do sistema"""
    try:
        mic = sc.default_microphone() if estado['sistema'] == 'Darwin' else sc.get_microphone(
            id=str(sc.default_speaker().name), include_loopback=True)
        with mic.recorder(samplerate=44100) as rec:
            while estado['rodando']:
                try:
                    chunk = base64.b64encode(rec.record(numframes=4096).tobytes()).decode()
                    if sio.connected:
                        sio.emit('stream_audio_video', {'maquina_id': CONFIG['maquina_id'], 'chunk': chunk})
                except Exception:
                    time.sleep(0.5)
    except Exception as e:
        print(f'[ERRO] Áudio: {e}')

# ═══════════════════════════════════════════
# 🔵 CAPTURA DE TECLADO FÍSICO (LOCAL → BROWSER)
# ═══════════════════════════════════════════

def capturar_teclas():
    if not TECLADO_DISPONIVEL:
        while estado['rodando']:
            time.sleep(1)
        return
    
    def on_press(e):
        if e.event_type == 'down' and e.name.lower() in MAPA_TECLAS and sio.connected:
            comando = MAPA_TECLAS[e.name.lower()]
            # Envia comando para o BROWSER (NÃO para o Arduino)
            sio.emit('executar_js', {
                'funcao': 'ClickBtn',
                'args': [CONFIG['maquina_id'], comando]
            })
            print(f'🕹️ Acionando botão físico: {comando}')
    
    keyboard.hook(on_press)
    while estado['rodando']:
        time.sleep(1)

    # ═══════════════════════════════════════
    # 🖱️ CLIQUE VIRTUAL (Recebido do Browser)
    # ═══════════════════════════════════════
    @sio.on('executar_clique')
    def on_click(data):
        try:
            x = int(float(data.get('x', 0)) * pyautogui.size()[0])
            y = int(float(data.get('y', 0)) * pyautogui.size()[1])
            pyautogui.click(x, y)
        except Exception:
            pass

    # ═══════════════════════════════════════
    # 🔘 COMANDO JS 
    # ═══════════════════════════════════════
    @sio.on('executar_js')
    def on_js(data):
        pass  

    # ═══════════════════════════════════════
    # 🟢 BOTÃO FÍSICO (Recebido → Arduino)
    # ═══════════════════════════════════════
    @sio.on('acionar_botao_maquina')
    def on_botao(data):
        tecla = data.get('tecla', '')
        if estado['arduino'] and estado['arduino'].is_open:
            try:
                estado['arduino'].write(tecla.encode('utf-8'))
                 print(f'📩 [RECEBIDO] acionar_botao_maquina: tecla={tecla}')
            except Exception:
                pass

# ═══════════════════════════════════════════
# CONEXÃO E ENCERRAMENTO
# ═══════════════════════════════════════════

def conectar():
    tentativa = 0
    while estado['rodando'] and tentativa < CONFIG['max_reconexoes']:
        try:
            if tentativa > 0:
                delay = min(CONFIG['delay_reconexao_min'] * (2 ** (tentativa - 1)), CONFIG['delay_reconexao_max'])
                print(f'[RECONECTANDO] {tentativa+1}/{CONFIG["max_reconexoes"]} em {delay}s')
                for _ in range(int(delay)):
                    if not estado['rodando']: return
                    time.sleep(1)
            
            sio.connect(f'{CONFIG["server_url"]}:{CONFIG["server_port"]}', auth={'token': TOKEN})
            tentativa = 0
            while sio.connected and estado['rodando']:
                time.sleep(1)
            tentativa += 1
        except Exception as e:
            tentativa += 1
            if tentativa == 1:
                print(f'[ERRO] Conexão: {e}')
    estado['rodando'] = False

    print(f'[STATUS ATUAL] Online: {estado["online"]} | Conectado: {sio.connected}')

def encerrar(signum=None, frame=None):
    """Encerramento limpo"""
    estado['rodando'] = False
    if sio.connected:
        sio.disconnect()
    if estado['arduino'] and estado['arduino'].is_open:
        estado['arduino'].close()
    print('[ENCERRADO]')
    sys.exit(0)
# ═══════════════════════════════════════════
# INÍCIO
# ═══════════════════════════════════════════
if __name__ == '__main__':
    signal.signal(signal.SIGINT, encerrar)
    signal.signal(signal.SIGTERM, encerrar)

    print(f'[INICIANDO] Máquina {CONFIG["maquina_id"]} | {estado["sistema"]}')

    verificar_instancia_duplicada()
    TOKEN = obter_token()
    if not TOKEN:
        print('[FALHA] Token não obtido')
        sys.exit(1)

    inicializar_hardware()
    configurar_socket()

    for nome, func in [('Video', capturar_video), ('Audio', capturar_audio), ('Teclas', capturar_teclas)]:
        t = threading.Thread(target=func, daemon=True, name=nome)
        t.start()
        threads.append(t)

    print(f'[RODANDO]')
    conectar()
    encerrar()