#!/usr/bin/env python3
"""
update_cookies.py — paste a cURL command to auto-update credentials in config.py

Usage:
    python3 tools/update_cookies.py

Paste the full cURL command when prompted, then press Ctrl+D (Linux/Mac) or
Ctrl+Z + Enter (Windows). The script detects the bookmaker from the URL or
cookie names, extracts the relevant values, and writes them into config.py.

How to get the cURL command:
    1. Open Chrome DevTools (F12) → Network tab
    2. On 1xBet: place any bet / refresh the page to capture a request to 1xbet.ng
       On Bet9ja: reload the sports page or fetch any event on sports.bet9ja.com
    3. Right-click any request to that domain → Copy → Copy as cURL (bash)
    4. Paste it here

Supported bookmakers and fields updated:
    1xBet    → ONEXBET_USER_TOKEN, ONEXBET_ACCESS_TOKEN, ONEXBET_SESSION,
                ONEXBET_USER_ID (from 'ua' cookie), ONEXBET_X_HD (if present)
    Bet9ja   → BET9JA_LIVSID, BET9JA_AK_BMSC (if present), BET9JA_BM_SV (if present)
    BetKing  → BETKING_AUTHORIZATION (Bearer JWT), BETKING_ARR_AFFINITY cookies
    Accessbet → ACCESSBET_SESSION (USERSESSION cookie)
    BCGame   → BCGAME_TOKEN (authorization JWT), BCGAME_SESSION_TOKEN (session JWT)
               from any authenticated request to 442hattrick.com or bc.game
"""
import os
import re
import sys

CONFIG_PATH = os.path.join(os.path.dirname(__file__), '..', 'config.py')


# ── cURL parser ───────────────────────────────────────────────────────────────

def _unescape(s: str) -> str:
    """Remove surrounding quotes and unescape basic shell escapes."""
    s = s.strip()
    if (s.startswith("'") and s.endswith("'")) or \
       (s.startswith('"') and s.endswith('"')):
        s = s[1:-1]
    return s


def parse_curl(curl_text: str) -> dict:
    """
    Parse a cURL command string into {url, headers, cookies}.
    Handles line continuations (\\n) and both single/double quoted values.
    """
    # Normalize line continuations (backslash + newline → single space)
    text = re.sub(r'\\\r?\n', ' ', curl_text)

    # Extract URL — first quoted argument after 'curl'
    url = ''
    m = re.search(r"curl\s+(?:-[^\s'\"]+\s+)*['\"]([^'\"]+)['\"]", text)
    if not m:
        m = re.search(r"curl\s+(?:--[^\s]+\s+)*['\"]([^'\"]+)['\"]", text)
    if m:
        url = m.group(1)

    # Extract all -H / --header values
    headers: dict[str, str] = {}
    for hm in re.finditer(r'(?:-H|--header)\s+([\'"])(.*?)\1', text):
        raw = hm.group(2)
        if ':' in raw:
            name, _, value = raw.partition(':')
            headers[name.strip().lower()] = value.strip()

    # Some curl exports use --cookie / -b instead of -H Cookie
    cookies: dict[str, str] = {}
    cookie_str = headers.get('cookie', '')
    for bm in re.finditer(r'(?:-b|--cookie)\s+([\'"])(.*?)\1', text):
        cookie_str = cookie_str + '; ' + bm.group(2) if cookie_str else bm.group(2)

    for part in cookie_str.split(';'):
        part = part.strip()
        if '=' in part:
            cname, _, cval = part.partition('=')
            cookies[cname.strip()] = cval.strip()

    return {'url': url, 'headers': headers, 'cookies': cookies}


# ── Bookmaker detection ───────────────────────────────────────────────────────

def detect_bookmaker(parsed: dict) -> str:
    url     = parsed['url'].lower()
    cookies = parsed['cookies']
    headers = parsed['headers']

    if '1xbet' in url or 'user_token' in cookies or 'access_token' in cookies:
        return '1xbet'
    if 'bet9ja' in url or 'livsid' in cookies:
        return 'bet9ja'
    if 'betking' in url or 'inbehalfof' in headers:
        return 'betking'
    if 'accessbet' in url or 'USERSESSION' in cookies:
        return 'accessbet'
    if '442hattrick.com' in url or 'bc.game' in url or 'sptpub.com' in url:
        return 'bcgame'
    return ''


# ── Credential extraction ─────────────────────────────────────────────────────

def extract_1xbet(parsed: dict) -> dict:
    cookies = parsed['cookies']
    headers = parsed['headers']
    updates = {}

    # user_token — from cookie; fallback to x-auth Bearer header
    user_token = cookies.get('user_token', '')
    if not user_token:
        auth = headers.get('x-auth', '')
        m = re.match(r'Bearer\s+(.+)', auth, re.IGNORECASE)
        if m:
            user_token = m.group(1).strip()
    if user_token:
        updates['ONEXBET_USER_TOKEN'] = user_token

    # access_token — from cookie
    if cookies.get('access_token'):
        updates['ONEXBET_ACCESS_TOKEN'] = cookies['access_token']

    # SESSION — from cookie
    if cookies.get('SESSION'):
        updates['ONEXBET_SESSION'] = cookies['SESSION']

    # User ID — from 'ua' cookie (numeric)
    if cookies.get('ua') and cookies['ua'].isdigit():
        updates['ONEXBET_USER_ID'] = cookies['ua']

    # x-hd — optional anti-fingerprint header
    if headers.get('x-hd'):
        updates['ONEXBET_X_HD'] = headers['x-hd']

    return updates


def extract_bet9ja(parsed: dict) -> dict:
    cookies = parsed['cookies']
    updates = {}

    if cookies.get('livsid'):
        updates['BET9JA_LIVSID'] = cookies['livsid']
    if cookies.get('ak_bmsc'):
        updates['BET9JA_AK_BMSC'] = cookies['ak_bmsc']
    if cookies.get('bm_sv'):
        updates['BET9JA_BM_SV'] = cookies['bm_sv']

    return updates


def extract_accessbet(parsed: dict) -> dict:
    cookies = parsed['cookies']
    updates = {}
    if cookies.get('USERSESSION'):
        updates['ACCESSBET_SESSION'] = cookies['USERSESSION']
    return updates


def extract_bcgame(parsed: dict) -> dict:
    headers = parsed['headers']
    cookies = parsed['cookies']
    updates = {}

    # authorization JWT — in Authorization header OR as authorization cookie
    auth_token = headers.get('authorization', '') or cookies.get('authorization', '')
    # Strip "Bearer " prefix if present (bc.game sends it without Bearer)
    m = re.match(r'Bearer\s+(.+)', auth_token, re.IGNORECASE)
    auth_token = m.group(1).strip() if m else auth_token.strip()
    if auth_token:
        updates['BCGAME_TOKEN'] = auth_token

    # session JWT — in session header OR as session cookie
    session_token = headers.get('session', '') or cookies.get('session', '')
    if session_token:
        updates['BCGAME_SESSION_TOKEN'] = session_token

    # Decode fotd from the payload if we can find it — not in cookies/headers
    # It must be set manually from a captured bet payload; skip auto-extraction.

    return updates


def extract_betking(parsed: dict) -> dict:
    headers = parsed['headers']
    cookies = parsed['cookies']
    updates = {}

    # Authorization Bearer JWT
    auth = headers.get('authorization', '')
    m = re.match(r'Bearer\s+(.+)', auth, re.IGNORECASE)
    if m:
        updates['BETKING_AUTHORIZATION'] = m.group(1).strip()

    # Azure sticky-session cookies
    if cookies.get('ARRAffinity'):
        updates['BETKING_ARR_AFFINITY'] = cookies['ARRAffinity']
    if cookies.get('ARRAffinitySameSite'):
        updates['BETKING_ARR_AFFINITY_SAME'] = cookies['ARRAffinitySameSite']

    return updates


# ── config.py writer ──────────────────────────────────────────────────────────

def update_config(updates: dict) -> list[str]:
    """
    Replace values for the given keys in config.py.
    Handles both string assignments (key = 'value') and
    bare integer assignments (key = 12345).
    Returns list of keys that were NOT found.
    """
    with open(CONFIG_PATH, 'r') as f:
        content = f.read()

    missing = []
    for key, value in updates.items():
        # Try string assignment first: KEY = 'old' or KEY = "old"
        pattern_str = rf'^({re.escape(key)}\s*=\s*)[\'"][^\'"]*[\'"]'
        replacement_str = rf"\g<1>'{value}'"
        new_content, n = re.subn(pattern_str, replacement_str, content, flags=re.MULTILINE)

        if n > 0:
            content = new_content
            continue

        # Try bare integer/value assignment: KEY = 123456
        pattern_int = rf'^({re.escape(key)}\s*=\s*)\S+'
        replacement_int = rf"\g<1>{value}"
        new_content, n = re.subn(pattern_int, replacement_int, content, flags=re.MULTILINE)

        if n > 0:
            content = new_content
        else:
            missing.append(key)

    with open(CONFIG_PATH, 'w') as f:
        f.write(content)

    return missing


# ── Main ──────────────────────────────────────────────────────────────────────

def main():
    print('Paste cURL command, then press Ctrl+D (Linux/Mac) or Ctrl+Z+Enter (Windows):')
    print('─' * 60)
    try:
        curl_text = sys.stdin.read()
    except KeyboardInterrupt:
        print('\nAborted.')
        return

    if not curl_text.strip():
        print('No input received.')
        return

    parsed = parse_curl(curl_text)
    bm     = detect_bookmaker(parsed)

    if bm == '1xbet':
        updates = extract_1xbet(parsed)
        label   = '1xBet'
    elif bm == 'bet9ja':
        updates = extract_bet9ja(parsed)
        label   = 'Bet9ja'
    elif bm == 'betking':
        updates = extract_betking(parsed)
        label   = 'BetKing'
    elif bm == 'accessbet':
        updates = extract_accessbet(parsed)
        label   = 'Accessbet'
    elif bm == 'bcgame':
        updates = extract_bcgame(parsed)
        label   = 'BCGame'
    else:
        print('\nCould not detect bookmaker from the curl command.')
        print('Expected a URL containing "1xbet" or "bet9ja", or cookies')
        print('named "user_token" / "access_token" (1xBet) or "livsid" (Bet9ja).')
        return

    print(f'\nDetected: {label}')

    if not updates:
        print('No credentials found in the curl command.')
        print('Make sure the request includes a Cookie header.')
        return

    print(f'\nFound {len(updates)} credential(s):')
    for k, v in updates.items():
        preview = v[:40] + '...' if len(v) > 40 else v
        print(f'  {k:<28} {preview}')

    print(f'\nUpdating {os.path.relpath(CONFIG_PATH)}...')
    missing = update_config(updates)

    if missing:
        for k in missing:
            print(f'  WARNING: {k} not found in config.py — skipped')

    updated = len(updates) - len(missing)
    print(f'\nDone. {updated}/{len(updates)} value(s) updated in config.py.')

    # Show JWT expiry
    import base64, json as _json, time
    jwt_fields = {
        '1xbet':   ['ONEXBET_USER_TOKEN', 'ONEXBET_ACCESS_TOKEN'],
        'betking': ['BETKING_AUTHORIZATION'],
        'bcgame':  ['BCGAME_TOKEN', 'BCGAME_SESSION_TOKEN'],
    }.get(bm, [])
    for field in jwt_fields:
        token = updates.get(field, '')
        if not token:
            continue
        try:
            parts = token.split('.')
            padded = parts[1] + '=' * (-len(parts[1]) % 4)
            payload = _json.loads(base64.urlsafe_b64decode(padded))
            # Standard JWT uses 'exp' (seconds); session tokens may use
            # 'expiredDate' (milliseconds) as used by 442hattrick.com.
            exp = payload.get('exp', 0)
            if not exp:
                exp_ms = payload.get('expiredDate', 0)
                exp = exp_ms / 1000.0 if exp_ms else 0
            remaining = exp - time.time()
            if remaining <= 0:
                print(f'  {field}: EXPIRED')
            elif remaining > 86400:
                print(f'  {field}: expires in {remaining / 86400:.1f}d')
            else:
                print(f'  {field}: expires in {remaining / 3600:.1f}h')
        except Exception:
            pass


if __name__ == '__main__':
    main()
