"""
Bet9ja bet placement driver.

API endpoint (same for prematch AND live):
  POST https://apigw.bet9ja.com/sportsbook/placebet/PlacebetV2
       ?source=desktop&v_cache_version=1.308.4.231

Content-Type: application/x-www-form-urlencoded

Body fields:
  BETSLIP  = URL-encoded JSON (see below)
  BONUS    = 0
  ACCEPT_ODDS_CHANGES = 1   (accept bet even if odds changed)
  IS_PASSBET  = 0
  IS_FIREBETS = 0
  IS_CUT1     = 0

BETSLIP JSON structure:
  {
    "BETS": [{
      "BSTYPE": 3, "TAB": 3, "NUMLINES": 1, "COMB": 1, "TYPE": 1,
      "STAKE": <amount>,
      "POTWINMIN": <round(stake * odds)>,
      "POTWINMAX": <round(stake * odds)>,
      "BONUSMIN": "0", "BONUSMAX": "0",
      "ODDMIN": "<odds>", "ODDMAX": "<odds>",
      "ODDS": {"<event_id>$<odds_key>": "<odds>"},
      "FIXED": {}
    }],
    "IMPERSONIZE": 0
  }

ODDS key format:
  Prematch: "{event_id}${odds_key}"         e.g. "749016274$S_1X2_1"
  Live:     "{event_id}$LIVES_{key[2:]}"    e.g. "9110563$LIVES_1X2_1"
            (drops the leading S_ / B_ / T_ and prepends LIVES_)

Prematch odds keys (same as Bet9ja scraper MARKETS dict):
  Football:
    1X2:         S_1X2_1 (Home), S_1X2_X (Draw), S_1X2_2 (Away)
    Double Chance: S_DC_1X, S_DC_X2, S_DC_12
    Over/Under:  S_OU@{line}_O (Over), S_OU@{line}_U (Under)
    BTTS:        S_GGNG_Y (Yes), S_GGNG_N (No)
    Draw No Bet: S_DNB_1 (Home), S_DNB_2 (Away)
    Asian Handicap: S_AH@{line}_1 (Home), S_AH@{line}_2 (Away)
  Basketball: B_12_1 (Home), B_12_2 (Away)
  Tennis:     T_12_1 (Home), T_12_2 (Away)

Authentication:
  Cookies: livsid (auth session), ftv, ak_bmsc, bm_sv (Akamai anti-bot)
  No Bearer token required.
  livsid is persistent (tied to logged-in account).
  ak_bmsc / bm_sv rotate periodically — update from browser cookies when needed.

Token refresh:
  Visit sports.bet9ja.com → log in → DevTools → Application → Cookies
  Copy: livsid, ak_bmsc, bm_sv
  Update BET9JA_LIVSID, BET9JA_AK_BMSC, BET9JA_BM_SV in config.py
"""
import json
import logging
import re
from typing import Optional
from urllib.parse import quote

import requests

from execution.drivers.base import PlacementDriver, BetResult, parse_numeric_event_id

logger = logging.getLogger(__name__)

BET_URL    = 'https://apigw.bet9ja.com/sportsbook/placebet/PlacebetV2'
BET_PARAMS = {'source': 'desktop', 'v_cache_version': '1.308.4.231'}
EVENT_URL  = 'https://sports.bet9ja.com/desktop/feapi/PalimpsestAjax/GetEvent'
SEED_URL   = 'https://sports.bet9ja.com/'

# ── Odds key tables ───────────────────────────────────────────────────────────

_FOOTBALL_KEYS = {
    '1X2':          {'Home': 'S_1X2_1', 'Draw': 'S_1X2_X', 'Away': 'S_1X2_2'},
    'Double Chance':{'1X': 'S_DC_1X',  'X2': 'S_DC_X2',   '12': 'S_DC_12'},
    'BTTS':         {'Yes': 'S_GGNG_Y', 'No': 'S_GGNG_N'},
    'Draw No Bet':  {'Home': 'S_DNB_1', 'Away': 'S_DNB_2'},
}
_BASKETBALL_KEYS = {
    'Home/Away': {'Home': 'B_12_1', 'Away': 'B_12_2'},
}
_TENNIS_KEYS = {
    'Home/Away': {'Home': 'T_12_1', 'Away': 'T_12_2'},
}
_SPORT_KEYS = {
    'football':   _FOOTBALL_KEYS,
    'basketball': _BASKETBALL_KEYS,
    'tennis':     _TENNIS_KEYS,
}


def _get_odds_key(market: str, outcome: str, sport: str = 'football') -> Optional[str]:
    """
    Return the Bet9ja prematch odds key for a market + outcome combination.
    Returns None if the market is unknown.
    """
    # Sport-specific static lookup first
    sport_keys = _SPORT_KEYS.get(sport, _FOOTBALL_KEYS)
    if market in sport_keys:
        return sport_keys[market].get(outcome)

    # Fall back to all sports (for football markets that don't need sport context)
    for keys in _SPORT_KEYS.values():
        if market in keys:
            return keys[market].get(outcome)

    # Over/Under {line}: S_OU@{line}_O / _U
    m = re.match(r'^Over/Under\s+(\d+\.?\d*)$', market)
    if m:
        lv = m.group(1)
        return f'S_OU@{lv}_O' if outcome == 'Over' else f'S_OU@{lv}_U' if outcome == 'Under' else None

    # Asian Handicap {line}: S_AH@{line}_1 / _2
    m = re.match(r'^Asian Handicap\s+([+-]?\d+\.?\d*)$', market)
    if m:
        raw_line = m.group(1)
        try:
            line_val = float(raw_line)
        except ValueError:
            return None
        # Format: keep sign for negative, no leading + for positive
        line_str = f'{line_val:g}'
        return f'S_AH@{line_str}_1' if outcome == 'Home' else f'S_AH@{line_str}_2' if outcome == 'Away' else None

    logger.warning(f'[Bet9ja] Unknown market for odds key: {market!r} {outcome!r}')
    return None


def _to_live_key(prematch_key: str) -> str:
    """
    Convert a prematch odds key to its live equivalent.

    Pattern: strip the leading sport prefix (S_, B_, T_) and prepend LIVES_
      S_1X2_1      → LIVES_1X2_1
      S_OU@2.5_O   → LIVES_OU@2.5_O
      S_AH@-0.5_1  → LIVES_AH@-0.5_1
      B_12_1       → LIVES_12_1
      T_12_1       → LIVES_12_1
    """
    if re.match(r'^[A-Z]_', prematch_key):
        return 'LIVES_' + prematch_key[2:]
    return 'LIVES_' + prematch_key


# ── Driver ────────────────────────────────────────────────────────────────────

class Bet9jaDriver(PlacementDriver):
    """
    Placement driver for Bet9ja Nigeria.

    Uses session cookies for authentication. Update the cookies in config.py
    whenever you receive auth errors.
    """

    def __init__(
        self,
        livsid:   str,
        ak_bmsc:  str = '',
        bm_sv:    str = '',
    ):
        self.livsid  = livsid
        self.ak_bmsc = ak_bmsc
        self.bm_sv   = bm_sv

        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent':    'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36',
            'Accept':        'application/json, text/plain, */*',
            'Accept-Language':'en-GB,en-US;q=0.9,en;q=0.8',
            'Origin':        'https://sports.bet9ja.com',
            'Referer':       'https://sports.bet9ja.com/',
        })
        self._seeded = False
        self._apply_cookies()

    @classmethod
    def from_config(cls) -> 'Bet9jaDriver':
        """Convenience: build driver from config.py values."""
        import config
        return cls(
            livsid  = config.BET9JA_LIVSID,
            ak_bmsc = getattr(config, 'BET9JA_AK_BMSC', ''),
            bm_sv   = getattr(config, 'BET9JA_BM_SV',   ''),
        )

    def _apply_cookies(self):
        cookies = {
            'ftv':    '1',
            'livsid': self.livsid,
            'livlang':'en',
        }
        if self.ak_bmsc:
            cookies['ak_bmsc'] = self.ak_bmsc
        if self.bm_sv:
            cookies['bm_sv'] = self.bm_sv
        self.session.cookies.update(cookies)

    def _seed(self):
        """Visit sports.bet9ja.com to ensure ftv and other session cookies are set."""
        if self._seeded:
            return
        try:
            self.session.get(
                SEED_URL,
                headers={'Accept': 'text/html,application/xhtml+xml,*/*'},
                timeout=10,
            )
            self._seeded = True
        except Exception as ex:
            logger.warning(f'[Bet9ja] Seed failed: {ex}')

    # ── Auth ──────────────────────────────────────────────────────────────────

    def login(self) -> bool:
        """
        Authenticate using the stored livsid cookie.
        If livsid is empty, the driver cannot place bets.
        """
        if not self.livsid:
            logger.error(
                '[Bet9ja] No livsid configured. '
                'Log into sports.bet9ja.com, go to DevTools → Application → Cookies, '
                'copy the livsid value and set BET9JA_LIVSID in config.py.'
            )
            return False
        self._seed()
        logger.info('[Bet9ja] Session configured with livsid cookie.')
        return True

    def get_balance(self) -> Optional[float]:
        """TODO: find the balance endpoint."""
        raise NotImplementedError('[Bet9ja] Balance endpoint not yet captured.')

    # ── Odds verification ──────────────────────────────────────────────────────

    def verify_odds(
        self,
        event_id:     str,
        outcome_name: str,
        market:       str  = '',
        is_live:      bool = False,
        sport:        str  = 'football',
    ) -> Optional[float]:
        """
        Re-fetch the event via GetEvent and return current odds.
        outcome_name and market are used to derive the odds key.
        """
        event_id = parse_numeric_event_id(event_id)
        self._seed()
        prematch_key = _get_odds_key(market, outcome_name, sport)
        if not prematch_key:
            logger.warning(f'[Bet9ja] Cannot derive odds key for verify: {market} {outcome_name}')
            return None
        try:
            r = self.session.get(
                EVENT_URL,
                params={'EVENTID': event_id},
                timeout=10,
            )
            r.raise_for_status()
            odds_dict = r.json().get('D', {}).get('O', {})
            val = odds_dict.get(prematch_key)
            return float(val) if val is not None else None
        except Exception as ex:
            logger.warning(f'[Bet9ja] verify_odds failed for {event_id}: {ex}')
            return None

    # ── Bet placement ──────────────────────────────────────────────────────────

    def place_bet(
        self,
        event_id:     str,
        outcome_name: str,
        odds:         float,
        stake:        float,
        market:       str  = '',
        is_live:      bool = False,
        sport:        str  = 'football',
    ) -> BetResult:
        """
        Place a single bet on Bet9ja.

        event_id:     numeric Bet9ja event ID (e.g. '749016274')
        outcome_name: 'Home', 'Away', 'Draw', 'Over', 'Under', etc.
        odds:         expected decimal odds
        stake:        amount in NGN
        market:       market name for deriving the odds key
        is_live:      True for in-play bets
        sport:        'football', 'basketball', or 'tennis'
        """
        event_id = parse_numeric_event_id(event_id)
        if not self.livsid:
            return BetResult(
                success=False, bet_id=None, odds_placed=None, stake_placed=None,
                message='No livsid — update BET9JA_LIVSID in config.py'
            )

        # Derive the odds key
        prematch_key = _get_odds_key(market, outcome_name, sport)
        if not prematch_key:
            return BetResult(
                success=False, bet_id=None, odds_placed=None, stake_placed=None,
                message=f'Cannot derive odds key for market={market!r} outcome={outcome_name!r}'
            )

        odds_key = _to_live_key(prematch_key) if is_live else prematch_key
        full_odds_key = f'{event_id}${odds_key}'

        odds_str   = f'{odds:.2f}'
        pot_win    = round(stake * odds)
        stake_int  = int(stake)

        betslip = {
            'BETS': [{
                'BSTYPE':   3,
                'TAB':      3,
                'NUMLINES': 1,
                'COMB':     1,
                'TYPE':     1,
                'STAKE':    stake_int,
                'POTWINMIN':pot_win,
                'POTWINMAX':pot_win,
                'BONUSMIN': '0',
                'BONUSMAX': '0',
                'ODDMIN':   odds_str,
                'ODDMAX':   odds_str,
                'ODDS':     {full_odds_key: odds_str},
                'FIXED':    {},
            }],
            'IMPERSONIZE': 0,
        }

        body = (
            f'BETSLIP={quote(json.dumps(betslip, separators=(",", ":")))}'
            f'&BONUS=0'
            f'&ACCEPT_ODDS_CHANGES=1'
            f'&IS_PASSBET=0'
            f'&IS_FIREBETS=0'
            f'&IS_CUT1=0'
        )

        try:
            self._seed()
            r = self.session.post(
                BET_URL,
                params=BET_PARAMS,
                data=body,
                headers={'Content-Type': 'application/x-www-form-urlencoded'},
                timeout=10,
            )
            r.raise_for_status()
            return self._parse_response(r.json(), odds, stake)
        except requests.HTTPError as ex:
            return BetResult(
                success=False, bet_id=None, odds_placed=None, stake_placed=None,
                message=f'HTTP {ex.response.status_code}: {ex.response.text[:200]}'
            )
        except Exception as ex:
            return BetResult(
                success=False, bet_id=None, odds_placed=None, stake_placed=None,
                message=str(ex)
            )

    # ── Response parser ───────────────────────────────────────────────────────

    def _parse_response(self, data, expected_odds: float, stake: float) -> BetResult:
        """
        Parse the PlacebetV2 response.

        Expected success format (based on common Bet9ja API patterns):
          {"RC": 0, "BETS": [{"ID": "123456", "RC": 0, ...}]}
        or:
          {"RC": 0, "BetId": 123456}

        RC=0 → success. RC≠0 → error (check Msg or ERRMSG field).
        """
        if not isinstance(data, dict):
            logger.warning(f'[Bet9ja] Unexpected response type: {type(data)} — {data}')
            return BetResult(
                success=False, bet_id=None, odds_placed=None, stake_placed=None,
                message=f'Unexpected response: {str(data)[:200]}'
            )

        rc  = data.get('RC', data.get('rc', -1))
        msg = data.get('Msg', data.get('ERRMSG', data.get('Message', '')))

        if rc == 0:
            # Extract bet ID from various possible fields
            bet_id = None
            bets = data.get('BETS', [])
            if bets and isinstance(bets, list):
                bet_id = str(bets[0].get('ID') or bets[0].get('BetId') or '')
            if not bet_id:
                bet_id = str(data.get('BetId') or data.get('ID') or '')

            logger.info(f'[Bet9ja] Bet placed: id={bet_id} odds={expected_odds} stake={stake}')
            return BetResult(
                success     = True,
                bet_id      = bet_id or None,
                odds_placed  = expected_odds,
                stake_placed = stake,
                message     = 'OK',
            )
        else:
            # Common Bet9ja error codes (inferred from typical API behaviour):
            # RC=1  → generic error
            # RC=3  → odds changed
            # RC=5  → insufficient balance
            # RC=10 → event/market suspended
            err = msg or f'Error code RC={rc}'
            logger.warning(f'[Bet9ja] Bet rejected: {err} | raw={data}')
            return BetResult(
                success=False, bet_id=None, odds_placed=None, stake_placed=None,
                message=err,
            )
