"""
Bangbet Nigeria scraper.

API: REST/JSON — https://bet-api.bangbet.com/api/bet/match/list (POST)
Auth: none — country=ng param required on all requests.

Key fields:
  sportId       — "sr:sport:1" football, "sr:sport:2" basketball, "sr:sport:5" tennis
  producer      — 3=prematch, 1=all (live+prematch), 6=virtuals
  groupIndex    — selects market group:
                    football:   0=1X2, 1=Over/Under (all lines), 2=Double Chance, 3=BTTS
                    basketball: 0=Winner H/A, 1=Over/Under
                    tennis:     0=Winner H/A
  pageNo/pageSize — pagination

Response structure:
  data.data[]   — list of match objects
    .id                   — "sr:match:XXXXXXX"
    .homeTeamName / .awayTeamName
    .tournamentName
    .scheduledTime        — Unix ms UTC
    .marketList[].markets[].outcomes[]
      .id    — outcome position id ("1"/"2"/"3")
      .desc  — team name or "draw" / "over X.X" / "under X.X"
      .odds  — decimal odds (float)
      .sort  — display order (1=first, 2=second, 3=third)
"""
import logging
import time
from datetime import datetime, timezone, timedelta
from typing import List, Optional, Tuple

import requests

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

logger = logging.getLogger(__name__)

_API_URL = 'https://bet-api.bangbet.com/api/bet/match/list'

_HEADERS = {
    'User-Agent': (
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
        'AppleWebKit/537.36 (KHTML, like Gecko) '
        'Chrome/120.0.0.0 Safari/537.36'
    ),
    'Accept':       'application/json',
    'Content-Type': 'application/json',
    'Origin':       'https://www.bangbet.com',
    'Referer':      'https://www.bangbet.com/',
}

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

SPORT_GROUP_INDICES = {
    'football':   [0, 1, 2, 3],  # 1X2, O/U, Double Chance, BTTS
    'basketball': [0, 1],         # H/A, O/U
    'tennis':     [0],            # H/A
}

# groupIndex 0, football: outcome id → label
_1X2_MAP  = {'1': 'Home', '2': 'Draw', '3': 'Away'}
# groupIndex 0, basketball/tennis: outcome id → label
_HA_MAP   = {'4': 'Home', '5': 'Away'}
# groupIndex 1 (O/U), all sports: outcome id → label
_OU_MAP   = {'12': 'Over', '13': 'Under'}
# groupIndex 2 (Double Chance), football only: outcome id → label
_DC_MAP   = {'9': '1X', '10': '12', '11': 'X2'}
# groupIndex 3 (BTTS), football only: outcome id → label
_BTTS_MAP = {'74': 'Yes', '76': 'No'}

PAGE_SIZE = 50
MAX_PAGES = 8   # cap at 400 events per sport/group

# Only these O/U lines are worth keeping — Asian quarter-lines won't match other bookmakers
_OU_LINES_ACCEPTED = {'0.5', '1.5', '2.5', '3.5', '4.5', '5.5'}


class BangbetScraper(BaseScraper):

    def __init__(self):
        super().__init__('Bangbet')
        self.session.headers.update(_HEADERS)

    def get_events(self, sport: str) -> List[Event]:
        sport_id = SPORT_IDS.get(sport)
        if not sport_id:
            return []

        all_events: List[Event] = []
        for group_idx in SPORT_GROUP_INDICES.get(sport, [0]):
            try:
                raw = self._fetch_pages(sport_id, group_idx, producer=3)
                all_events.extend(self._parse(raw, sport, group_idx))
            except Exception as ex:
                logger.error(f'[Bangbet] {sport} groupIndex={group_idx}: {ex}')

        return all_events

    def get_live_events(self, sport: str) -> List[Event]:
        sport_id = SPORT_IDS.get(sport)
        if not sport_id:
            return []

        now_ms = int(datetime.now(timezone.utc).timestamp() * 1000)
        all_events: List[Event] = []
        for group_idx in SPORT_GROUP_INDICES.get(sport, [0]):
            try:
                # producer=1 returns all (live + prematch); filter to already-started matches
                raw = self._fetch_pages(sport_id, group_idx, producer=1)
                live = [m for m in raw if (m.get('scheduledTime') or 0) <= now_ms]
                all_events.extend(self._parse(live, sport, group_idx))
            except Exception as ex:
                logger.error(f'[Bangbet] live {sport} groupIndex={group_idx}: {ex}')

        return all_events

    # ── Network ───────────────────────────────────────────────────────────────

    def _fetch_pages(self, sport_id: str, group_index: int, producer: int = 3) -> list:
        items = []
        for page in range(1, MAX_PAGES + 1):
            payload = {
                'sportId':      sport_id,
                'groupIndex':   group_index,
                'tournamentId': '',
                'producer':     producer,
                'position':     17,
                'beginTime':    '',
                'highLight':    True,
                'endTime':      '',
                'showMarket':   True,
                'timeZone':     '+1',
                'page':         page,
                'sortType':     1,
                'pageSize':     PAGE_SIZE,
                'country':      'ng',
                'pageNo':       page,
                'dataGroup':    False,
            }
            resp = self.session.post(_API_URL, json=payload, timeout=30)
            resp.raise_for_status()
            data = resp.json()

            if data.get('result') != 1:
                logger.warning(f'[Bangbet] API result={data.get("result")} info={data.get("info")}')
                break

            page_data = data['data'].get('data') or []
            items.extend(page_data)

            total = data['data'].get('total', 0)
            if not page_data or len(items) >= total:
                break

            time.sleep(0.15)

        return items

    # ── Parsing ───────────────────────────────────────────────────────────────

    def _parse(self, raw: list, sport: str, group_index: int) -> List[Event]:
        cutoff = datetime.now(timezone.utc) + timedelta(hours=config.HOURS_AHEAD)
        events: List[Event] = []

        for item in raw:
            starts_at = self._parse_time(item.get('scheduledTime'))
            if starts_at and starts_at > cutoff:
                continue

            home = item.get('homeTeamName', '').strip()
            away = item.get('awayTeamName', '').strip()
            if not home or not away:
                name = item.get('name', '')
                for sep in (' vs. ', ' vs ', ' - '):
                    if sep in name:
                        home, away = name.split(sep, 1)
                        home, away = home.strip(), away.strip()
                        break
            if not home or not away:
                continue

            ev_id     = item.get('id', '')
            league    = item.get('tournamentName', '')
            event_url = (
                f"https://www.bangbet.com/ng-m/sport/{sport.capitalize()}/match"
                f"?groupId=&matchId={ev_id.replace(':', '%3A')}"
                f"&sportId={SPORT_IDS[sport].replace(':', '%3A')}"
                f"&producer=3&position=16&prediction=20"
            ) if ev_id else None

            for mkt_group in item.get('marketList', []):
                for market in mkt_group.get('markets', []):
                    if not market.get('active') or market.get('marketStatus') != 'Active':
                        continue

                    outcomes, market_name = self._parse_market(
                        market, sport, group_index, event_url
                    )
                    if not outcomes:
                        continue

                    events.append(Event(
                        event_id  = f'bb_{ev_id}_{market_name.replace(" ", "_")}',
                        bookmaker = 'Bangbet',
                        sport     = sport,
                        home_team = home,
                        away_team = away,
                        market    = market_name,
                        outcomes  = outcomes,
                        starts_at = starts_at,
                        league    = league,
                    ))

        return events

    def _parse_market(
        self, market: dict, sport: str, group_index: int, event_url: Optional[str] = None
    ) -> Tuple[List[Outcome], str]:
        outcomes_raw = market.get('outcomes', [])
        specifiers   = market.get('specifiers', '')

        if group_index == 0:
            return self._parse_main_market(outcomes_raw, sport, event_url)
        elif group_index == 1:
            return self._parse_ou(outcomes_raw, specifiers, event_url)
        elif group_index == 2:
            return self._parse_dc(outcomes_raw, event_url)
        elif group_index == 3:
            return self._parse_btts(outcomes_raw, event_url)
        return [], ''

    def _parse_main_market(
        self, outcomes_raw: list, sport: str, event_url: Optional[str] = None
    ) -> Tuple[List[Outcome], str]:
        """1X2 for football, Winner H/A for basketball/tennis."""
        if sport == 'football':
            id_map, market_name, expected = _1X2_MAP, '1X2', 3
        else:
            id_map, market_name, expected = _HA_MAP, 'Home/Away', 2

        outcomes = []
        for oc in outcomes_raw:
            label = id_map.get(str(oc.get('id', '')))
            if label is None:
                continue
            try:
                odds = float(oc['odds'])
            except (KeyError, TypeError, ValueError):
                continue
            if odds <= 1.0:
                continue
            outcomes.append(Outcome(name=label, odds=odds, bookmaker='Bangbet', event_url=event_url))

        if len(outcomes) != expected:
            return [], ''
        return outcomes, market_name

    def _parse_ou(
        self, outcomes_raw: list, specifiers: str, event_url: Optional[str] = None
    ) -> Tuple[List[Outcome], str]:
        """Over/Under — line from specifiers, outcomes by id (12=Over, 13=Under)."""
        line = ''
        if 'total=' in specifiers:
            try:
                line = specifiers.split('total=')[1].split('&')[0].strip()
            except Exception:
                pass
        if not line or line not in _OU_LINES_ACCEPTED:
            return [], ''

        market_name = f'Over/Under {line}'
        outcomes = []
        for oc in outcomes_raw:
            label = _OU_MAP.get(str(oc.get('id', '')))
            if label is None:
                continue
            try:
                odds = float(oc['odds'])
            except (KeyError, TypeError, ValueError):
                continue
            if odds <= 1.0:
                continue
            outcomes.append(Outcome(name=label, odds=odds, bookmaker='Bangbet', event_url=event_url))

        if len(outcomes) == 2:
            return outcomes, market_name
        return [], ''

    def _parse_dc(
        self, outcomes_raw: list, event_url: Optional[str] = None
    ) -> Tuple[List[Outcome], str]:
        """Double Chance — outcome ids: 9=1X, 10=12, 11=X2."""
        outcomes = []
        for oc in outcomes_raw:
            label = _DC_MAP.get(str(oc.get('id', '')))
            if label is None:
                continue
            try:
                odds = float(oc['odds'])
            except (KeyError, TypeError, ValueError):
                continue
            if odds <= 1.0:
                continue
            outcomes.append(Outcome(name=label, odds=odds, bookmaker='Bangbet', event_url=event_url))

        if len(outcomes) == 3:
            return outcomes, 'Double Chance'
        return [], ''

    def _parse_btts(
        self, outcomes_raw: list, event_url: Optional[str] = None
    ) -> Tuple[List[Outcome], str]:
        """Both Teams to Score — outcome ids: 74=Yes, 76=No."""
        outcomes = []
        for oc in outcomes_raw:
            label = _BTTS_MAP.get(str(oc.get('id', '')))
            if label is None:
                continue
            try:
                odds = float(oc['odds'])
            except (KeyError, TypeError, ValueError):
                continue
            if odds <= 1.0:
                continue
            outcomes.append(Outcome(name=label, odds=odds, bookmaker='Bangbet', event_url=event_url))

        if len(outcomes) == 2:
            return outcomes, 'BTTS'
        return [], ''

    @staticmethod
    def _parse_time(ms: Optional[int]) -> Optional[datetime]:
        if not ms:
            return None
        try:
            return datetime.fromtimestamp(ms / 1000, tz=timezone.utc)
        except Exception:
            return None
