"""
BC.Game sportsbook scraper.

Endpoints discovered via Playwright network interception on bc.game

Prematch index:
  GET https://api-k-c7818b61-623.sptpub.com/api/v4/prematch/brand/{BRAND}/en/0
  → {top_events_versions: [...], rest_events_versions: [...], version: <current>}

Data chunks (one per version):
  GET https://api-k-c7818b61-623.sptpub.com/api/v4/prematch/brand/{BRAND}/en/{version}
  → {events: {event_id: {...}}}

Snapshot update cadence: BCGame regenerates all 8 chunks simultaneously every
~60 seconds. The index version numbers all change at once. Delta caching:
on each refresh, fetch the index first; if all version numbers are unchanged
since the last fetch, return the in-memory cache (saves 8 HTTP requests).
When versions change, re-fetch all chunks and rebuild the cache.

Event structure:
  desc.sport        → sport ID string ("1"=Soccer, "2"=Basketball, "5"=Tennis)
  desc.competitors  → [{name: "..."}, {name: "..."}]  (index 0=home, 1=away)
  desc.scheduled    → Unix timestamp (seconds)
  state.status      → 0 = prematch, 1 = live
  markets           → {market_id: {specifier: {outcome_id: {"k": decimal_odds}}}}

Market / outcome ID mapping:
  market "1"   → 1X2       outcomes: "1"=Home "2"=Draw "3"=Away
  market "18"  → Total Goals (filter specifier "total=2.5")  "12"=Over "13"=Under
  market "219" → Basketball moneyline  "4"=Home "5"=Away
  market "186" → Tennis winner         "4"=Home "5"=Away
"""
import logging
from datetime import datetime, timezone
from typing import List, Dict

from scrapers.base import BaseScraper
from core.models import Event, Outcome

logger = logging.getLogger(__name__)

BRAND        = '2103509236163162112'
BASE         = 'https://api-k-c7818b61-623.sptpub.com/api'
IDX_URL      = f'{BASE}/v4/prematch/brand/{BRAND}/en/0'
LIVE_IDX_URL = f'{BASE}/v4/live/brand/{BRAND}/en/0'
CHK_URL      = f'{BASE}/v4/prematch/brand/{{brand}}/en/{{ver}}'
LIVE_CHK_URL = f'{BASE}/v4/live/brand/{{brand}}/en/{{ver}}'

SPORT_IDS = {
    'football':   '1',
    'basketball': '2',
    'tennis':     '5',
}

SPORT_SLUGS = {
    'football':   'soccer',
    'basketball': 'basketball',
    'tennis':     'tennis',
}

# (market_id, specifier_filter_or_None) → {outcome_id: label}
MARKET_CONFIG = {
    'football': [
        ('1',  None,        {'1': 'Home', '2': 'Draw', '3': 'Away'}, '1X2'),
        ('10', None,        {'9': '1X', '10': '12', '11': 'X2'},      'Double Chance'),
        ('18', 'total=0.5', {'12': 'Over', '13': 'Under'},           'Over/Under 0.5'),
        ('18', 'total=1.5', {'12': 'Over', '13': 'Under'},           'Over/Under 1.5'),
        ('18', 'total=2.5', {'12': 'Over', '13': 'Under'},           'Over/Under 2.5'),
        ('18', 'total=3.5', {'12': 'Over', '13': 'Under'},           'Over/Under 3.5'),
        ('18', 'total=4.5', {'12': 'Over', '13': 'Under'},           'Over/Under 4.5'),
        ('68', 'total=0.5', {'12': 'Over', '13': 'Under'},           'HT Over/Under 0.5'),
        ('68', 'total=1.5', {'12': 'Over', '13': 'Under'},           'HT Over/Under 1.5'),
        ('90', 'total=0.5', {'12': 'Over', '13': 'Under'},           '2H Over/Under 0.5'),
        ('90', 'total=1.5', {'12': 'Over', '13': 'Under'},           '2H Over/Under 1.5'),
        ('90', 'total=2.5', {'12': 'Over', '13': 'Under'},           '2H Over/Under 2.5'),
        ('26', None,        {'60': 'Home', '61': 'Away'},            'Draw No Bet'),
        ('11', '__ah__',    {'714': 'Home', '715': 'Away'},          'Asian Handicap'),
        ('29', None,        {'74': 'Yes', '76': 'No'},               'BTTS'),
    ],
    'basketball': [
        ('219', None,    {'4': 'Home', '5': 'Away'},   'Home/Away'),
        ('18',  '__ou__', {'12': 'Over', '13': 'Under'}, 'Over/Under'),  # dynamic lines
    ],
    'tennis': [
        ('186', None, {'4': 'Home', '5': 'Away'}, 'Home/Away'),
    ],
}


def _parse_ah_line(specifier: str):
    """Extract normalised AH line from 'hcp=N' specifier. Returns None for quarter-ball lines."""
    for key in ('hcp=', 'handicap='):
        if key in specifier:
            try:
                raw = specifier.split(key)[1].split('&')[0].strip()
                val = float(raw)
                if abs(val * 4) % 2 != 0:
                    return None
                return f'+{val:g}' if val > 0 else f'{val:g}'
            except (ValueError, IndexError):
                return None
    return None


class BCGameScraper(BaseScraper):

    def __init__(self):
        super().__init__('BCGame')
        self.session.headers.update({
            'Origin':  'https://bc.game',
            'Referer': 'https://bc.game/',
        })
        self._cached_events: Dict = {}
        self._cached_tournaments: Dict = {}
        self._cached_categories: Dict = {}
        self._cached_top_versions: list = []
        self._cached_rest_versions: list = []

    # ── Public API ────────────────────────────────────────────────────────────

    def get_all_events(self) -> List[Event]:
        """Override to fetch all chunks once, then parse all sports."""
        raw_events, tournaments, categories = self._fetch_all_events()
        result: List[Event] = []
        for sport in ['football', 'basketball', 'tennis']:
            try:
                events = self._parse_sport(raw_events, tournaments, categories, sport)
                result.extend(events)
                logger.info(f'[BCGame] {sport}: {len(events)} events')
            except Exception as ex:
                logger.error(f'[BCGame] {sport} parse error: {ex}')
        return result

    def get_events(self, sport: str) -> List[Event]:
        """Used when called individually; re-uses cached raw events if available."""
        raw_events, tournaments, categories = self._fetch_all_events()
        return self._parse_sport(raw_events, tournaments, categories, sport)

    def get_all_live_events(self) -> List[Event]:
        """Fetch all live chunks once, then parse all sports."""
        try:
            raw_events, tournaments, categories = self._fetch_all_live_events()
        except Exception as ex:
            logger.error(f'[BCGame] live fetch error: {ex}')
            return []
        result: List[Event] = []
        for sport in ['football', 'basketball', 'tennis']:
            try:
                events = self._parse_sport(raw_events, tournaments, categories, sport, live=True)
                result.extend(events)
                if events:
                    logger.info(f'[BCGame] live {sport}: {len(events)} events')
            except Exception as ex:
                logger.error(f'[BCGame] live {sport} parse error: {ex}')
        return result

    def get_live_events(self, sport: str) -> List[Event]:
        try:
            raw_events, tournaments, categories = self._fetch_all_live_events()
        except Exception as ex:
            logger.error(f'[BCGame] live fetch error: {ex}')
            return []
        return self._parse_sport(raw_events, tournaments, categories, sport, live=True)

    # ── Fetch ─────────────────────────────────────────────────────────────────

    def _fetch_all_events(self) -> tuple:
        """Return cached snapshot if versions unchanged; full reload otherwise."""
        idx = self.session.get(IDX_URL, timeout=15)
        idx.raise_for_status()
        idx_data = idx.json()

        new_top  = idx_data.get('top_events_versions', [])
        new_rest = idx_data.get('rest_events_versions', [])

        if (new_top  == self._cached_top_versions
                and new_rest == self._cached_rest_versions
                and self._cached_events):
            logger.debug(f'[BCGame] snapshot unchanged, using cache ({len(self._cached_events)} events)')
            return self._cached_events, self._cached_tournaments, self._cached_categories

        # Versions changed (or first load) — re-fetch all chunks
        versions = new_top + new_rest
        merged_events: Dict = {}
        merged_tournaments: Dict = {}
        merged_categories: Dict = {}
        for ver in versions:
            url = CHK_URL.format(brand=BRAND, ver=ver)
            try:
                r = self.session.get(url, timeout=20)
                r.raise_for_status()
                chunk = r.json()
                for eid, ev in chunk.get('events', {}).items():
                    if ev is not None:
                        merged_events[eid] = ev
                merged_tournaments.update(chunk.get('tournaments', {}))
                merged_categories.update(chunk.get('categories', {}))
            except Exception as ex:
                logger.warning(f'[BCGame] chunk {ver} failed: {ex}')

        self._cached_events = merged_events
        self._cached_tournaments = merged_tournaments
        self._cached_categories = merged_categories
        self._cached_top_versions = new_top
        self._cached_rest_versions = new_rest

        logger.info(f'[BCGame] snapshot: {len(merged_events)} events across {len(versions)} chunks')
        return merged_events, merged_tournaments, merged_categories

    def _fetch_all_live_events(self) -> tuple:
        """Fetch the live snapshot (no caching — live data changes every few seconds)."""
        idx = self.session.get(LIVE_IDX_URL, timeout=15)
        idx.raise_for_status()
        idx_data = idx.json()

        versions = idx_data.get('top_events_versions', []) + idx_data.get('rest_events_versions', [])
        merged_events: Dict = {}
        merged_tournaments: Dict = {}
        merged_categories: Dict = {}
        for ver in versions:
            url = LIVE_CHK_URL.format(brand=BRAND, ver=ver)
            try:
                r = self.session.get(url, timeout=20)
                r.raise_for_status()
                chunk = r.json()
                for eid, ev in chunk.get('events', {}).items():
                    if ev is not None:
                        merged_events[eid] = ev
                merged_tournaments.update(chunk.get('tournaments', {}))
                merged_categories.update(chunk.get('categories', {}))
            except Exception as ex:
                logger.warning(f'[BCGame] live chunk {ver} failed: {ex}')

        logger.debug(f'[BCGame] live snapshot: {len(merged_events)} events')
        return merged_events, merged_tournaments, merged_categories

    # ── Parse ─────────────────────────────────────────────────────────────────

    def _parse_sport(self, raw_events: Dict, tournaments: Dict, categories: Dict, sport: str, live: bool = False) -> List[Event]:
        sport_id = SPORT_IDS.get(sport)
        if not sport_id:
            return []

        mkt_configs = MARKET_CONFIG.get(sport, [])
        result: List[Event] = []
        now_utc = datetime.now(timezone.utc).timestamp()

        for eid, ev in raw_events.items():
            desc = ev.get('desc', {})

            if desc.get('sport') != sport_id:
                continue
            if desc.get('virtual', False):
                continue

            ts = desc.get('scheduled')
            if not live:
                # Prematch: skip events whose scheduled time is in the past
                if ts and ts < now_utc:
                    continue

            comps = desc.get('competitors', [])
            if len(comps) < 2:
                continue
            home = comps[0].get('name', '').strip()
            away = comps[1].get('name', '').strip()
            if not home or not away:
                continue

            t_id = desc.get('tournament')
            t_data = tournaments.get(str(t_id), {}) if t_id is not None else {}
            if t_id is not None and not t_data:
                logger.debug(f'[BCGame] tournament id {t_id!r} not found in tournaments dict (keys sample: {list(tournaments.keys())[:5]})')
            league = t_data.get('name', '')
            t_slug = t_data.get('slug', '')
            cat_id = str(t_data.get('category_id', desc.get('category', '')))
            cat_slug = categories.get(cat_id, {}).get('slug', '')
            sport_slug = SPORT_SLUGS.get(sport, sport)
            if cat_slug and t_slug:
                event_url = f'https://bc.game/sports/{sport_slug}/{cat_slug}/{t_slug}/{eid}'
            else:
                event_url = f'https://bc.game/sports/{sport_slug}'

            try:
                starts_at = datetime.utcfromtimestamp(ts) if ts else None
            except Exception:
                starts_at = None

            ev_markets = ev.get('markets', {})

            for mkt_id, spec_filter, outcome_map, market_label in mkt_configs:
                # Dynamic O/U: iterate all "total=X" specifiers (used for basketball)
                if spec_filter == '__ou__':
                    mkt_data = ev_markets.get(mkt_id, {})
                    for spec, spec_data in mkt_data.items():
                        if not spec.startswith('total='):
                            continue
                        line = spec[6:]  # e.g. "185.5"
                        outcomes = []
                        for oid, label in outcome_map.items():
                            entry = spec_data.get(oid)
                            if not entry:
                                continue
                            try:
                                odds = float(entry['k'])
                            except (KeyError, TypeError, ValueError):
                                continue
                            if odds <= 1.0:
                                continue
                            outcomes.append(Outcome(name=label, odds=odds, bookmaker='BCGame', event_url=event_url))
                        if len(outcomes) == len(outcome_map):
                            result.append(Event(
                                event_id  = f'bcg_{eid}_{mkt_id}_{line}',
                                bookmaker = 'BCGame',
                                sport     = sport,
                                home_team = home,
                                away_team = away,
                                market    = f'Over/Under {line}',
                                outcomes  = outcomes,
                                starts_at = starts_at,
                                league    = league,
                            ))
                    continue

                # Dynamic Asian Handicap: iterate all "hcp=X" specifiers
                if spec_filter == '__ah__':
                    mkt_data = ev_markets.get(mkt_id, {})
                    for spec, spec_data in mkt_data.items():
                        if not spec.startswith('hcp='):
                            continue
                        ah_line = _parse_ah_line(spec)
                        if ah_line is None:
                            continue
                        outcomes = []
                        for oid, label in outcome_map.items():
                            entry = spec_data.get(oid)
                            if not entry:
                                continue
                            try:
                                odds = float(entry['k'])
                            except (KeyError, TypeError, ValueError):
                                continue
                            if odds <= 1.0:
                                continue
                            outcomes.append(Outcome(name=label, odds=odds, bookmaker='BCGame', event_url=event_url))
                        if len(outcomes) == len(outcome_map):
                            result.append(Event(
                                event_id  = f'bcg_{eid}_{mkt_id}_{ah_line}',
                                bookmaker = 'BCGame',
                                sport     = sport,
                                home_team = home,
                                away_team = away,
                                market    = f'Asian Handicap {ah_line}',
                                outcomes  = outcomes,
                                starts_at = starts_at,
                                league    = league,
                            ))
                    continue

                parsed = self._parse_market(ev_markets, mkt_id, spec_filter, outcome_map, event_url)
                if parsed:
                    result.append(Event(
                        event_id  = f'bcg_{eid}_{mkt_id}',
                        bookmaker = 'BCGame',
                        sport     = sport,
                        home_team = home,
                        away_team = away,
                        market    = market_label,
                        outcomes  = parsed,
                        starts_at = starts_at,
                        league    = league,
                    ))

        return result

    def _parse_market(
        self,
        ev_markets: Dict,
        mkt_id: str,
        spec_filter,
        outcome_map: Dict,
        event_url: str = None,
    ):
        mkt = ev_markets.get(mkt_id)
        if not mkt:
            return None

        # Pick the right specifier bucket
        if spec_filter is not None:
            spec_data = mkt.get(spec_filter)
        else:
            spec_data = mkt.get('')   # empty-string specifier = no specifier

        if not spec_data:
            return None

        outcomes = []
        for oid, label in outcome_map.items():
            entry = spec_data.get(oid)
            if not entry:
                continue
            try:
                odds = float(entry['k'])
            except (KeyError, TypeError, ValueError):
                continue
            if odds <= 1.0:
                continue
            outcomes.append(Outcome(name=label, odds=odds, bookmaker='BCGame', event_url=event_url))

        if len(outcomes) == len(outcome_map):
            return outcomes
        return None
