from dataclasses import dataclass

import httpx

from bot.core.logger import logger

STRATZ_GRAPHQL_URL = "https://api.stratz.com/graphql"

_POSITION_LABELS = {
    "POSITION_1": "Carry",
    "POSITION_2": "Midlaner",
    "POSITION_3": "Offlaner",
    "POSITION_4": "Soft Support",
    "POSITION_5": "Hard Support",
}

_LANE_LABELS = {
    "SAFE_LANE": "Safe",
    "MID_LANE": "Mid",
    "OFF_LANE": "Off",
    "ROAMING": "Roaming",
    "JUNGLE": "Jungle",
}

_OUTCOME_LABELS = {
    "STOMPED": "stomped",
    "WON": "won",
    "DRAW": "draw",
    "LOST": "lost",
    "STOMPED_LOSS": "stomped loss",
}

# GraphQL may return gameMode as a string enum; map back to the int IDs
# that poll_matches.py compares against TRACKED_GAME_MODES = {1, 22, 4}.
_GAME_MODE_IDS = {
    "ALL_PICK": 1,
    "SINGLE_DRAFT": 4,
    "ALL_RANDOM": 5,
    "INTRO": 6,
    "DIRETIDE": 7,
    "REVERSE_ALL_PICK": 8,
    "THE_GREEVILING": 9,
    "TUTORIAL": 10,
    "MID_ONLY": 11,
    "LEAST_PLAYED": 12,
    "NEW_PLAYER_POOL": 13,
    "COMPENDIUM_MATCHMAKING": 14,
    "CO_OP_VS_BOTS": 15,
    "CAPTAINS_DRAFT": 16,
    "BALANCED_DRAFT": 17,
    "ABILITY_DRAFT": 18,
    "EVENT": 19,
    "ALL_RANDOM_DEATH_MATCH": 20,
    "SOLO_MID": 21,
    "RANKED_ALL_PICK": 22,
    "TURBO": 23,
    "MUTATION": 24,
}


@dataclass
class RecentMatch:
    match_id: int
    start_time: int


@dataclass
class PlayerMatchStats:
    hero_id: int
    is_radiant: bool
    kills: int
    deaths: int
    assists: int
    position: str
    lane: str
    lane_outcome: str


@dataclass
class MatchDetails:
    match_id: int
    start_time: int
    radiant_win: bool
    duration: int
    game_mode: int
    player_stats: dict[int, PlayerMatchStats]  # steam32 -> stats

    @property
    def player_teams(self) -> dict[int, bool]:
        return {s32: ps.is_radiant for s32, ps in self.player_stats.items()}


class StratzClient:
    def __init__(self, api_key: str) -> None:
        self._client = httpx.AsyncClient(
            timeout=15.0,
            headers={
                "Accept": "application/json",
                "Content-Type": "application/json",
                "Authorization": f"Bearer {api_key}",
                "User-Agent": "Mozilla/5.0 (compatible; dota2-tg-bot/1.0)",
            },
        )
        self._heroes: dict[int, str] = {}

    async def _gql(self, query: str, variables: dict | None = None) -> dict:
        payload: dict = {"query": query}
        if variables:
            payload["variables"] = variables
        resp = await self._client.post(STRATZ_GRAPHQL_URL, json=payload)
        resp.raise_for_status()
        result = resp.json()
        if "errors" in result:
            logger.warning("GraphQL errors: %s", result["errors"])
        return result.get("data") or {}

    async def get_heroes(self) -> dict[int, str]:
        if self._heroes:
            return self._heroes
        try:
            data = await self._gql("""
                query {
                  constants {
                    heroes {
                      id
                      displayName
                      shortName
                    }
                  }
                }
            """)
            heroes_list = (data.get("constants") or {}).get("heroes") or []
            self._heroes = {
                h["id"]: h.get("displayName") or h.get("shortName", str(h["id"]))
                for h in heroes_list
                if h.get("id")
            }
            logger.info("Loaded %d hero names from STRATZ", len(self._heroes))
        except httpx.HTTPError as e:
            logger.warning("STRATZ hero list request failed: %s", e, exc_info=True)
        except Exception:
            logger.warning("Failed to fetch hero list from STRATZ", exc_info=True)
        return self._heroes

    async def get_recent_matches(self, steam32: int) -> list[RecentMatch]:
        try:
            data = await self._gql(
                """
                query PlayerMatches($steamAccountId: Long!, $take: Int) {
                  player(steamAccountId: $steamAccountId) {
                    matches(request: { take: $take }) {
                      id
                      startDateTime
                    }
                  }
                }
                """,
                {"steamAccountId": steam32, "take": 20},
            )
            matches = ((data.get("player") or {}).get("matches")) or []
            return [
                RecentMatch(match_id=m["id"], start_time=m.get("startDateTime", 0))
                for m in matches
            ]
        except httpx.HTTPError as e:
            logger.warning("STRATZ matches request failed for steam32=%s: %s", steam32, e, exc_info=True)
            return []
        except Exception:
            logger.warning("Unexpected error fetching matches for steam32=%s", steam32, exc_info=True)
            return []

    async def get_match_details(self, match_id: int) -> MatchDetails | None:
        try:
            data = await self._gql(
                """
                query MatchDetails($matchId: Long!) {
                  match(id: $matchId) {
                    id
                    startDateTime
                    durationSeconds
                    didRadiantWin
                    gameMode
                    players {
                      steamAccountId
                      isRadiant
                      kills
                      deaths
                      assists
                      position
                      lane
                      laneOutcome
                      heroId
                    }
                  }
                }
                """,
                {"matchId": match_id},
            )
            match_data = data.get("match")
            if not match_data:
                return None
            player_stats: dict[int, PlayerMatchStats] = {}
            for p in match_data.get("players", []):
                steam32 = p.get("steamAccountId")
                if not steam32:
                    continue
                raw_pos = p.get("position") or ""
                raw_lane = p.get("lane") or ""
                raw_outcome = p.get("laneOutcome") or ""
                player_stats[steam32] = PlayerMatchStats(
                    hero_id=p.get("heroId", 0),
                    is_radiant=p.get("isRadiant", False),
                    kills=p.get("kills", 0),
                    deaths=p.get("deaths", 0),
                    assists=p.get("assists", 0),
                    position=_POSITION_LABELS.get(raw_pos, raw_pos),
                    lane=_LANE_LABELS.get(raw_lane, raw_lane),
                    lane_outcome=_OUTCOME_LABELS.get(raw_outcome, raw_outcome),
                )
            raw_mode = match_data.get("gameMode", 0)
            game_mode = _GAME_MODE_IDS.get(raw_mode, raw_mode) if isinstance(raw_mode, str) else raw_mode
            return MatchDetails(
                match_id=match_id,
                start_time=match_data.get("startDateTime", 0),
                radiant_win=match_data.get("didRadiantWin", False),
                duration=match_data.get("durationSeconds", 0),
                game_mode=game_mode,
                player_stats=player_stats,
            )
        except httpx.HTTPError as e:
            logger.warning("STRATZ match details request failed for match_id=%s: %s", match_id, e, exc_info=True)
            return None
        except Exception:
            logger.warning("Unexpected error fetching match_id=%s", match_id, exc_info=True)
            return None

    async def close(self) -> None:
        await self._client.aclose()
