"""
BetWinner Nigeria scraper.

Same LineFeed API as 1xBet — only the base domain and partner ID differ.

League list:  GET /service-api/LineFeed/GetSportsShortZip?sports=1&lng=en&country=132&partner=3
Events:       GET /service-api/LineFeed/Get1x2_VZip?sports=1&champs=<LI>&count=500&lng=en&mode=4&country=132&partner=3&getEmpty=true&virtualSports=true

Football: per-league pagination (top MAX_LEAGUES leagues, no 406 issues observed).
Basketball/Tennis: single global call.

Response field mapping (identical to 1xBet):
  O1 / O2  → home / away team name
  I        → event ID
  S        → start time (Unix seconds)
  L        → league name
  E[]      → odds array
    G=1,  T=1/2/3       → 1X2: Home / Draw / Away
    G=2,  T=7/8, P=None → Draw No Bet: Home / Away
    G=2,  T=7/8, P=val  → Asian Handicap: T=7 home side (P=home handicap),
                           T=8 away side (P=away handicap)
    G=17, T=9/10        → Over/Under (P = line value, e.g. 2.5)
    G=101, T=401/402    → Basketball Winner: Team1 / Team2
"""
import re
import time
import logging
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
from typing import List, Tuple

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

logger = logging.getLogger(__name__)

MAX_LEAGUES = 40

BASE_URL    = 'https://betwinner.ng/service-api/LineFeed'
LEAGUES_URL = f'{BASE_URL}/GetSportsShortZip'
EVENTS_URL  = f'{BASE_URL}/Get1x2_VZip'

PARTNER = 3

SPORT_IDS = {
    'football':   '1',
    'basketball': '3',
    'tennis':     '4',
}

MARKETS = {
    1:   ('1X2',            {1: 'Home', 2: 'Draw', 3: 'Away'}),
    5:   ('HT 1X2',         {1: 'Home', 2: 'Draw', 3: 'Away'}),  # football only
    8:   ('Double Chance',  {4: '1X', 5: '12', 6: 'X2'}),        # football only
    17:  ('Over/Under 2.5', {9: 'Over', 10: 'Under'}),
    101: ('Home/Away',      {401: 'Home', 402: 'Away'}),
    19:  ('BTTS',           {1170: 'Yes', 1171: 'No'}),           # football only
}
# G=15 and G=62 exist in the main event but represent team-individual goal markets,
# NOT HT/2H totals. HT/2H markets live in separate sub-events not fetched here.
# G=2 is handled separately: P=None → DNB, P≠None → AH (T=7=Home, T=8=Away)
AH_DNB_G = 2
AH_T_MAP  = {7: 'Home', 8: 'Away'}

# Correct Score — only in GetGameZip, not Get1x2_VZip.
# Same G/T/P encoding as 1xBet (shared Betmaster LineFeed).
CS_G        = 136
_CS_SCORE_T = 731
_CS_OTHER_T = 3786
CS_MAX_EVENTS = 150

TENNIS_MARKET = ('Home/Away', {1: 'Home', 3: 'Away'})

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


def _slugify(text: str) -> str:
    text = text.lower()
    text = re.sub(r'[^a-z0-9 ]', '', text)
    return re.sub(r'\s+', '-', text.strip())


def _decode_cs_p(p) -> str:
    """Decode LineFeed CS P encoding: home_goals + away_goals/1000; None → 0-0."""
    if p is None:
        return '0-0'
    home_g = int(p)
    away_g = round(p * 1000) - home_g * 1000
    return f'{home_g}-{away_g}'


class BetWinnerScraper(BaseScraper):

    def __init__(self):
        super().__init__('BetWinner')
        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://betwinner.ng/en/line/football',
            'Origin': 'https://betwinner.ng',
        }

    # ── Public ────────────────────────────────────────────────────────────────

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

        if sport == 'football':
            return self._get_football_events()
        else:
            return self._get_events_single_call(sport, sport_id)

    # ── Football: per-league pagination ───────────────────────────────────────

    def _get_football_events(self) -> List[Event]:
        try:
            leagues = self._fetch_leagues('1')
        except Exception as ex:
            logger.error(f'[BetWinner] league list failed: {ex}')
            return []

        raw_pool: List[dict] = []
        events: List[Event] = []
        for league_id, league_name in leagues:
            try:
                raw_events = self._fetch_events('1', champs=str(league_id))
                raw_pool.extend(raw_events)
                for raw in raw_events:
                    events.extend(self._parse(raw, 'football'))
                time.sleep(0.3)
            except Exception as ex:
                if '406' in str(ex):
                    logger.warning('[BetWinner] per-league requests blocked (406), falling back to global endpoint')
                    break
                logger.warning(f'[BetWinner] league {league_id} ({league_name}) error: {ex}')

        if not events:
            try:
                raw_fb = self._fetch_events('1')
                raw_pool.extend(raw_fb)
                for raw in raw_fb:
                    events.extend(self._parse(raw, 'football'))
            except Exception as ex:
                logger.error(f'[BetWinner] fallback failed: {ex}')

        events.extend(self._fetch_football_cs(raw_pool))
        return events

    def _fetch_football_cs(self, raw_pool: List[dict]) -> List[Event]:
        seen: set = set()
        unique_raws: List[dict] = []
        for raw in raw_pool:
            eid = str(raw.get('I', ''))
            if eid and eid not in seen:
                seen.add(eid)
                unique_raws.append(raw)
                if len(unique_raws) >= CS_MAX_EVENTS:
                    break
        if not unique_raws:
            return []

        cs_events: List[Event] = []

        def _fetch_one(raw: dict) -> List[Event]:
            eid = str(raw.get('I', ''))
            try:
                r = self.session.get(
                    f'{BASE_URL}/GetGameZip',
                    params={'id': eid, 'lng': 'en', 'isStatic': 'true',
                            'groupEvents': 'true', 'country': '132', 'partner': str(PARTNER)},
                    timeout=15,
                )
                r.raise_for_status()
                return self._parse_cs(r.json().get('Value', {}), raw)
            except Exception as ex:
                logger.debug(f'[BetWinner] GetGameZip {eid} CS: {ex}')
                return []

        with ThreadPoolExecutor(max_workers=10) as pool:
            for result in pool.map(_fetch_one, unique_raws):
                cs_events.extend(result)

        logger.info(f'[BetWinner] CS: {len(cs_events)} events from {len(unique_raws)} GetGameZip calls')
        return cs_events

    def _parse_cs(self, val: dict, raw: dict) -> List[Event]:
        home = (val.get('O1') or raw.get('O1') or '').strip()
        away = (val.get('O2') or raw.get('O2') or '').strip()
        if not home or not away:
            return []

        eid    = str(val.get('I') or raw.get('I') or '')
        li     = val.get('LI') or raw.get('LI')
        league = (val.get('L') or raw.get('L') or '').strip()
        ts     = val.get('S') or raw.get('S')
        try:
            starts_at = datetime.utcfromtimestamp(ts) if ts else None
        except Exception:
            starts_at = None

        if li and eid:
            lg_slug = _slugify(league)
            ev_slug = _slugify(f'{home} {away}')
            event_url = f'https://betwinner.ng/en/line/football/{li}-{lg_slug}/{eid}-{ev_slug}'
        else:
            event_url = 'https://betwinner.ng/en/line/football'

        outcomes: List[Outcome] = []
        for e in val.get('E', []):
            if e.get('G') != CS_G or e.get('CE') == 1:
                continue
            t = e.get('T')
            p = e.get('P')
            try:
                odds = float(e.get('C', 0))
            except (TypeError, ValueError):
                continue
            if odds <= 1.0:
                continue
            if t == _CS_SCORE_T:
                name = _decode_cs_p(p)
            elif t == _CS_OTHER_T and p is None:
                name = 'Any Other Score'
            else:
                continue
            outcomes.append(Outcome(name=name, odds=odds, bookmaker='BetWinner', event_url=event_url))

        if len(outcomes) < 2:
            return []

        return [Event(
            event_id  = f'bwn_{eid}_cs',
            bookmaker = 'BetWinner',
            sport     = 'football',
            home_team = home,
            away_team = away,
            market    = 'Correct Score',
            outcomes  = outcomes,
            starts_at = starts_at,
            league    = league,
        )]

    def _fetch_leagues(self, sport_id: str) -> List[Tuple[int, str]]:
        r = self.session.get(
            LEAGUES_URL,
            params={
                'sports':        sport_id,
                'lng':           'en',
                'country':       '132',
                'partner':       PARTNER,
                'virtualSports': 'true',
                'gr':            '412',
            },
            timeout=15,
        )
        r.raise_for_status()
        sport_data = r.json().get('Value', [])
        if not sport_data:
            return []
        leagues = sport_data[0].get('L', [])
        leagues_sorted = sorted(leagues, key=lambda x: x.get('GC', 0), reverse=True)
        return [(lg['LI'], lg.get('L', '')) for lg in leagues_sorted[:MAX_LEAGUES] if 'LI' in lg]

    # ── Basketball / Tennis: single call ──────────────────────────────────────

    def _get_events_single_call(self, sport: str, sport_id: str) -> List[Event]:
        events: List[Event] = []
        try:
            for raw in self._fetch_events(sport_id):
                events.extend(self._parse(raw, sport))
        except Exception as ex:
            logger.error(f'[BetWinner] {sport} fetch error: {ex}')
        return events

    # ── Shared fetch ──────────────────────────────────────────────────────────

    def get_live_events(self, sport: str) -> List[Event]:
        """Fetch in-play events via the LiveFeed endpoint (separate from prematch LineFeed)."""
        sport_id = SPORT_IDS.get(sport)
        if not sport_id:
            return []
        events: List[Event] = []
        try:
            raw_events = self._fetch_live_events(sport_id)
            for raw in raw_events:
                events.extend(self._parse(raw, sport, live=True))
        except Exception as ex:
            logger.error(f'[BetWinner] live {sport} fetch error: {ex}')
        return events

    def _fetch_live_events(self, sport_id: str) -> list:
        r = self.session.get(
            BASE_URL.replace('LineFeed', 'LiveFeed') + '/Get1x2_VZip',
            params={
                'sports':  sport_id,
                'count':   '500',
                'lng':     'en',
                'mode':    '1',
                'country': '132',
                'partner': str(PARTNER),
            },
            timeout=15,
        )
        r.raise_for_status()
        return r.json().get('Value', [])

    def _fetch_events(self, sport_id: str, champs: str = None) -> list:
        params = {
            'sports':        sport_id,
            'count':         '500',
            'lng':           'en',
            'mode':          '4',
            'country':       '132',
            'partner':       PARTNER,
            'getEmpty':      'true',
            'virtualSports': 'true',
        }
        if champs:
            params['champs'] = champs
        r = self.session.get(EVENTS_URL, params=params, timeout=15)
        r.raise_for_status()
        return r.json().get('Value', [])

    # ── Parser ────────────────────────────────────────────────────────────────

    def _parse(self, raw: dict, sport: str, live: bool = False) -> List[Event]:
        home = (raw.get('O1') or '').strip()
        away = (raw.get('O2') or '').strip()
        if not home or not away:
            return []

        ts = raw.get('S')
        try:
            starts_at = datetime.utcfromtimestamp(ts) if ts else None
        except Exception:
            starts_at = None

        event_id_base = str(raw.get('I', ''))
        league = raw.get('L', '')

        li         = raw.get('LI')
        event_i    = raw.get('I')
        sport_slug = SPORT_SLUGS.get(sport, sport)
        if li and event_i:
            lg_slug = _slugify(league)
            ev_slug = _slugify(f'{home} {away}')
            if live:
                event_url = f'https://betwinner.ng/en/live/{sport_slug}/{event_i}-{ev_slug}'
            else:
                event_url = (
                    f'https://betwinner.ng/en/line/{sport_slug}'
                    f'/{li}-{lg_slug}/{event_i}-{ev_slug}'
                )
        else:
            event_url = f'https://betwinner.ng/en/{"live" if live else "line"}/{sport_slug}'

        markets_data: dict = {}
        for entry in raw.get('E', []):
            if live and entry.get('CE', 0) == 1:
                continue  # suspended/closed market entry
            g = entry.get('G')
            if g not in MARKETS and not (g == AH_DNB_G and sport == 'football') and not (sport == 'tennis' and g == 1):
                continue
            markets_data.setdefault(g, []).append(entry)

        result: List[Event] = []

        # G=2: Draw No Bet (P=None) and Asian Handicap (P≠None) — football only
        if AH_DNB_G in markets_data and sport == 'football':
            g2_entries = markets_data.pop(AH_DNB_G)
            dnb_entries = [e for e in g2_entries if e.get('P') is None]
            ah_entries  = [e for e in g2_entries if e.get('P') is not None]

            # Draw No Bet
            dnb_outcomes = []
            for e in dnb_entries:
                label = AH_T_MAP.get(e.get('T'))
                if label is None:
                    continue
                try:
                    odds = float(e.get('C', 0))
                except (TypeError, ValueError):
                    continue
                if odds > 1.0:
                    dnb_outcomes.append(Outcome(name=label, odds=odds, bookmaker='BetWinner', event_url=event_url))
            if len(dnb_outcomes) == 2:
                result.append(Event(
                    event_id  = f'bwn_{event_id_base}_dnb',
                    bookmaker = 'BetWinner',
                    sport     = sport,
                    home_team = home,
                    away_team = away,
                    market    = 'Draw No Bet',
                    outcomes  = dnb_outcomes,
                    starts_at = starts_at,
                    league    = league,
                ))

            # Asian Handicap
            home_odds: dict = {}
            away_odds: dict = {}
            for e in ah_entries:
                label = AH_T_MAP.get(e.get('T'))
                p = e.get('P')
                if label is None or p is None:
                    continue
                try:
                    p_val = float(p)
                    odds  = float(e.get('C', 0))
                except (TypeError, ValueError):
                    continue
                if odds <= 1.0:
                    continue
                if label == 'Home':
                    home_odds[p_val] = odds
                else:
                    away_odds[p_val] = odds

            for home_p, home_cf in home_odds.items():
                if abs(home_p * 4) % 2 != 0:
                    continue
                away_p = -home_p
                away_cf = away_odds.get(away_p)
                if away_cf is None:
                    away_cf = next((v for k, v in away_odds.items() if abs(k - away_p) < 1e-9), None)
                if away_cf is None:
                    continue
                line = f'+{home_p:g}' if home_p > 0 else f'{home_p:g}'
                result.append(Event(
                    event_id  = f'bwn_{event_id_base}_ah{line}',
                    bookmaker = 'BetWinner',
                    sport     = sport,
                    home_team = home,
                    away_team = away,
                    market    = f'Asian Handicap {line}',
                    outcomes  = [
                        Outcome(name='Home', odds=home_cf, bookmaker='BetWinner', event_url=event_url),
                        Outcome(name='Away', odds=away_cf, bookmaker='BetWinner', event_url=event_url),
                    ],
                    starts_at = starts_at,
                    league    = league,
                ))

        for g, entries in markets_data.items():
            if g in (5, 8, 19) and sport != 'football':
                continue
            if sport == 'tennis' and g == 1:
                market_name, outcome_map = TENNIS_MARKET
            else:
                market_name, outcome_map = MARKETS[g]

            if g == 17:
                lines = (0.5, 1.5, 2.5, 3.5, 4.5)
                mkt_prefix = 'Over/Under'
                for lv in lines:
                    lv_entries = [e for e in entries if e.get('P') == lv]
                    ou_outcomes = []
                    for e in lv_entries:
                        label = outcome_map.get(e.get('T'))
                        if label is None:
                            continue
                        try:
                            odds = float(e.get('C', 0))
                        except (TypeError, ValueError):
                            continue
                        if odds > 1.0:
                            ou_outcomes.append(Outcome(name=label, odds=odds, bookmaker='BetWinner', event_url=event_url))
                    if len(ou_outcomes) == len(outcome_map):
                        result.append(Event(
                            event_id=f'bwn_{event_id_base}_{g}_{lv}',
                            bookmaker='BetWinner',
                            sport=sport,
                            home_team=home,
                            away_team=away,
                            market=f'{mkt_prefix} {lv}',
                            outcomes=ou_outcomes,
                            starts_at=starts_at,
                            league=league,
                        ))
                continue

            outcomes = []
            for e in entries:
                t = e.get('T')
                label = outcome_map.get(t)
                if label is None:
                    continue
                try:
                    odds = float(e.get('C', 0))
                except (TypeError, ValueError):
                    continue
                if odds > 1.0:
                    outcomes.append(Outcome(name=label, odds=odds, bookmaker='BetWinner', event_url=event_url))

            if len(outcomes) == len(outcome_map):
                result.append(Event(
                    event_id=f'bwn_{event_id_base}_{g}',
                    bookmaker='BetWinner',
                    sport=sport,
                    home_team=home,
                    away_team=away,
                    market=market_name,
                    outcomes=outcomes,
                    starts_at=starts_at,
                    league=league,
                ))

        return result
