#!/usr/bin/env python3.12
"""Daemon CDP: auto-attach em todos os targets, injeta snippet em novos documents, faz poll de crashes."""
import json, os, ssl, sys, time, threading, urllib.request
import websocket

CDP_BROWSER = None
BACKEND = 'https://localhost:8766'
SNIPPET_PATH = os.path.join(os.path.dirname(__file__), 'static', 'snippet.js')
ssl_ctx = ssl._create_unverified_context()

with open(SNIPPET_PATH) as f:
    SNIPPET = f.read()


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


class CDPClient:
    def __init__(self, ws_url):
        self.ws = websocket.create_connection(ws_url, timeout=10)
        self.next_id = 1
        self.pending = {}
        self.events = []
        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)
                elif 'method' in msg:
                    with self.lock:
                        self.events.append(msg)
            except websocket.WebSocketException:
                self.running = False
                break
            except Exception:
                pass

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

    def drain_events(self):
        with self.lock:
            evts = self.events
            self.events = []
        return evts

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


def main():
    # Get browser WebSocket URL
    r = urllib.request.urlopen('http://localhost:9222/json/version', timeout=3)
    meta = json.loads(r.read())
    browser_ws_url = meta['webSocketDebuggerUrl']
    print(f'[daemon] conectando ao browser: {browser_ws_url}')

    cdp = CDPClient(browser_ws_url)

    # Auto-attach em tudo
    cdp.call('Target.setAutoAttach', {
        'autoAttach': True,
        'waitForDebuggerOnStart': True,
        'flatten': True,
    })
    print('[daemon] auto-attach habilitado')

    # Enumerar targets existentes e attach manualmente (autoAttach cobre apenas novos)
    existing = cdp.call('Target.getTargets').get('result', {}).get('targetInfos', [])
    for ti in existing:
        url = ti.get('url', '')
        if ('spribegaming.com' in url or 'spribe.games' in url or '7k.bet.br' in url) and ti.get('type') in ('page', 'iframe'):
            try:
                r = cdp.call('Target.attachToTarget', {'targetId': ti['targetId'], 'flatten': True})
                print(f'[daemon] attach manual {ti["type"]} {url[:80]}')
            except Exception as e:
                print(f'[daemon] erro attach manual: {e}')

    # Monta uma tabela de sessions -> targetInfo
    sessions = {}          # sessionId -> {url, type, targetId}
    attached_spribe = {}   # targetId -> sessionId (Spribe iframes ativos)

    def handle_event(evt):
        method = evt.get('method')
        params = evt.get('params', {})
        session_id = evt.get('sessionId')
        # Target.attachedToTarget vem do browser connection quando flatten=true
        if method == 'Target.attachedToTarget':
            ti = params['targetInfo']
            sid = params['sessionId']
            sessions[sid] = ti
            url = ti.get('url', '')
            print(f'[daemon] attached: [{ti.get("type")}] {url[:90]} (session={sid[:10]})')
            # Registra snippet em qualquer target que nos interessa
            try:
                cdp.call('Runtime.enable', {}, session_id=sid)
                cdp.call('Page.enable', {}, session_id=sid)
                res = cdp.call('Page.addScriptToEvaluateOnNewDocument', {'source': SNIPPET, 'runImmediately': True}, session_id=sid)
                print(f'  addScriptToEvaluateOnNewDocument: {res.get("result", {}).get("identifier")}')
                # Tenta injetar imediatamente caso já exista documento
                cdp.call('Runtime.evaluate', {'expression': SNIPPET, 'returnByValue': True}, session_id=sid)
                print(f'  snippet imediato também injetado')
            except Exception as e:
                print(f'  [!] erro: {e}')
            if 'spribegaming.com' in url or 'spribe.games' in url:
                attached_spribe[ti['targetId']] = sid
        elif method == 'Target.detachedFromTarget':
            sid = params.get('sessionId')
            if sid in sessions:
                tid = sessions[sid].get('targetId')
                if tid in attached_spribe: del attached_spribe[tid]
                del sessions[sid]

    # Loop principal
    crash_count = 0
    last_heartbeat = 0
    try:
        while True:
            # Processa eventos
            for evt in cdp.drain_events():
                handle_event(evt)

            # Poll crashes de cada Spribe session
            for target_id, sid in list(attached_spribe.items()):
                try:
                    res = cdp.call('Runtime.evaluate', {
                        'expression': '(()=>{const b=window.__aviatorSignalsBuffer||[];window.__aviatorSignalsBuffer=[];const s=window.__aviatorSignalsStats||{};return JSON.stringify({crashes:b,stats:{connected:!!s.wsConnected,msgs:s.wsMsgs||0}})})()',
                        'returnByValue': True,
                    }, session_id=sid, timeout=3)
                    val = res.get('result', {}).get('result', {}).get('value')
                    if not val: continue
                    data = json.loads(val)
                    for c in data.get('crashes', []):
                        if post_backend({'kind': 'crash', 'value': c['value']}):
                            crash_count += 1
                            print(f'[daemon] CRASH enviado: {c["value"]:.2f}x (total: {crash_count})')
                    # Heartbeat
                    now = time.time()
                    if now - last_heartbeat > 2:
                        post_backend({'kind': 'heartbeat', 'connected': data['stats']['connected'], 'msgsReceived': data['stats']['msgs']})
                        last_heartbeat = now
                except Exception as e:
                    print(f'[daemon] eval err session {sid[:8]}: {e}')
            time.sleep(0.5)
    except KeyboardInterrupt:
        print('\n[daemon] ctrl+c')
    finally:
        cdp.close()


if __name__ == '__main__':
    main()
