import json
import asyncio
import random
import math
from typing import Set, Dict, Optional
from fastapi import WebSocket
from datetime import datetime


class ConnectionManager:
    def __init__(self):
        self.active_connections: Dict[int, Set[WebSocket]] = {}
        self._lock = asyncio.Lock()

    async def connect(self, websocket: WebSocket, user_id: int):
        await websocket.accept()
        async with self._lock:
            if user_id not in self.active_connections:
                self.active_connections[user_id] = set()
            self.active_connections[user_id].add(websocket)

    async def disconnect(self, websocket: WebSocket, user_id: int):
        async with self._lock:
            if user_id in self.active_connections:
                self.active_connections[user_id].discard(websocket)
                if not self.active_connections[user_id]:
                    del self.active_connections[user_id]

    async def send_personal(self, user_id: int, message: dict):
        async with self._lock:
            if user_id in self.active_connections:
                dead = set()
                for ws in self.active_connections[user_id]:
                    try:
                        await ws.send_json(message)
                    except Exception:
                        dead.add(ws)
                self.active_connections[user_id] -= dead
                if not self.active_connections[user_id]:
                    del self.active_connections[user_id]

    async def broadcast(self, message: dict):
        async with self._lock:
            dead: Dict[int, Set[WebSocket]] = {}
            for uid, sockets in self.active_connections.items():
                for ws in sockets:
                    try:
                        await ws.send_json(message)
                    except Exception:
                        dead.setdefault(uid, set()).add(ws)
            for uid, sockets in dead.items():
                self.active_connections[uid] -= sockets
                if not self.active_connections[uid]:
                    del self.active_connections[uid]

    async def broadcast_except(self, exclude_user_id: int, message: dict):
        async with self._lock:
            for uid, sockets in self.active_connections.items():
                if uid == exclude_user_id:
                    continue
                for ws in sockets:
                    try:
                        await ws.send_json(message)
                    except Exception:
                        pass

    @property
    def online_count(self) -> int:
        """Количество уникальных пользователей онлайн"""
        return len(self.active_connections)


manager = ConnectionManager()


# ─── Crash Game State ────────────────────────────────────────────────────────

class CrashGameState:
    def __init__(self):
        self.running = False
        self.current_multiplier = 1.0
        self.crash_point = 1.0
        self.bets: Dict[int, float] = {}
        self.cashed_out: Dict[int, float] = {}
        self.phase = "waiting"
        self.timer = 0
        self.round = 0
        self.history: list = []
        self._start_time = 0.0
        self._elapsed = 0.0

    def generate_crash_point(self) -> float:
        """True CS2-style provably fair crash point using exponential distribution"""
        # Используем распределение: чем выше множитель, тем меньше шанс
        r = random.random()
        # Формула: crash_point = 1 / (1 - r) с ограничением
        # Это даёт экспоненциальное распределение
        crash_point = 1.0 / (1.0 - r * 0.99)
        # В редких случаях может быть очень высоким (>1000), ограничиваем
        crash_point = min(crash_point, 1000.0)
        return round(crash_point, 2)

    def get_multiplier_at_time(self, elapsed_seconds: float) -> float:
        """Экспоненциальный рост множителя: e^(t * speed)"""
        speed = 0.08  # скорость роста
        return round(math.exp(elapsed_seconds * speed), 2)

    def get_time_for_multiplier(self, mult: float) -> float:
        """Сколько времени нужно для достижения множителя"""
        return math.log(mult) / 0.08

    def calculate_payout(self, bet_amount: float, cashout_at: float) -> float:
        return round(bet_amount * cashout_at, 2)

    def to_dict(self):
        return {
            "phase": self.phase,
            "round": self.round,
            "current_multiplier": self.current_multiplier,
            "crash_point": self.crash_point,
            "timer": self.timer,
            "bet_count": len(self.bets),
            "total_bets": sum(self.bets.values()),
            "cashed_out": {str(k): v for k, v in self.cashed_out.items()},
            "history": self.history[-20:],
        }


crash_game = CrashGameState()
