"""Cloud sync — espelha crashes/bets locais pro IronDome Cloud (VPS).

Modelo: fire-and-forget. Uma fila in-memory + thread daemon faz POST em batches.
Se o cloud cair, a fila acumula até MAX_QUEUE; depois descarta os mais antigos.
Nunca quebra o app local — todas as exceções são engolidas com log curto.

Config via env:
  IRONDOME_CLOUD_URL   — base URL (ex: https://irondome.vetorizza.com.br)
  IRONDOME_API_KEY     — x-api-key do tenant
  IRONDOME_SYNC        — '1' habilita; qualquer outra coisa desliga

Hooks expostos: enqueue_crash, enqueue_bet_open, enqueue_bet_resolve.
"""
import json
import os
import queue
import threading
import time
import urllib.error
import urllib.request

CLOUD_URL = os.environ.get('IRONDOME_CLOUD_URL', '').rstrip('/')
API_KEY = os.environ.get('IRONDOME_API_KEY', '')
ENABLED = os.environ.get('IRONDOME_SYNC', '0') == '1' and bool(CLOUD_URL) and bool(API_KEY)

MAX_QUEUE = 5000
BATCH_CRASHES = 50
FLUSH_INTERVAL = 2.0
TIMEOUT = 5.0

_q: queue.Queue = queue.Queue(maxsize=MAX_QUEUE)
_thread: threading.Thread | None = None
_local_to_remote_bet_id: dict[int, int] = {}
_id_lock = threading.Lock()


def _post(path: str, body) -> dict | None:
    if not ENABLED:
        return None
    data = json.dumps(body).encode('utf-8')
    req = urllib.request.Request(
        f'{CLOUD_URL}{path}',
        data=data,
        headers={'Content-Type': 'application/json', 'x-api-key': API_KEY},
        method='POST',
    )
    with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
        return json.loads(r.read().decode('utf-8'))


def _drop_oldest_if_full(item):
    try:
        _q.put_nowait(item)
    except queue.Full:
        try:
            _q.get_nowait()
        except queue.Empty:
            pass
        try:
            _q.put_nowait(item)
        except queue.Full:
            pass


def enqueue_crash(value: float, ts: float, inferred: bool = False):
    if not ENABLED:
        return
    payload = {'ts': float(ts), 'value': float(value)}
    if inferred:
        payload['inferred'] = True
    _drop_oldest_if_full(('crash', payload))


def enqueue_bet_open(local_id: int, panel: int, target: float, amount: float,
                     ia_approved: bool, pattern: str | None, probability: float | None,
                     ts: float):
    if not ENABLED or not local_id:
        return
    _drop_oldest_if_full(('bet_open', {
        'local_id': int(local_id),
        'ts': float(ts), 'panel': int(panel), 'target': float(target),
        'amount': float(amount), 'ia_approved': bool(ia_approved),
        'pattern': pattern, 'probability': probability,
    }))


def enqueue_bet_resolve(local_id: int, hit: bool, delta_pnl: float, resolved_ts: float):
    if not ENABLED or not local_id:
        return
    _drop_oldest_if_full(('bet_resolve', {
        'local_id': int(local_id),
        'hit': bool(hit), 'delta_pnl': float(delta_pnl),
        'resolved_ts': float(resolved_ts),
    }))


def _flush_loop():
    pending_crashes: list[dict] = []
    last = time.time()
    while True:
        try:
            kind, payload = _q.get(timeout=FLUSH_INTERVAL)
        except queue.Empty:
            kind = None
            payload = None

        if kind == 'crash':
            pending_crashes.append(payload)
        elif kind == 'bet_open':
            local_id = payload.pop('local_id')
            try:
                resp = _post('/api/v1/bets', payload)
                if resp and 'id' in resp:
                    with _id_lock:
                        _local_to_remote_bet_id[local_id] = int(resp['id'])
            except Exception as e:
                print(f'[cloud_sync] bet_open fail: {e}')
        elif kind == 'bet_resolve':
            local_id = payload.pop('local_id')
            with _id_lock:
                remote_id = _local_to_remote_bet_id.get(local_id)
            if remote_id is None:
                # bet nunca chegou no cloud — desiste
                continue
            try:
                _post('/api/v1/bets/resolve', {'bet_id': remote_id, **payload})
                with _id_lock:
                    _local_to_remote_bet_id.pop(local_id, None)
            except Exception as e:
                print(f'[cloud_sync] bet_resolve fail: {e}')

        now = time.time()
        if pending_crashes and (len(pending_crashes) >= BATCH_CRASHES or now - last >= FLUSH_INTERVAL):
            try:
                _post('/api/v1/crashes/bulk', pending_crashes)
                pending_crashes = []
            except Exception as e:
                print(f'[cloud_sync] crashes bulk fail ({len(pending_crashes)} items): {e}')
                # mantém na fila local pra próximo loop
                if len(pending_crashes) > 500:
                    pending_crashes = pending_crashes[-500:]
            last = now


def start():
    global _thread
    if not ENABLED:
        print('[cloud_sync] disabled')
        return
    if _thread and _thread.is_alive():
        return
    _thread = threading.Thread(target=_flush_loop, daemon=True, name='cloud-sync')
    _thread.start()
    print(f'[cloud_sync] enabled → {CLOUD_URL}')
