import json
import os
import threading
from dataclasses import dataclass
from datetime import datetime, timezone

from bot.core.logger import logger

MAX_SEEN_IDS = 5000
PRUNE_TO = 2500


@dataclass
class Player:
    steam32: int
    name: str
    added_at: str


@dataclass
class Member:
    user_id: int
    username: str | None
    first_name: str


class PlayerStore:
    def __init__(self, path: str) -> None:
        self._path = path
        self._lock = threading.Lock()
        # {chat_id: {steam32: Player}}
        self._players: dict[int, dict[int, Player]] = {}
        # {chat_id: set[int]}
        self._seen: dict[int, set[int]] = {}
        # {chat_id: {user_id: Member}}
        self._members: dict[int, dict[int, Member]] = {}

    def load(self) -> None:
        if not os.path.exists(self._path):
            logger.info("No data file found at %s, starting fresh", self._path)
            return
        try:
            with open(self._path, "r") as f:
                raw = json.load(f)
            for chat_str, data in raw.items():
                chat_id = int(chat_str)
                self._players[chat_id] = {}
                for steam_str, p in data.get("players", {}).items():
                    steam32 = int(steam_str)
                    self._players[chat_id][steam32] = Player(
                        steam32=p["steam32"],
                        name=p["name"],
                        added_at=p["added_at"],
                    )
                self._seen[chat_id] = set(data.get("seen_match_ids", []))
                self._members[chat_id] = {}
                for uid_str, m in data.get("members", {}).items():
                    uid = int(uid_str)
                    self._members[chat_id][uid] = Member(
                        user_id=m["user_id"],
                        username=m.get("username"),
                        first_name=m.get("first_name", ""),
                    )
            logger.info("Loaded data for %d chat(s)", len(self._players))
        except Exception:
            logger.exception("Failed to load data file %s", self._path)

    def save(self) -> None:
        with self._lock:
            self._save_locked()

    def _save_locked(self) -> None:
        tmp = self._path + ".tmp"
        data: dict[str, dict] = {}
        all_chats = set(self._players) | set(self._members)
        for chat_id in all_chats:
            players = self._players.get(chat_id, {})
            seen_list = sorted(self._seen.get(chat_id, set()))
            members = self._members.get(chat_id, {})
            data[str(chat_id)] = {
                "players": {
                    str(s32): {"steam32": p.steam32, "name": p.name, "added_at": p.added_at}
                    for s32, p in players.items()
                },
                "seen_match_ids": seen_list,
                "members": {
                    str(uid): {"user_id": m.user_id, "username": m.username, "first_name": m.first_name}
                    for uid, m in members.items()
                },
            }
        try:
            with open(tmp, "w") as f:
                json.dump(data, f, indent=2)
            os.replace(tmp, self._path)
        except Exception:
            logger.exception("Failed to save data file %s", self._path)

    def add_player(self, chat_id: int, steam32: int, name: str) -> bool:
        if chat_id not in self._players:
            self._players[chat_id] = {}
        if steam32 in self._players[chat_id]:
            return False
        self._players[chat_id][steam32] = Player(
            steam32=steam32,
            name=name,
            added_at=datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S"),
        )
        self.save()
        return True

    def remove_player(self, chat_id: int, steam32: int) -> bool:
        if chat_id not in self._players or steam32 not in self._players[chat_id]:
            return False
        del self._players[chat_id][steam32]
        self.save()
        return True

    def list_players(self, chat_id: int) -> list[Player]:
        return list(self._players.get(chat_id, {}).values())

    def all_chats(self) -> list[int]:
        return [
            chat_id
            for chat_id, players in self._players.items()
            if players
        ]

    def mark_seen(self, chat_id: int, match_ids: set[int]) -> None:
        if chat_id not in self._seen:
            self._seen[chat_id] = set()
        self._seen[chat_id].update(match_ids)
        if len(self._seen[chat_id]) > MAX_SEEN_IDS:
            sorted_ids = sorted(self._seen[chat_id])
            self._seen[chat_id] = set(sorted_ids[PRUNE_TO:])

    def is_seen(self, chat_id: int, match_id: int) -> bool:
        return match_id in self._seen.get(chat_id, set())

    def record_member(self, chat_id: int, user_id: int, username: str | None, first_name: str) -> None:
        with self._lock:
            if chat_id not in self._members:
                self._members[chat_id] = {}
            self._members[chat_id][user_id] = Member(
                user_id=user_id,
                username=username,
                first_name=first_name,
            )
            self._save_locked()

    def remove_member(self, chat_id: int, user_id: int) -> None:
        with self._lock:
            if chat_id in self._members and user_id in self._members[chat_id]:
                del self._members[chat_id][user_id]
                self._save_locked()

    def list_members(self, chat_id: int) -> list[Member]:
        return list(self._members.get(chat_id, {}).values())
