"""
Betway Nigeria scraper.

Endpoints discovered via Playwright network interception on betway.com.ng

Events + odds:
  GET https://feeds-roa2.betwayafrica.com/br/_apis/sport/v1/BetBook/Upcoming/
  Params: countryCode=NG, sportId=soccer, marketTypes=[Win/Draw/Win],[Total Goals]
          cultureCode=en-US, boostedOnly=false, Skip=0, Take=200,
          ToStartEpoch=<unix_ts>

  Paginate using Skip until isFinalPage=True.
  eSoccer events (regionId contains "esoccer") are filtered client-side.

Response structure (flat arrays linked by ID):
  events[]   → eventId, homeTeam, awayTeam, expectedStartEpoch, isLive,
               regionId (contains "esoccer" for virtual events)
  markets[]  → marketId, name ("[Win/Draw/Win]" / "[Total Goals]"), eventId,
               handicap, isSquashedParent, squashedMarketIds
  outcomes[] → outcomeId, name, marketId, eventId, handicap, isTradingActive,
               originalMarketId
  prices[]   → outcomeId, priceDecimal

Market filtering:
  - 1X2:  market.name == "[Win/Draw/Win]"
  - O/U:  market.name == "[Total Goals]" (squashed parent; sub-markets in
          squashedMarketIds, filter by originalMarketId containing "total=2.5")

Outcome labels:
  - 1X2:  outcome.name matches event.homeTeam → "Home"
          outcome.name == "Draw"             → "Draw"
          outcome.name matches event.awayTeam → "Away"
  - O/U:  outcome.name.strip() "Over"/"Under" where handicap == 2.5

No auth required; Origin + Referer headers sufficient.
"""
import logging
from datetime import datetime
from typing import List, Dict, Optional

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

logger = logging.getLogger(__name__)

BASE_URL = 'https://feeds-roa2.betwayafrica.com/br/_apis/sport/v1/BetBook/Upcoming/'

SPORT_CONFIG = {
    'football': {
        'sportId': 'soccer',
        'marketTypes': ['[Win/Draw/Win]', '[Total Goals]', '[Both Teams To Score]',
                        '[Double Chance]', '[Draw No Bet]', '[Asian Handicap Goals]',
                        '[Correct Score]'],
    },
}


class BetwayScraper(BaseScraper):

    def __init__(self):
        super().__init__('Betway')
        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, */*',
            'Origin':  'https://www.betway.com.ng',
            'Referer': 'https://www.betway.com.ng/',
        }

    def get_all_events(self) -> List[Event]:
        """Only fetch sports Betway Nigeria supports."""
        all_events: List[Event] = []
        for sport in SPORT_CONFIG:
            try:
                events = self.get_events(sport)
                all_events.extend(events)
                logger.info(f'[{self.name}] {sport}: {len(events)} events')
            except Exception as e:
                logger.error(f'[{self.name}] {sport} fetch failed: {e}')
        return all_events

    def get_all_live_events(self) -> List[Event]:
        """Only fetch sports Betway Nigeria supports (live)."""
        all_events: List[Event] = []
        for sport in SPORT_CONFIG:
            try:
                events = self.get_live_events(sport)
                all_events.extend(events)
            except Exception as e:
                logger.error(f'[{self.name}] live {sport} fetch failed: {e}')
        return all_events

    def get_live_events(self, sport: str) -> List[Event]:
        # Betway live feed is delivered via SignalR (WebSocket pub-hub-br),
        # not a REST polling endpoint.  No HTTP equivalent discovered.
        return []

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

        events: List[Event] = []
        try:
            raw = self._fetch(cfg['sportId'], cfg['marketTypes'])
            events = self._parse_all(raw, sport)
        except Exception as ex:
            logger.error(f'[Betway] {sport} fetch error: {ex}')

        return events

    def _fetch(self, sport_id: str, market_types: List[str]) -> dict:
        import time as _time
        import config as _cfg

        cutoff_ts = int(_time.time() + _cfg.HOURS_AHEAD * 3600)
        mkt_qs = '&'.join(
            f'marketTypes={mt.replace("/", "%2F")}' for mt in market_types
        )

        merged: dict = {'events': [], 'markets': [], 'outcomes': [], 'prices': []}
        skip = 0
        take = 100
        max_pages = 40  # safety cap

        for _ in range(max_pages):
            base_qs = (
                f'countryCode=NG&sportId={sport_id}&Skip={skip}&Take={take}'
                f'&cultureCode=en-US&isEsport=false&boostedOnly=false'
                f'&ToStartEpoch={cutoff_ts}'
            )
            url = f'{BASE_URL}?{base_qs}&{mkt_qs}'
            try:
                r = self.session.get(url, timeout=20)
                r.raise_for_status()
                page = r.json()
            except Exception as ex:
                logger.warning(f'[Betway] page skip={skip} error: {ex}; using {len(merged["events"])} events collected so far')
                break
            for key in ('events', 'markets', 'outcomes', 'prices'):
                merged[key].extend(page.get(key, []))
            if page.get('isFinalPage'):
                break
            skip += take

        return merged

    def _parse_all(self, raw: dict, sport: str) -> List[Event]:
        # Build lookup dicts
        markets_by_id:  Dict[str, dict] = {m['marketId']:   m for m in raw.get('markets',  [])}
        outcomes_by_id: Dict[str, dict] = {o['outcomeId']:  o for o in raw.get('outcomes', [])}
        prices_by_id:   Dict[str, float] = {
            p['outcomeId']: p['priceDecimal']
            for p in raw.get('prices', [])
            if p.get('priceDecimal')
        }

        # Group outcomes + prices by eventId
        outcomes_by_event: Dict[int, List[dict]] = {}
        for o in raw.get('outcomes', []):
            ev_id = o.get('eventId')
            if ev_id is not None:
                outcomes_by_event.setdefault(ev_id, []).append(o)

        # Group markets by eventId
        markets_by_event: Dict[int, List[dict]] = {}
        for m in raw.get('markets', []):
            ev_id = m.get('eventId')
            if ev_id is not None:
                markets_by_event.setdefault(ev_id, []).append(m)

        result: List[Event] = []

        for ev in raw.get('events', []):
            if ev.get('isLive') or not ev.get('isActive'):
                continue
            # Skip virtual/eSoccer events
            if 'esoccer' in (ev.get('regionId') or '').lower():
                continue

            ev_id    = ev['eventId']
            home     = ev.get('homeTeam', '').strip()
            away     = ev.get('awayTeam', '').strip()
            league   = ev.get('league', '').strip()
            if not home or not away:
                continue

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

            # Build direct event URL using the slugs the API provides
            sport_id  = ev.get('sportId', 'soccer')
            region_id = ev.get('regionId', '')
            league_id = ev.get('leagueId', '')
            ssp       = ev.get('sportSpecificProperties') or {}
            home_slug = ssp.get('HomeTeamId', '')
            away_slug = ssp.get('AwayTeamId', '')
            if region_id and league_id and home_slug and away_slug:
                event_url = (
                    f'https://www.betway.com.ng/event/{sport_id}/{region_id}'
                    f'/{league_id}/{home_slug}-{away_slug}'
                    f'?eventId={ev_id}&fixtureType=highlights'
                )
            else:
                event_url = None

            ev_markets  = markets_by_event.get(ev_id, [])
            ev_outcomes = outcomes_by_event.get(ev_id, [])

            for market in ev_markets:
                mkt_name = market.get('name', '')

                if mkt_name == '[Win/Draw/Win]' and not market.get('isSquashedParent'):
                    parsed = self._parse_1x2(market, ev_outcomes, prices_by_id, home, away, event_url)
                    if parsed:
                        result.append(Event(
                            event_id  = f'bw_{ev_id}_{market["marketId"]}',
                            bookmaker = 'Betway',
                            sport     = sport,
                            home_team = home,
                            away_team = away,
                            market    = '1X2',
                            outcomes  = parsed,
                            starts_at = starts_at,
                            league    = league,
                        ))

                elif mkt_name == '[Total Goals]' and market.get('isSquashedParent'):
                    for _lv in ('0.5', '1.5', '2.5', '3.5', '4.5'):
                        parsed = self._parse_total_goals(market, ev_outcomes, prices_by_id, event_url, _lv)
                        if parsed:
                            result.append(Event(
                                event_id  = f'bw_{ev_id}_ou{_lv}',
                                bookmaker = 'Betway',
                                sport     = sport,
                                home_team = home,
                                away_team = away,
                                market    = f'Over/Under {_lv}',
                                outcomes  = parsed,
                                starts_at = starts_at,
                                league    = league,
                            ))

                elif mkt_name == '[Double Chance]' and not market.get('isSquashedParent'):
                    parsed = self._parse_dc(market, ev_outcomes, prices_by_id, event_url)
                    if parsed:
                        result.append(Event(
                            event_id  = f'bw_{ev_id}_dc',
                            bookmaker = 'Betway',
                            sport     = sport,
                            home_team = home,
                            away_team = away,
                            market    = 'Double Chance',
                            outcomes  = parsed,
                            starts_at = starts_at,
                            league    = league,
                        ))

                elif mkt_name == '[Both Teams To Score]' and not market.get('isSquashedParent'):
                    parsed = self._parse_btts(market, ev_outcomes, prices_by_id, event_url)
                    if parsed:
                        result.append(Event(
                            event_id  = f'bw_{ev_id}_btts',
                            bookmaker = 'Betway',
                            sport     = sport,
                            home_team = home,
                            away_team = away,
                            market    = 'BTTS',
                            outcomes  = parsed,
                            starts_at = starts_at,
                            league    = league,
                        ))

                elif mkt_name == '[Draw No Bet]' and not market.get('isSquashedParent'):
                    parsed = self._parse_dnb(market, ev_outcomes, prices_by_id, home, away, event_url)
                    if parsed:
                        result.append(Event(
                            event_id  = f'bw_{ev_id}_dnb',
                            bookmaker = 'Betway',
                            sport     = sport,
                            home_team = home,
                            away_team = away,
                            market    = 'Draw No Bet',
                            outcomes  = parsed,
                            starts_at = starts_at,
                            league    = league,
                        ))

                elif mkt_name == '[Asian Handicap Goals]' and market.get('isSquashedParent'):
                    for _lv in ('-1.5', '-1', '-0.5', '0', '+0.5', '+1', '+1.5'):
                        parsed = self._parse_ah(market, ev_outcomes, prices_by_id, event_url, _lv)
                        if parsed:
                            result.append(Event(
                                event_id  = f'bw_{ev_id}_ah{_lv}',
                                bookmaker = 'Betway',
                                sport     = sport,
                                home_team = home,
                                away_team = away,
                                market    = f'Asian Handicap {_lv}',
                                outcomes  = parsed,
                                starts_at = starts_at,
                                league    = league,
                            ))

                elif mkt_name == '[Correct Score]' and not market.get('isSquashedParent'):
                    parsed = self._parse_correct_score(market, ev_outcomes, prices_by_id, event_url)
                    if parsed:
                        result.append(Event(
                            event_id  = f'bw_{ev_id}_cs',
                            bookmaker = 'Betway',
                            sport     = sport,
                            home_team = home,
                            away_team = away,
                            market    = 'Correct Score',
                            outcomes  = parsed,
                            starts_at = starts_at,
                            league    = league,
                        ))

        return result

    def _parse_1x2(self, market: dict, all_outcomes: List[dict], prices: Dict, home: str, away: str, event_url: Optional[str]) -> Optional[List[Outcome]]:
        mid = market['marketId']
        oc_list = [o for o in all_outcomes if o.get('marketId') == mid and o.get('isTradingActive')]

        result = []
        for o in oc_list:
            price = prices.get(o['outcomeId'])
            if not price or price <= 1.0:
                continue

            name = o.get('name', '').strip()
            if name == home:
                label = 'Home'
            elif name == away:
                label = 'Away'
            elif name.lower() == 'draw':
                label = 'Draw'
            else:
                continue

            result.append(Outcome(name=label, odds=price, bookmaker='Betway', event_url=event_url))

        if len(result) == 3:
            return result
        return None

    def _parse_dc(self, market: dict, all_outcomes: List[dict], prices: Dict, event_url: Optional[str]) -> Optional[List[Outcome]]:
        """Parse Double Chance outcomes.

        Betway DC outcome names contain team names, e.g.:
          "Bayern Or Draw"       → 1X  (draw at end)
          "Bayern Or Man Utd"    → 12  (no draw)
          "Draw Or Man Utd"      → X2  (draw at start)
        """
        mid = market['marketId']
        oc_list = [o for o in all_outcomes if o.get('marketId') == mid and o.get('isTradingActive')]
        result = []
        for o in oc_list:
            price = prices.get(o['outcomeId'])
            if not price or price <= 1.0:
                continue
            name_lower = o.get('name', '').lower().strip()
            if 'draw' not in name_lower:
                label = '12'                      # Home or Away (no draw)
            elif name_lower.endswith('draw') or ' or draw' in name_lower:
                label = '1X'                      # Home or Draw
            else:
                label = 'X2'                      # Draw or Away
            result.append(Outcome(name=label, odds=price, bookmaker='Betway', event_url=event_url))
        if len(result) == 3:
            return result
        return None

    def _parse_btts(self, market: dict, all_outcomes: List[dict], prices: Dict, event_url: Optional[str]) -> Optional[List[Outcome]]:
        mid = market['marketId']
        oc_list = [o for o in all_outcomes if o.get('marketId') == mid and o.get('isTradingActive')]
        result = []
        for o in oc_list:
            price = prices.get(o['outcomeId'])
            if not price or price <= 1.0:
                continue
            name = o.get('name', '').strip()
            if name in ('Yes', 'No'):
                result.append(Outcome(name=name, odds=price, bookmaker='Betway', event_url=event_url))
        if len(result) == 2:
            return result
        return None

    def _parse_dnb(self, market: dict, all_outcomes: List[dict], prices: Dict, home: str, away: str, event_url: Optional[str]) -> Optional[List[Outcome]]:
        """Draw No Bet — 2 outcomes: home team wins or away team wins."""
        mid = market['marketId']
        oc_list = [o for o in all_outcomes if o.get('marketId') == mid and o.get('isTradingActive')]
        result = []
        for o in oc_list:
            price = prices.get(o['outcomeId'])
            if not price or price <= 1.0:
                continue
            name = o.get('name', '').strip()
            if name == home:
                label = 'Home'
            elif name == away:
                label = 'Away'
            else:
                continue
            result.append(Outcome(name=label, odds=price, bookmaker='Betway', event_url=event_url))
        if len(result) == 2:
            return result
        return None

    def _parse_ah(self, market: dict, all_outcomes: List[dict], prices: Dict, event_url: Optional[str], line: str) -> Optional[List[Outcome]]:
        """Asian Handicap — filter by hcp={line}~ in originalMarketId."""
        # Normalise line for URL matching: '+0.5' → '0.5', '-0.5' → '-0.5', '0' → '0'
        url_line = line.lstrip('+')
        seen = set()
        oc_line = []
        for o in all_outcomes:
            orig = o.get('originalMarketId', '')
            if (f'hcp={url_line}~' in orig or f'handicap={url_line}~' in orig) and o.get('isTradingActive') and o['outcomeId'] not in seen:
                seen.add(o['outcomeId'])
                oc_line.append(o)
        result = []
        for o in oc_line:
            price = prices.get(o['outcomeId'])
            if not price or price <= 1.0:
                continue
            name = o.get('name', '').strip()
            # Betway AH outcome names include the handicap, e.g. "Team Name (-0.5)"
            # Home = first listed participant = home team
            if result:
                label = 'Away'
            else:
                label = 'Home'
            result.append(Outcome(name=label, odds=price, bookmaker='Betway', event_url=event_url))
        if len(result) == 2:
            return result
        return None

    def _parse_correct_score(self, market: dict, all_outcomes: List[dict], prices: Dict, event_url: Optional[str]) -> Optional[List[Outcome]]:
        mid = market['marketId']
        oc_list = [o for o in all_outcomes if o.get('marketId') == mid and o.get('isTradingActive')]
        result = []
        for o in oc_list:
            price = prices.get(o['outcomeId'])
            if not price or price <= 1.0:
                continue
            name = normalize_cs_score(o.get('name', ''))
            if not name:
                continue
            result.append(Outcome(name=name, odds=price, bookmaker='Betway', event_url=event_url))
        if len(result) >= 2:
            return result
        return None

    def _parse_total_goals(self, market: dict, all_outcomes: List[dict], prices: Dict, event_url: Optional[str], line: str = '2.5') -> Optional[List[Outcome]]:
        # The squashed parent contains outcomes for all lines; filter to the requested line via originalMarketId
        # Deduplicate by outcomeId (same outcome appears under parent + sub-market)
        seen = set()
        oc_line = []
        for o in all_outcomes:
            if (o.get('originalMarketId', '').endswith(f'total={line}~')
                    and o.get('isTradingActive')
                    and o['outcomeId'] not in seen):
                seen.add(o['outcomeId'])
                oc_line.append(o)

        result = []
        for o in oc_line:
            price = prices.get(o['outcomeId'])
            if not price or price <= 1.0:
                continue

            name = o.get('name', '').strip()
            if name == 'Over':
                label = 'Over'
            elif name == 'Under':
                label = 'Under'
            else:
                continue

            result.append(Outcome(name=label, odds=price, bookmaker='Betway', event_url=event_url))

        if len(result) == 2:
            return result
        return None
