"""
Direct WebSocket connection to Surebet247 backend to discover the API format.

Protocol:
- WebSocket at wss://sport-iframe.serhjs.xyz/direct-feed/feed?brand=CL38B1&X-Api-Key=...
- Each frame: 1-byte payload length prefix + MessagePack body (for small frames)
  OR 3-byte prefix (0x00 + 2-byte big-endian length) for larger frames?
  Actually: the first byte IS the payload byte count, confirming len(payload) == first_byte.

Request format (msgpack array):
  [4, {}, id_str, method_name, args]
  type=4 = RPC call

Response format:
  [2, {}, id_str, error_or_null, result]
  type=2 = RPC reply

Methods discovered from Playwright capture:
  - GetSportsByStage(stage, context)         stage: 3=prematch
  - GetEventsBySportAndTimeRange(sport, timerange, context)
      sport: 'F'=football, 'B'=basketball, 'T'=tennis
      timerange: [0, N] where N = hours ahead
  - GetRichEventsByTournamentIdAndStage(tournament_id, stage, context)
  - GetMainMarketsByProfileAndEventIds(profile, event_ids, version, flag, context)
      profile: 'pro_main_period'

Context = ['en', 'MOBILE_WEB', 'CL38B1', '', 'NGN']
"""
import json
import struct
import time
import threading
import msgpack
import websocket

WS_URL = (
    'wss://sport-iframe.serhjs.xyz/direct-feed/feed'
    '?brand=CL38B1'
    '&X-Api-Key=b8b92942-ce6a-44e8-a5d5-6bf407e316a2'
)
CONTEXT = ['en', 'MOBILE_WEB', 'CL38B1', '', 'NGN']

_req_id = [0]


def make_request(method: str, args: list) -> bytes:
    _req_id[0] += 1
    rid = str(_req_id[0])
    body = msgpack.packb([4, {}, rid, method, args], use_bin_type=True)
    return bytes([len(body)]) + body


def decode_frame(data: bytes):
    """Try to decode a received binary frame."""
    if not data:
        return None
    # Skip length prefix byte(s)
    # Try 1-byte prefix first
    try:
        return msgpack.unpackb(data[1:], raw=False, strict_map_key=False)
    except Exception:
        pass
    # Try 2-byte prefix
    try:
        return msgpack.unpackb(data[2:], raw=False, strict_map_key=False)
    except Exception:
        pass
    # Try 3-byte prefix
    try:
        return msgpack.unpackb(data[3:], raw=False, strict_map_key=False)
    except Exception:
        pass
    # Try no prefix
    try:
        return msgpack.unpackb(data, raw=False, strict_map_key=False)
    except Exception:
        pass
    return None


def main():
    results = {}
    got_sports = threading.Event()
    got_events = threading.Event()
    got_markets = threading.Event()

    football_event_ids = []
    ws_ref = [None]

    def on_open(ws):
        ws_ref[0] = ws
        print('[WS] Connected')
        # The Centrifuge protocol requires a JSON text handshake first
        # to negotiate messagepack binary protocol
        ws.send('{"protocol":"messagepack","version":1}')

    handshake_done = [False]

    def on_message(ws, msg):
        if isinstance(msg, str):
            print(f'[TEXT] {msg[:200]}')
            return
        # The server sends b'{}\x1e' as a record-separator delimited JSON ACK
        # after we send the protocol negotiation
        if not handshake_done[0]:
            print(f'[BIN handshake] {msg[:50]}')
            handshake_done[0] = True
            # Now send the actual GetSportsByStage request
            print('[→] Sending GetSportsByStage')
            req = make_request('GetSportsByStage', [3, CONTEXT])
            ws.send(req, opcode=websocket.ABNF.OPCODE_BINARY)
            return
        # Binary frame
        decoded = decode_frame(msg)
        if decoded is None:
            print(f'[BIN] Cannot decode {len(msg)} bytes: {msg[:30]}')
            return

        if not isinstance(decoded, (list, tuple)) or len(decoded) < 3:
            print(f'[MSG] Unexpected format: {str(decoded)[:200]}')
            return

        msg_type = decoded[0]
        rid = decoded[2] if len(decoded) > 2 else '?'
        result = decoded[4] if len(decoded) > 4 else decoded[3] if len(decoded) > 3 else None

        if msg_type == 2:  # RPC reply
            print(f'\n[REPLY id={rid}]')
            print(json.dumps(result, default=str, ensure_ascii=False)[:3000])
            results[rid] = result

            # After receiving sports, request football events
            if rid == '1' and not got_sports.is_set():
                got_sports.set()
                # GetEventsBySportAndTimeRange: sport='F', timerange=[0,24], context
                print('\n[→] Sending GetEventsBySportAndTimeRange for football (next 24h)')
                req = make_request('GetEventsBySportAndTimeRange', ['F', [0, 24], CONTEXT])
                ws.send(req, opcode=websocket.ABNF.OPCODE_BINARY)

            elif rid == '2' and not got_events.is_set():
                got_events.set()
                # Extract event IDs from result
                if isinstance(result, list):
                    for item in result:
                        if isinstance(item, (list, tuple)) and len(item) >= 1:
                            eid = item[0]
                            if isinstance(eid, int):
                                football_event_ids.append(str(eid))
                        elif isinstance(item, dict):
                            eid = item.get('id') or item.get('eventId')
                            if eid:
                                football_event_ids.append(str(eid))

                print(f'\n[INFO] Got {len(football_event_ids)} event IDs')
                if football_event_ids:
                    print(f'  First 5: {football_event_ids[:5]}')
                    # Fetch markets for first 4 events
                    sample_ids = football_event_ids[:4]
                    print(f'\n[→] Sending GetMainMarketsByProfileAndEventIds for {sample_ids}')
                    req = make_request('GetMainMarketsByProfileAndEventIds', [
                        'pro_main_period', sample_ids, 4, True, CONTEXT
                    ])
                    ws.send(req, opcode=websocket.ABNF.OPCODE_BINARY)
                else:
                    got_markets.set()

            elif rid == '3' and not got_markets.is_set():
                got_markets.set()

        elif msg_type == 4:  # Push message
            print(f'[PUSH id={rid}] {str(result)[:200]}')

    def on_error(ws, err):
        print(f'[ERROR] {err}')

    def on_close(ws, code, msg):
        print(f'[CLOSED] code={code}')

    ws = websocket.WebSocketApp(
        WS_URL,
        header={
            'Origin': 'https://sport-iframe.serhjs.xyz',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
        },
        on_open=on_open,
        on_message=on_message,
        on_error=on_error,
        on_close=on_close,
    )

    t = threading.Thread(target=lambda: ws.run_forever(), daemon=True)
    t.start()

    # Wait for markets or timeout
    deadline = time.time() + 30
    while time.time() < deadline:
        if got_markets.is_set():
            break
        time.sleep(0.5)

    ws.close()
    print('\n\n=== SUMMARY ===')
    print(f'Sports reply:  {"YES" if "1" in results else "NO"}')
    print(f'Events reply:  {"YES" if "2" in results else "NO"} ({len(football_event_ids)} events)')
    print(f'Markets reply: {"YES" if "3" in results else "NO"}')


if __name__ == '__main__':
    main()
