"""
BetKing Nigeria scraper.

Endpoints discovered via Playwright network interception on betking.com/sports/s/football/

Prematch events + odds:
  GET https://sportsapicdn-desktop.betking.com/api/feeds/prematch/mostpopularsports/en/{sport_id}/{days}/{count}/
  Response: data[0]['AreaMatches'][0]['Items'] — each Item has OddsCollection/MatchOdds

Live events:
  Step 1: GET /api/feeds/live/mostpopular/{sport_id}/200 → event IDs + metadata
  Step 2: GET /api/feeds/live/{match_id}/en → full odds per match
          Response: Tournaments[].Events[].Markets[].Selections[].Odds[0].Value
  Fetched concurrently with ThreadPoolExecutor.

Market TypeIds (same for prematch and live):
  Football:   110=1X2, 146=Double Chance, 147=Draw No Bet, 160=O/U, 302=BTTS
  Basketball: 9300=Moneyline
  Tennis:     9388=Home/Away

Prematch MatchOdds entries are duplicated — deduplicated by MatchOddsID before parsing.
"""
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timezone
from typing import List

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

logger = logging.getLogger(__name__)

BASE_URL      = "https://sportsapicdn-desktop.betking.com/api/feeds/prematch/mostpopularsports/en"
LIVE_BASE_URL = "https://sportsapicdn-desktop.betking.com/api/feeds/live"

# BetKing sport IDs
SPORT_IDS = {
    'football':   1,
    'basketball': 2,
    'tennis':     5,
}

# {sport: {OddsTypeID: (market_name, {OddName: label}, special_value_filter)}}
# special_value_filter=None means accept all; a float means only accept that SpecialValue
MARKETS = {
    'football': {
        110: ('1X2',            {'1': 'Home', 'X': 'Draw', '2': 'Away'}, None),
        146: ('Double Chance',  {'1X': '1X', 'X2': 'X2', '12': '12'},   None),
        147: ('Draw No Bet',    {'1 DNB': 'Home', '2 DNB': 'Away'},       None),
        160: ('Over/Under',     {'Over': 'Over', 'Under': 'Under'},       {0.5, 1.5, 2.5, 3.5, 4.5}),
        302: ('BTTS',           {'GG': 'Yes', 'NG': 'No'},                None),
    },
    'basketball': {
        9300: ('Home/Away', {'1': 'Home', '2': 'Away'}, None),
    },
    'tennis': {
        9388: ('Home/Away', {'1': 'Home', '2': 'Away'}, None),
    },
}

DAYS_AHEAD  = 7
EVENT_COUNT = 200   # max events per request

# Live market TypeId → (market_name, {SelectionName: label}, special_value_filter)
# Selection names in the live feed differ slightly from MatchOdds OddNames in prematch
LIVE_MARKETS = {
    'football': {
        110: ('1X2',           {'1': 'Home', 'X': 'Draw', '2': 'Away'},         None),
        146: ('Double Chance', {'1X': '1X',  'X2': 'X2',  '12': '12'},          None),
        147: ('Draw No Bet',   {'1 DNB': 'Home', '2 DNB': 'Away'},               None),
        160: ('Over/Under',    {'Over': 'Over', 'Under': 'Under'},               {0.5, 1.5, 2.5, 3.5, 4.5}),
        302: ('BTTS',          {'GG': 'Yes', 'NG': 'No'},                        None),
    },
    'basketball': {
        9300: ('Home/Away', {'1': 'Home', '2': 'Away'}, None),
    },
    'tennis': {
        9388: ('Home/Away', {'1': 'Home', '2': 'Away'}, None),
    },
}


class BetKingScraper(BaseScraper):

    def __init__(self):
        super().__init__('BetKing')
        self.session.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, text/plain, */*',
            'Referer': 'https://www.betking.com/sports/s/football/',
            'Origin': 'https://www.betking.com',
        }

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

        events: List[Event] = []
        try:
            raw_items = self._fetch(sport_id)
            for item in raw_items:
                events.extend(self._parse(item, sport))
        except Exception as ex:
            logger.error(f"[BetKing] {sport} fetch error: {ex}")

        return events

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

        # Step 1: get all live match IDs for this sport
        try:
            r = self.session.get(f'{LIVE_BASE_URL}/mostpopular/{sport_id}/200', timeout=15)
            r.raise_for_status()
            raw_events = r.json().get('Events', [])
        except Exception as ex:
            logger.error(f'[BetKing] live {sport} list error: {ex}')
            return []

        if not raw_events:
            return []

        # Step 2: fetch per-match odds concurrently
        match_ids = [e['Id'] for e in raw_events]
        meta_by_id = {e['Id']: e for e in raw_events}

        def _fetch_match(match_id: int):
            try:
                r = self.session.get(f'{LIVE_BASE_URL}/{match_id}/en', timeout=10)
                r.raise_for_status()
                return match_id, r.json()
            except Exception as ex:
                logger.warning(f'[BetKing] live match {match_id} error: {ex}')
                return match_id, None

        events: List[Event] = []
        with ThreadPoolExecutor(max_workers=min(len(match_ids), 20)) as pool:
            for match_id, data in pool.map(_fetch_match, match_ids):
                if not data:
                    continue
                meta = meta_by_id.get(match_id, {})
                for tournament in data.get('Tournaments', []):
                    league = tournament.get('Name', '')
                    for raw in tournament.get('Events', []):
                        events.extend(self._parse_live(raw, sport, league))

        return events

    def _parse_live(self, raw: dict, sport: str, league: str = '') -> List[Event]:
        name = (raw.get('Name') or '').strip()
        if ' - ' not in name:
            return []

        home, away = [t.strip() for t in name.split(' - ', 1)]
        if not home or not away:
            return []

        date_str = raw.get('Date', '')
        try:
            starts_at = datetime.fromisoformat(date_str).astimezone(timezone.utc).replace(tzinfo=None) if date_str else None
        except Exception:
            starts_at = None

        event_id = str(raw.get('Id', ''))
        event_url = f'https://www.betking.com/sports/live/s/event/{event_id}' if event_id else None
        sport_markets = LIVE_MARKETS.get(sport, {})
        result: List[Event] = []

        for market in raw.get('Markets', []):
            if market.get('Status', 1) == 0:
                continue  # suspended market
            type_id = market.get('TypeId')
            if type_id not in sport_markets:
                continue

            market_name, name_map, sv_filter = sport_markets[type_id]
            special_val = market.get('SpecialValue', '')
            try:
                sv = float(special_val) if special_val else None
            except (ValueError, TypeError):
                sv = None

            # For O/U: filter to the lines we want
            if isinstance(sv_filter, set):
                if sv not in sv_filter:
                    continue
                market_name = f'Over/Under {sv}'

            outcomes = []
            for sel in market.get('Selections', []):
                sel_name = sel.get('Name', '')
                label = name_map.get(sel_name)
                if label is None:
                    continue
                odds_list = sel.get('Odds', [])
                if not odds_list:
                    continue
                odd = odds_list[0]
                if odd.get('Status', 1) == 0:
                    continue  # suspended selection
                try:
                    odds_val = float(odd.get('Value', 0))
                except (TypeError, ValueError):
                    continue
                if odds_val > 1.0:
                    outcomes.append(Outcome(name=label, odds=odds_val, bookmaker='BetKing', event_url=event_url))

            if len(outcomes) == len(name_map):
                result.append(Event(
                    event_id=f"bk_live_{event_id}_{type_id}{'_' + str(sv) if sv else ''}",
                    bookmaker='BetKing',
                    sport=sport,
                    home_team=home,
                    away_team=away,
                    market=market_name,
                    outcomes=outcomes,
                    starts_at=starts_at,
                    league=league,
                ))

        return result

    def _fetch(self, sport_id: int) -> list:
        url = f"{BASE_URL}/{sport_id}/{DAYS_AHEAD}/{EVENT_COUNT}/"
        r = self.session.get(url, timeout=20)
        r.raise_for_status()
        data = r.json()
        # data[0]['AreaMatches'][0]['Items']
        try:
            return data[0]['AreaMatches'][0]['Items']
        except (IndexError, KeyError, TypeError):
            return []

    def _parse(self, item: dict, sport: str) -> List[Event]:
        name = (item.get('ItemName') or '').strip()
        if ' - ' not in name:
            return []

        home, away = [t.strip() for t in name.split(' - ', 1)]
        if not home or not away:
            return []

        league = (item.get('TournamentName') or '').strip()
        date_str = item.get('ItemDate', '')
        try:
            # Format: "2026-04-11T13:30:00+02:00" — strip tz so it's consistent with other scrapers
            starts_at = datetime.fromisoformat(date_str).astimezone(timezone.utc).replace(tzinfo=None) if date_str else None
        except Exception:
            starts_at = None

        event_id_base = str(item.get('ItemID', ''))
        event_url = f'https://www.betking.com/sports/s/event/{event_id_base}' if event_id_base else None
        sport_markets = MARKETS.get(sport, {})
        result: List[Event] = []

        for oc in item.get('OddsCollection', []):
            odds_type = oc.get('OddsType', {})
            type_id = odds_type.get('OddsTypeID')
            if type_id not in sport_markets:
                continue

            market_name, name_map, sv_filter = sport_markets[type_id]

            # Deduplicate MatchOdds by MatchOddsID
            seen_ids = set()
            unique_odds = []
            for mo in oc.get('MatchOdds', []):
                mid = mo.get('MatchOddsID')
                if mid not in seen_ids:
                    seen_ids.add(mid)
                    unique_odds.append(mo)

            # For O/U (sv_filter is a set): group outcomes by line, emit one Event per line
            if isinstance(sv_filter, set):
                by_line: dict = {}
                for mo in unique_odds:
                    attr = mo.get('OddAttribute', {})
                    sv = attr.get('SpecialValue')
                    if sv not in sv_filter:
                        continue
                    label = name_map.get(attr.get('OddName', ''))
                    if label is None:
                        continue
                    try:
                        odds_val = float(mo['Outcome']['OddOutcome'])
                    except (KeyError, TypeError, ValueError):
                        continue
                    if odds_val > 1.0:
                        by_line.setdefault(sv, []).append(
                            Outcome(name=label, odds=odds_val, bookmaker='BetKing', event_url=event_url)
                        )
                for sv, line_outcomes in by_line.items():
                    if len(line_outcomes) == len(name_map):
                        result.append(Event(
                            event_id=f"bk_{event_id_base}_{type_id}_{sv}",
                            bookmaker='BetKing',
                            sport=sport,
                            home_team=home,
                            away_team=away,
                            market=f'Over/Under {sv}',
                            outcomes=line_outcomes,
                            starts_at=starts_at,
                            league=league,
                        ))
                continue

            outcomes = []
            for mo in unique_odds:
                attr = mo.get('OddAttribute', {})
                odd_name = attr.get('OddName', '')
                sv = attr.get('SpecialValue')

                if sv_filter is not None and sv != sv_filter:
                    continue

                label = name_map.get(odd_name)
                if label is None:
                    continue

                try:
                    odds_val = float(mo['Outcome']['OddOutcome'])
                except (KeyError, TypeError, ValueError):
                    continue

                if odds_val > 1.0:
                    outcomes.append(Outcome(name=label, odds=odds_val, bookmaker='BetKing', event_url=event_url))

            if len(outcomes) == len(name_map):
                result.append(Event(
                    event_id=f"bk_{event_id_base}_{type_id}",
                    bookmaker='BetKing',
                    sport=sport,
                    home_team=home,
                    away_team=away,
                    market=market_name,
                    outcomes=outcomes,
                    starts_at=starts_at,
                    league=league,
                ))

        return result
