"""
AltSportsData Python SDK

Alternative sports data for prediction markets, DFS platforms, and sportsbooks.

    pip install altsportsdata

    from altsportsdata import AltSportsData

    wsl = AltSportsData(api_key="sk_live_xxx", league="wsl")

    # Events
    wsl.list_events(status="upcoming")

    # Probabilities & odds
    wsl.get_market_probabilities("evt_id")   # prediction markets (Kalshi, Polymarket)
    wsl.get_player_props("evt_id")           # DFS platforms (PrizePicks, Underdog)
    wsl.get_moneylines("evt_id")             # sportsbooks (DraftKings, Bet365)
    wsl.get_matchups("evt_id")               # head-to-head markets

    # Combine picks
    wsl.calculate_parlay("evt_id", picks=["outcomeId_1", "outcomeId_2"])

    # Season futures
    wsl.get_futures(tour="tour_id", type="winner")

Docs: https://api.altsportsdata.com/public/docs
API:  https://api.altsportsdata.com/public/docs/swagger
"""

from typing import List, Optional, Dict, Any, Union

import requests

from .models import (
    SportType, OddType, EventStatus, FutureType, ExactasType,
    OverUnderSubMarketType, SortOrder,
    RoundScore, EventParticipant,
    EventResponse, EventListing,
    JaiAlaiEvent, JaiAlaiOdds,
    FutureListing, FutureOdds,
    SportsListing,
)
from .exceptions import raise_for_status, NetworkError, ConfigurationError


_BASE_URL = "https://api.altsportsdata.com"
_API_PREFIX = "/api/v1/public"

# Map sportsbook-friendly status names → API values
_STATUS_ALIASES = {
    "live": "LIVE",
    "upcoming": "UPCOMING",
    "completed": "COMPLETED",
    "next": "NEXT",
    "in_window": "IN_WINDOW",
    "cancelled": "CANCELLED",
    "postponed": "POSTPONED",
}

# Map sportsbook-friendly market names → API oddType values
_MARKET_ALIASES = {
    # Sportsbook-native names
    "moneyline": "eventWinner",
    "moneylines": "eventWinner",
    "winner": "eventWinner",
    "matchup": "headToHead",
    "matchups": "headToHead",
    "h2h": "headToHead",
    "head_to_head": "headToHead",
    "props": "propBets",
    "player_props": "propBets",
    "totals": "overUnder",
    "over_under": "overUnder",
    "ou": "overUnder",
    "fastest_lap": "fastestLap",
    "heat_winner": "heatWinner",
    "dream_team": "dreamTeam",
    "exacta": "eventExacta",
    "exactas": "eventExacta",
    "heat_exacta": "heatExacta",
    "podium": "podiums",
    "top3": "raceTop3",
    "top5": "raceTop5",
    "top10": "raceTop10",
    "second": "secondPlace",
    "second_place": "secondPlace",
    "hole_shot": "holeShot",
    "multi_ou": "multiOverUnder",
    # Pass-through for API-native names
    "eventWinner": "eventWinner",
    "headToHead": "headToHead",
    "propBets": "propBets",
    "overUnder": "overUnder",
    "multiOverUnder": "multiOverUnder",
    "fastestLap": "fastestLap",
    "heatWinner": "heatWinner",
    "dreamTeam": "dreamTeam",
    "eventExacta": "eventExacta",
    "heatExacta": "heatExacta",
    "shows": "shows",
    "podiums": "podiums",
    "raceTop3": "raceTop3",
    "raceTop5": "raceTop5",
    "raceTop10": "raceTop10",
    "secondPlace": "secondPlace",
    "holeShot": "holeShot",
}


def _enum_val(val: Any, enum_cls: Any) -> str:
    if isinstance(val, enum_cls):
        return val.value
    return str(val)


def _resolve_status(s: Union[str, EventStatus]) -> str:
    if isinstance(s, EventStatus):
        return s.value
    return _STATUS_ALIASES.get(s.lower(), s.upper())


def _resolve_market(m: Union[str, OddType]) -> str:
    if isinstance(m, OddType):
        return m.value
    return _MARKET_ALIASES.get(m, _MARKET_ALIASES.get(m.lower(), m))


class AltSportsData:
    """
    AltSportsData client — the data feed for alternative sports betting.

    Designed for sportsbooks, DFS platforms, and prediction markets.
    Flat API, no nesting. Sportsbook-native naming with full alias support.

    Quick start::

        from altsportsdata import AltSportsData

        # Lock to a league — all queries auto-filter
        wsl = AltSportsData(api_key="sk_live_xxx", league="wsl")

        # Events
        wsl.list_events(status="upcoming")
        wsl.list_events(status="live")
        event = wsl.get_event("evt_id")

        # Odds — use names your team already knows
        wsl.get_moneylines("evt_id")      # event winner
        wsl.get_matchups("evt_id")        # head-to-head
        wsl.get_player_props("evt_id")    # prop bets
        wsl.get_totals("evt_id")          # over/under
        wsl.get_odds("evt_id", "shows")   # any market by name

        # Parlays
        wsl.calculate_parlay("evt_id", picks=["odd_1", "odd_2"])

        # Futures
        wsl.get_futures(tour="tour_id", type="winner")

    Supported leagues: wsl, sls, f1, motogp, pbr, bkfc, nrx, fdrift,
    powerslap, masl, nll, nhra, dgpt, jaialai, worldoutlaws, and 16 more.
    """

    def __init__(
        self,
        api_key: Optional[str] = None,
        league: Optional[Union[SportType, str]] = None,
        *,
        base_url: str = _BASE_URL,
        timeout: int = 30,
    ):
        """
        Args:
            api_key: Your API key
            league: Lock to a league (e.g. "wsl", "f1", "pbr"). Auto-filters all queries.
            base_url: API base URL (default: production)
            timeout: Request timeout in seconds
        """
        if not api_key:
            raise ConfigurationError(
                "API key required. Get one at https://altsportsdata.com"
            )
        self.api_key = api_key
        self.league = _enum_val(league, SportType) if league else None
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self._session = requests.Session()
        self._session.headers.update({
            "X-API-KEY": api_key,
            "Content-Type": "application/json",
            "Accept": "application/json",
            "User-Agent": "altsportsdata-python/2.2.0",
        })

    # ══════════════════════════════════════════════════════════════════════
    #  LEAGUES / SPORTS
    # ══════════════════════════════════════════════════════════════════════

    def list_leagues(self) -> List[SportsListing]:
        """List all available leagues with their markets and tours."""
        data = self._get(f"{_API_PREFIX}/sports")
        return [SportsListing.model_validate(i) for i in data] if isinstance(data, list) else []

    # alias
    list_sports = list_leagues

    # ══════════════════════════════════════════════════════════════════════
    #  EVENTS
    # ══════════════════════════════════════════════════════════════════════

    def list_events(
        self,
        *,
        league: Optional[Union[SportType, str]] = None,
        status: Optional[Union[str, List[str], EventStatus, List[EventStatus]]] = None,
        start_date: Optional[str] = None,
        end_date: Optional[str] = None,
        sort: Optional[str] = None,
    ) -> List[EventListing]:
        """
        List events.

        Args:
            league: Override client league
            status: "upcoming", "live", "completed", or list of statuses.
                    Also accepts API values: "UPCOMING", "LIVE", "COMPLETED", etc.
            start_date: ISO datetime filter
            end_date: ISO datetime filter
            sort: "asc" or "desc"
        """
        params = self._build_event_params(
            league=league, status=status,
            start_date=start_date, end_date=end_date, sort=sort,
        )
        data = self._get(f"{_API_PREFIX}/events", params=params)
        return [EventListing.model_validate(i) for i in data] if isinstance(data, list) else []

    def get_event(self, event_id: str) -> EventResponse:
        """Get full event details by ID."""
        return EventResponse.model_validate(self._get(f"{_API_PREFIX}/events/{event_id}"))

    def get_participants(self, event_id: str) -> List[EventParticipant]:
        """Get athletes/participants for an event."""
        data = self._get(f"{_API_PREFIX}/events/{event_id}/participants")
        return [EventParticipant.model_validate(i) for i in data] if isinstance(data, list) else []

    def get_heat_scores(self, event_id: str, heat_id: str) -> List[RoundScore]:
        """Get scores/lap times for a specific heat."""
        data = self._get(f"{_API_PREFIX}/events/{event_id}/heats/{heat_id}")
        return [RoundScore.model_validate(i) for i in data] if isinstance(data, list) else []

    # ══════════════════════════════════════════════════════════════════════
    #  ODDS — generic + sportsbook-native aliases
    # ══════════════════════════════════════════════════════════════════════

    def get_odds(
        self,
        event_id: str,
        market: Union[OddType, str],
        *,
        exactas_type: Optional[int] = None,
        sub_market: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        Get odds for any market type.

        Args:
            event_id: Event ID
            market: Market name. Accepts sportsbook-native names:
                    "moneyline", "matchups", "h2h", "props", "totals",
                    "exacta", "top3", "top5", etc.
                    Also accepts API names: "eventWinner", "headToHead", etc.
            exactas_type: For exacta markets — 2, 3, or 4
            sub_market: For over/under — "finishingPosition", "points", "gapToLeaders"
        """
        market_str = _resolve_market(market)
        params: Dict[str, Any] = {}
        if exactas_type is not None:
            params["exactasType"] = exactas_type
        if sub_market is not None:
            params["overUnderSubMarketType"] = sub_market
        return self._get(f"{_API_PREFIX}/events/{event_id}/{market_str}", params=params)

    # ── Sportsbook-native shortcuts ────────────────────────────────────

    # ── Prediction Markets (Kalshi, Polymarket) ─────────────────────────

    def get_market_probabilities(self, event_id: str) -> Dict[str, Any]:
        """
        Get win probabilities for all participants — built for prediction markets.

        Returns the same data as get_moneylines() but named for how
        Kalshi/Polymarket engineers think about it: probabilities, not odds.

        Each entry includes: athlete, probability (0-100), odds (decimal), status, outcomeId
        """
        return self.get_odds(event_id, "eventWinner")

    def get_podium_probabilities(self, event_id: str) -> Dict[str, Any]:
        """Top-3 finish probabilities — prediction market friendly."""
        return self.get_odds(event_id, "podiums")

    def get_top_finish_probabilities(self, event_id: str, top_n: int = 5) -> Dict[str, Any]:
        """
        Top-N finish probabilities.

        Args:
            event_id: Event ID
            top_n: 3, 5, or 10
        """
        market_map = {3: "raceTop3", 5: "raceTop5", 10: "raceTop10"}
        market = market_map.get(top_n, f"raceTop{top_n}")
        return self.get_odds(event_id, market)

    # ── DFS Platforms (PrizePicks, Underdog) ──────────────────────────

    def get_player_props(self, event_id: str) -> Dict[str, Any]:
        """Player proposition bets — core DFS market data."""
        return self.get_odds(event_id, "props")

    def get_player_matchups(self, event_id: str) -> Dict[str, Any]:
        """Head-to-head player matchups — DFS pick'em format."""
        return self.get_odds(event_id, "matchups")

    def get_player_totals(self, event_id: str, stat: Optional[str] = None) -> Dict[str, Any]:
        """
        Player over/under totals.

        Args:
            event_id: Event ID
            stat: Stat type — 'points', 'finishingPosition', 'gapToLeaders'
        """
        return self.get_odds(event_id, "totals", sub_market=stat)

    # ── Sportsbooks (DraftKings, Bet365, Stake) ──────────────────────

    def get_moneylines(self, event_id: str) -> Dict[str, Any]:
        """Event winner / moneyline odds."""
        return self.get_odds(event_id, "moneyline")

    def get_matchups(self, event_id: str) -> Dict[str, Any]:
        """Head-to-head matchup odds."""
        return self.get_odds(event_id, "matchups")

    def get_totals(self, event_id: str, sub_market: Optional[str] = None) -> Dict[str, Any]:
        """Over/under totals. Alias for get_player_totals()."""
        return self.get_odds(event_id, "totals", sub_market=sub_market)

    def get_heat_winners(self, event_id: str) -> Dict[str, Any]:
        """Heat winner odds (WSL, SLS, racing sports)."""
        return self.get_odds(event_id, "heatWinner")

    def get_fastest_lap(self, event_id: str) -> Dict[str, Any]:
        """Fastest lap odds (racing sports)."""
        return self.get_odds(event_id, "fastestLap")

    def get_exactas(self, event_id: str, n: int = 2) -> Dict[str, Any]:
        """Exacta odds. n=2 (default), 3, or 4."""
        return self.get_odds(event_id, "exacta", exactas_type=n)

    def get_podiums(self, event_id: str) -> Dict[str, Any]:
        """Podium (top-3) finish odds."""
        return self.get_odds(event_id, "podiums")

    def get_shows(self, event_id: str) -> Dict[str, Any]:
        """Shows odds."""
        return self.get_odds(event_id, "shows")

    def get_dream_team(self, event_id: str) -> Dict[str, Any]:
        """Dream team odds."""
        return self.get_odds(event_id, "dreamTeam")

    # ══════════════════════════════════════════════════════════════════════
    #  PARLAYS (SGP)
    # ══════════════════════════════════════════════════════════════════════

    def calculate_parlay(self, event_id: str, picks: List[str]) -> Dict[str, Any]:
        """
        Same Game Parlay — calculate combined odds for multiple picks.

        Args:
            event_id: Event ID
            picks: List of odd IDs to combine

        Returns:
            {"combinedOdds": float, "combinedProbability": float, "selections": [...]}
        """
        return self._post(f"{_API_PREFIX}/events/{event_id}/sgp", body={"oddIds": picks})

    # alias
    calculate_sgp = calculate_parlay

    # ══════════════════════════════════════════════════════════════════════
    #  FUTURES
    # ══════════════════════════════════════════════════════════════════════

    def list_futures(self) -> List[FutureListing]:
        """List all available futures across leagues."""
        data = self._get(f"{_API_PREFIX}/futures")
        return [FutureListing.model_validate(i) for i in data] if isinstance(data, list) else []

    def get_futures(
        self,
        tour: str,
        type: Union[FutureType, str] = "winner",
        *,
        league: Optional[Union[SportType, str]] = None,
    ) -> List[FutureOdds]:
        """
        Get futures odds.

        Args:
            tour: Tour ID
            type: "winner", "top2", "top3", "top5", "top10", "makeCut", "makePlayOdds"
            league: Override client league
        """
        league_val = _enum_val(league, SportType) if league else self.league
        if not league_val:
            raise ConfigurationError(
                "League required for futures. Set league= on client or pass league= here."
            )
        ft = _enum_val(type, FutureType)
        data = self._get(f"{_API_PREFIX}/futures/{league_val}/tour/{tour}/odds/{ft}")
        return [FutureOdds.model_validate(i) for i in data] if isinstance(data, list) else []

    # ══════════════════════════════════════════════════════════════════════
    #  JAI ALAI (team-based match format)
    # ══════════════════════════════════════════════════════════════════════

    def get_jaialai_matches(self, event_id: str) -> List[JaiAlaiEvent]:
        """Get Jai Alai match details with set scores."""
        data = self._get(f"{_API_PREFIX}/events/jaialai/{event_id}")
        return [JaiAlaiEvent.model_validate(i) for i in data] if isinstance(data, list) else []

    def get_jaialai_odds(self, event_id: str) -> JaiAlaiOdds:
        """Get Jai Alai odds (moneyline, spread, totals per set)."""
        return JaiAlaiOdds.model_validate(
            self._get(f"{_API_PREFIX}/events/jaialai/{event_id}/odds")
        )

    # ══════════════════════════════════════════════════════════════════════
    #  INTERNAL
    # ══════════════════════════════════════════════════════════════════════

    def _build_event_params(
        self,
        league=None, status=None,
        start_date=None, end_date=None, sort=None,
    ) -> Dict[str, Any]:
        params: Dict[str, Any] = {}
        lv = _enum_val(league, SportType) if league else self.league
        if lv:
            params["sportType"] = lv
        if status is not None:
            if isinstance(status, (str, EventStatus)):
                status = [status]
            params["eventStatus"] = [_resolve_status(s) for s in status]
        if start_date:
            params["startDate"] = start_date
        if end_date:
            params["endDate"] = end_date
        if sort:
            params["sortOrder"] = sort.upper()
        return params

    def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
        return self._request("GET", path, params=params)

    def _post(self, path: str, body: Optional[Dict[str, Any]] = None) -> Any:
        return self._request("POST", path, body=body)

    def _request(self, method, path, params=None, body=None) -> Any:
        url = f"{self.base_url}{path}"
        # API uses bracket notation for arrays: eventStatus[]=LIVE&eventStatus[]=UPCOMING
        req_params = params
        if params:
            flat = []
            for k, v in params.items():
                if isinstance(v, list):
                    for item in v:
                        flat.append((f"{k}[]", item))
                else:
                    flat.append((k, v))
            req_params = flat
        try:
            r = self._session.request(
                method=method, url=url, params=req_params, json=body, timeout=self.timeout,
            )
            if not r.ok:
                try:
                    msg = r.json().get("message", f"HTTP {r.status_code}")
                except Exception:
                    msg = f"HTTP {r.status_code}: {r.text}"
                raise_for_status(r.status_code, msg)
            body = r.json()
            # API wraps responses: {"status": 200, "data": [...]} (prod)
            # or {"statusCode": 200, "data": [...]} (dev). Unwrap automatically.
            if isinstance(body, dict) and "data" in body and ("status" in body or "statusCode" in body):
                return body["data"]
            return body
        except requests.exceptions.Timeout:
            raise NetworkError(f"Request timed out after {self.timeout}s")
        except requests.exceptions.ConnectionError:
            raise NetworkError("Failed to connect to AltSportsData API")
        except requests.exceptions.RequestException as e:
            raise NetworkError(f"Network error: {e}")

    def __repr__(self) -> str:
        parts = [f"base_url='{self.base_url}'"]
        if self.league:
            parts.append(f"league='{self.league}'")
        return f"AltSportsData({', '.join(parts)})"
