#!/usr/bin/env python3.12
"""Daemon v3: lê direto do DOM do iframe (.payouts-block) — fonte canônica do histórico visível."""
import json
import os
import re
import ssl
import threading
import time
import urllib.request
import websocket

CDP = 'http://localhost:9222'
BACKEND = 'https://localhost:8766'
ssl_ctx = ssl._create_unverified_context()


def post_backend(payload):
    try:
        req = urllib.request.Request(
            BACKEND + '/api/events',
            data=json.dumps(payload).encode(),
            headers={'Content-Type': 'application/json'},
            method='POST',
        )
        urllib.request.urlopen(req, context=ssl_ctx, timeout=2).read()
        return True
    except Exception as e:
        print(f'[backend] erro: {e}')
        return False


def find_iframe_target():
    try:
        r = urllib.request.urlopen(CDP + '/json/list', timeout=3)
        for t in json.loads(r.read()):
            if 'spribegaming.com' in t.get('url', '') or 'spribe.games' in t.get('url', ''):
                return t
    except Exception:
        pass
    return None


# Estado global: lista FIFO dos crashes já registrados
registered = []   # [(value, timestamp)]
MULT_PATTERN = re.compile(r'\b(\d+(?:\.\d{1,3})?)x', re.IGNORECASE)


def is_new_crash(value, now):
    """Debounce por valor+tempo: se valor idêntico nos últimos 10s, ignora."""
    for v, ts in registered[-30:]:
        if abs(v - value) < 0.005 and (now - ts) < 10:
            return False
    return True


def register(value):
    now = time.time()
    if value < 1.0 or value > 1000: return
    if not is_new_crash(value, now): return
    registered.append((value, now))
    if len(registered) > 200:
        registered[:] = registered[-100:]
    print(f'[dom] CRASH real: {value:.2f}x')
    post_backend({'kind': 'crash', 'value': value})


# Lista anterior do DOM, pra detectar inserções no topo
last_dom_values = []


def process_dom_list(text):
    """text: innerText do .payouts-block, algo como '2.33x 3.17x 4.34x ...'"""
    global last_dom_values
    matches = [float(m.group(1)) for m in MULT_PATTERN.finditer(text)]
    # Filtra valores que parecem multiplicadores (1.00 a 1000, 1-3 casas decimais)
    vals = [v for v in matches if 0.99 < v < 1000]
    if not vals: return

    # Detecta novos valores no TOPO (esquerda = mais recente na UI)
    if last_dom_values:
        # Acha quantos valores NOVOS estão na frente
        # comparar: novo prefix ends where it matches last_dom_values[0]
        new_prefix = []
        for v in vals:
            if last_dom_values and abs(v - last_dom_values[0]) < 0.005:
                break
            new_prefix.append(v)
            if len(new_prefix) >= 10: break
        # Registra novos do mais antigo pro mais novo (reverso)
        for v in reversed(new_prefix):
            register(v)
    else:
        # Primeira leitura: registra histórico em ordem cronológica (mais antigo primeiro)
        # UI mostra mais recente à esquerda, então reverter
        for v in reversed(vals[:15]):
            register(v)

    last_dom_values = vals[:20]


class CDPClient:
    def __init__(self, ws_url):
        self.ws = websocket.create_connection(ws_url, timeout=10)
        self.next_id = 1
        self.pending = {}
        self.lock = threading.Lock()
        self.running = True
        self.reader = threading.Thread(target=self._read_loop, daemon=True)
        self.reader.start()

    def _read_loop(self):
        while self.running:
            try:
                raw = self.ws.recv()
                msg = json.loads(raw)
                if 'id' in msg:
                    with self.lock:
                        if msg['id'] in self.pending:
                            self.pending[msg['id']].append(msg)
            except websocket.WebSocketException:
                self.running = False
                break
            except Exception:
                pass

    def call(self, method, params=None, timeout=5):
        with self.lock:
            cid = self.next_id
            self.next_id += 1
            self.pending[cid] = []
        self.ws.send(json.dumps({'id': cid, 'method': method, 'params': params or {}}))
        start = time.time()
        while time.time() - start < timeout:
            with self.lock:
                if self.pending.get(cid):
                    msg = self.pending[cid].pop(0)
                    del self.pending[cid]
                    return msg
            time.sleep(0.02)
        raise TimeoutError(method)

    def close(self):
        self.running = False
        try: self.ws.close()
        except: pass


SELECTORS = [
    '.payouts-block',
    '[class*="payouts"]',
    '[class*="history"]',
    '[class*="multipliers"]',
]


def main():
    print('[dom] procurando iframe Spribe...')
    target = None
    for _ in range(60):
        target = find_iframe_target()
        if target: break
        time.sleep(1)
        print('.', end='', flush=True)
    if not target:
        print('\n[dom] timeout — Aviator não encontrado'); return
    print(f'\n[dom] target: {target["url"][:80]}')

    client = CDPClient(target['webSocketDebuggerUrl'])
    client.call('Runtime.enable')

    # Descobre qual seletor funciona
    working_selector = None
    print('[dom] procurando elemento de histórico...')
    for _ in range(30):
        for sel in SELECTORS:
            try:
                expr = f"document.querySelector({sel!r})?.innerText || null"
                res = client.call('Runtime.evaluate', {'expression': expr, 'returnByValue': True})
                val = res.get('result', {}).get('result', {}).get('value')
                if val and len(val) > 5:
                    # Verifica se contém pelo menos 1 multiplicador
                    if re.search(r'\d+\.\d+x?', val):
                        working_selector = sel
                        print(f'[dom] encontrado: {sel} -> {val[:150]}')
                        break
            except Exception as e:
                continue
        if working_selector: break
        time.sleep(1)
    if not working_selector:
        print('[dom] nenhum seletor funcionou. Você pode inspecionar o elemento e me enviar o seletor correto.')
        return

    print('[dom] iniciando poll loop a cada 500ms...')
    heartbeat_last = 0
    try:
        while True:
            try:
                expr = f"document.querySelector({working_selector!r})?.innerText || null"
                res = client.call('Runtime.evaluate', {'expression': expr, 'returnByValue': True})
                text = res.get('result', {}).get('result', {}).get('value')
                if text:
                    process_dom_list(text)
            except Exception as e:
                # Se falhou, talvez iframe recarregou
                print(f'[dom] erro eval: {e} — retentando em 3s')
                time.sleep(3)
                new_target = find_iframe_target()
                if new_target and new_target['id'] != target['id']:
                    target = new_target
                    client.close()
                    client = CDPClient(target['webSocketDebuggerUrl'])
                    client.call('Runtime.enable')
                    print(f'[dom] reconectado ao novo target {target["id"][:10]}')
                continue

            # Heartbeat
            now = time.time()
            if now - heartbeat_last > 2:
                post_backend({'kind': 'heartbeat', 'connected': True, 'msgsReceived': len(registered)})
                heartbeat_last = now

            time.sleep(0.5)
    except KeyboardInterrupt:
        pass
    finally:
        client.close()


if __name__ == '__main__':
    main()
