import asyncio
from collections import defaultdict
from datetime import datetime, timezone, timedelta

TZ_UTC3 = timezone(timedelta(hours=3))

from telegram.ext import ContextTypes

from bot.core.logger import logger
from bot.services.storage import Player
from bot.services.stratz import MatchDetails, PlayerMatchStats

# All Pick, Ranked All Pick, Single Draft
TRACKED_GAME_MODES = {1, 22, 4}

_GAME_MODE_NAMES = {
    1: "All Pick",
    22: "Ranked All Pick",
    4: "Single Draft",
}


async def poll_dota_matches(context: ContextTypes.DEFAULT_TYPE) -> None:
    store = context.bot_data["store"]
    client = context.bot_data["stratz"]

    chats = store.all_chats()
    if not chats:
        return

    for chat_id in chats:
        players = store.list_players(chat_id)
        if len(players) < 2:
            continue

        # Fetch recent matches for each player
        fresh: dict[int, list[int]] = {}  # steam32 -> new match_ids
        for i, player in enumerate(players):
            if i > 0:
                await asyncio.sleep(1.1)
            matches = await client.get_recent_matches(player.steam32)
            fresh[player.steam32] = [
                m.match_id for m in matches
                if not store.is_seen(chat_id, m.match_id)
            ]

        # Find matches shared by 2+ players
        match_participants: dict[int, list[Player]] = defaultdict(list)
        player_by_steam32 = {p.steam32: p for p in players}
        for steam32, match_ids in fresh.items():
            for mid in match_ids:
                match_participants[mid].append(player_by_steam32[steam32])

        shared = {
            mid: ps
            for mid, ps in match_participants.items()
            if len(ps) >= 2
        }

        heroes = await client.get_heroes()

        # Notify and mark seen
        for match_id in sorted(shared):
            participants = shared[match_id]
            details = await client.get_match_details(match_id)
            await asyncio.sleep(1.1)

            if details:
                if details.game_mode not in TRACKED_GAME_MODES:
                    logger.info(
                        "Skipping match_id=%s — game_mode=%s not tracked",
                        match_id,
                        details.game_mode,
                    )
                    continue

                teams = {
                    details.player_teams.get(p.steam32)
                    for p in participants
                    if p.steam32 in details.player_teams
                }
                if len(teams) > 1:
                    logger.info(
                        "Skipping match_id=%s — tracked players are on different teams",
                        match_id,
                    )
                    continue

            await _send_notification(context.bot, chat_id, match_id, participants, details, heroes)
            logger.info(
                "Notified chat_id=%s match_id=%s players=%s",
                chat_id,
                match_id,
                [p.name for p in participants],
            )

        all_new_ids = {mid for ids in fresh.values() for mid in ids}
        if all_new_ids:
            store.mark_seen(chat_id, all_new_ids)
            loop = asyncio.get_event_loop()
            await loop.run_in_executor(None, store.save)


async def _send_notification(
    bot,
    chat_id: int,
    match_id: int,
    participants: list[Player],
    details: MatchDetails | None,
    heroes: dict,
) -> None:
    lines = ["🎮 Dota 2 match found!"]

    if details:
        dt = datetime.fromtimestamp(details.start_time, tz=TZ_UTC3)
        total_minutes = details.duration // 60

        first_steam32 = participants[0].steam32
        is_radiant = details.player_teams.get(first_steam32)
        if is_radiant is not None:
            won = (is_radiant and details.radiant_win) or (not is_radiant and not details.radiant_win)
            result = "Won" if won else "Lost"
        else:
            result = "Radiant won" if details.radiant_win else "Dire won"

        game_mode_name = _GAME_MODE_NAMES.get(details.game_mode, f"Mode {details.game_mode}")
        lines.append(f"Date: {dt.strftime('%d %b %Y, %H:%M')} UTC+3")
        lines.append(f"Game Mode: {game_mode_name}")
        lines.append(f"Result: {result} in {total_minutes} mins")

        for p in participants:
            stats: PlayerMatchStats | None = details.player_stats.get(p.steam32)
            hero = heroes.get(stats.hero_id if stats else 0, "Unknown")
            if stats:
                kda = f"{stats.kills}/{stats.deaths}/{stats.assists}"
                lines.append(
                    f"  {p.name} — {hero}, {kda}, {stats.position}, {stats.lane} lane ({stats.lane_outcome})"
                )
            else:
                lines.append(f"  {p.name} — {hero}")
    else:
        for p in participants:
            lines.append(f"  {p.name}")

    lines.append(f"https://www.dotabuff.com/matches/{match_id}")
    await bot.send_message(chat_id=chat_id, text="\n".join(lines))
