namespace GeoSus.Server;

using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text;

// Správa lobby - vytváření, join codes, vyhledávání
public class LobbyManager
{
    private readonly ServerConfig _config;
    private readonly ILogger<LobbyManager> _logger;
    private readonly StatsDb _statsDb;
    private readonly Persistence _persistence;
    private readonly OverpassService _overpassService;
    
    private readonly ConcurrentDictionary<string, LobbyActor> _lobbies = new();
    private readonly ConcurrentDictionary<string, string> _joinCodes = new(); // code -> lobbyId
    private readonly ConcurrentDictionary<string, DateTime> _codeExpiry = new();
    private readonly ConcurrentDictionary<string, RateLimiter> _joinRateLimiters = new();
    
    // Znaky pro join code - bez podobných (0/O, 1/I/L)
    private const string JoinCodeChars = "23456789ABCDEFGHJKMNPQRSTUVWXYZ";
    
    public LobbyManager(ServerConfig config, ILogger<LobbyManager> logger, StatsDb statsDb, Persistence persistence, OverpassService overpassService)
    {
        _config = config;
        _logger = logger;
        _statsDb = statsDb;
        _persistence = persistence;
        _overpassService = overpassService;
    }
    
    public async Task<(LobbyActor? Lobby, string? JoinCode, string? Error)> CreateLobbyAsync(
        string ownerId, string ownerName, CreateLobby request)
    {
        var lobbyId = Guid.NewGuid().ToString("N")[..16];
        var joinCode = GenerateJoinCode();
        
        var playAreaCenter = request.PlayAreaCenter ?? new Position(50.0, 14.0); // Default Praha
        var playAreaRadius = request.PlayAreaRadius > 0 ? request.PlayAreaRadius : 500;
        
        // NOTE: Map data is now fetched when game starts, not on lobby creation
        // This allows all clients to see a loading screen while data is fetched
        
        var settings = new LobbySettings
        {
            LobbyId = lobbyId,
            JoinCode = joinCode,
            Password = request.Password,
            PlayAreaCenter = playAreaCenter,
            PlayAreaRadius = playAreaRadius,
            ImpostorCount = request.ImpostorCount > 0 ? request.ImpostorCount : _config.DefaultImpostorCount,
            TaskCount = request.TaskCount > 0 ? request.TaskCount : _config.DefaultTaskCount,
            TiePolicy = _config.DefaultTiePolicy,
            EmergencyMeetingCooldownMs = _config.EmergencyMeetingCooldownMs,
            MaxEmergencyMeetingsPerPlayer = _config.MaxEmergencyMeetingsPerPlayer,
            AllowJoinInProgress = false,
            MapData = null,
            MapDataPayload = null,
            OverpassEnabled = _config.OverpassEnabled
        };
        
        var lobbyLogger = _logger.CreateLogger<LobbyActor>();
        var lobby = new LobbyActor(lobbyId, settings, _config, lobbyLogger, _statsDb, _persistence, _overpassService);
        
        if (!_lobbies.TryAdd(lobbyId, lobby))
        {
            return (null, null, "Nepodařilo se vytvořit lobby");
        }
        
        _joinCodes[joinCode] = lobbyId;
        _codeExpiry[joinCode] = DateTime.UtcNow.AddMilliseconds(_config.JoinCodeTtlMs);
        
        // Přidáme vlastníka
        await lobby.AddPlayerAsync(ownerId, ownerName, isOwner: true);
        
        _logger.LogInformation("Lobby {LobbyId} vytvořeno, join code: {JoinCode}", lobbyId, joinCode);
        
        return (lobby, joinCode, null);
    }
    
    public async Task<(LobbyActor? Lobby, string? Error)> JoinLobbyAsync(
        string clientIp, string clientUuid, string displayName, string joinCode, string? password)
    {
        // Rate limiting per IP
        var limiter = _joinRateLimiters.GetOrAdd(clientIp, _ => new RateLimiter(_config.JoinRateLimitPerMinute, TimeSpan.FromMinutes(1)));
        if (!limiter.TryAcquire())
        {
            return (null, "Příliš mnoho pokusů o připojení. Počkej chvíli.");
        }
        
        // Najdeme lobby podle join code
        if (!_joinCodes.TryGetValue(joinCode.ToUpperInvariant(), out var lobbyId))
        {
            return (null, "Neplatný join code");
        }
        
        // Kontrola expirace
        if (_codeExpiry.TryGetValue(joinCode.ToUpperInvariant(), out var expiry) && expiry < DateTime.UtcNow)
        {
            _joinCodes.TryRemove(joinCode.ToUpperInvariant(), out _);
            _codeExpiry.TryRemove(joinCode.ToUpperInvariant(), out _);
            return (null, "Join code vypršel");
        }
        
        if (!_lobbies.TryGetValue(lobbyId, out var lobby))
        {
            return (null, "Lobby již neexistuje");
        }
        
        // Kontrola hesla
        if (!string.IsNullOrEmpty(lobby.Settings.Password) && lobby.Settings.Password != password)
        {
            return (null, "Špatné heslo");
        }
        
        // Kontrola fáze
        if (lobby.Phase != GamePhase.Lobby && !lobby.Settings.AllowJoinInProgress)
        {
            return (null, "Hra již probíhá");
        }
        
        // Kontrola počtu hráčů
        if (lobby.PlayerCount >= _config.MaxPlayersPerLobby)
        {
            return (null, "Lobby je plné");
        }
        
        await lobby.AddPlayerAsync(clientUuid, displayName, isOwner: false);
        
        _logger.LogInformation("Hráč {ClientUuid} se připojil do lobby {LobbyId}", clientUuid, lobbyId);
        
        return (lobby, null);
    }
    
    public LobbyActor? GetLobby(string lobbyId)
    {
        _lobbies.TryGetValue(lobbyId, out var lobby);
        return lobby;
    }
    
    public LobbyActor? GetLobbyByJoinCode(string joinCode)
    {
        if (_joinCodes.TryGetValue(joinCode.ToUpperInvariant(), out var lobbyId))
        {
            return GetLobby(lobbyId);
        }
        return null;
    }
    
    public IEnumerable<LobbyActor> GetAllLobbies()
    {
        return _lobbies.Values;
    }
    
    public void RemoveLobby(string lobbyId)
    {
        if (_lobbies.TryRemove(lobbyId, out var lobby))
        {
            // Odstraníme join code
            var joinCode = lobby.Settings.JoinCode;
            _joinCodes.TryRemove(joinCode, out _);
            _codeExpiry.TryRemove(joinCode, out _);
            
            lobby.Dispose();
            _logger.LogInformation("Lobby {LobbyId} odstraněno", lobbyId);
        }
    }
    
    // Čištění idle lobby
    public async Task CleanupIdleLobbiesAsync()
    {
        var now = DateTime.UtcNow;
        var idleThreshold = TimeSpan.FromMilliseconds(_config.IdleLobbyTtlMs);
        
        foreach (var (lobbyId, lobby) in _lobbies)
        {
            if (now - lobby.LastActivity > idleThreshold)
            {
                _logger.LogInformation("Ruším idle lobby {LobbyId}", lobbyId);
                await lobby.ArchiveAndCloseAsync();
                RemoveLobby(lobbyId);
            }
        }
        
        // Čištění expirovaných join codes
        foreach (var (code, expiry) in _codeExpiry)
        {
            if (expiry < now)
            {
                _joinCodes.TryRemove(code, out _);
                _codeExpiry.TryRemove(code, out _);
            }
        }
    }
    
    private string GenerateJoinCode()
    {
        Span<byte> randomBytes = stackalloc byte[6];
        RandomNumberGenerator.Fill(randomBytes);
        
        var sb = new StringBuilder(6);
        foreach (var b in randomBytes)
        {
            sb.Append(JoinCodeChars[b % JoinCodeChars.Length]);
        }
        
        var code = sb.ToString();
        
        // Pokud kód existuje, generujeme znovu
        if (_joinCodes.ContainsKey(code))
        {
            return GenerateJoinCode();
        }
        
        return code;
    }
    
    public int LobbyCount => _lobbies.Count;
    
    public int TotalPlayerCount => _lobbies.Values.Sum(l => l.PlayerCount);
}

public class LobbySettings
{
    public required string LobbyId { get; set; }
    public required string JoinCode { get; set; }
    public string? Password { get; set; }
    public Position PlayAreaCenter { get; set; }
    public double PlayAreaRadius { get; set; }
    public int ImpostorCount { get; set; }
    public int TaskCount { get; set; }
    public TiePolicy TiePolicy { get; set; }
    public int EmergencyMeetingCooldownMs { get; set; }
    public int MaxEmergencyMeetingsPerPlayer { get; set; }
    public bool AllowJoinInProgress { get; set; }
    public Position EmergencyMeetingLocation { get; set; } // Střed play area jako default
    
    /// <summary>Full map data from Overpass (server-side only, for task placement)</summary>
    public MapData? MapData { get; set; }
    
    /// <summary>Compact map data payload for clients</summary>
    public MapDataPayload? MapDataPayload { get; set; }
    
    /// <summary>Whether Overpass API was enabled for this lobby</summary>
    public bool OverpassEnabled { get; set; }
}

// Jednoduchý rate limiter
public class RateLimiter
{
    private readonly int _maxRequests;
    private readonly TimeSpan _window;
    private readonly Queue<DateTime> _requests = new();
    private readonly object _lock = new();
    
    public RateLimiter(int maxRequests, TimeSpan window)
    {
        _maxRequests = maxRequests;
        _window = window;
    }
    
    public bool TryAcquire()
    {
        lock (_lock)
        {
            var now = DateTime.UtcNow;
            var cutoff = now - _window;
            
            // Odstraníme staré požadavky
            while (_requests.Count > 0 && _requests.Peek() < cutoff)
            {
                _requests.Dequeue();
            }
            
            if (_requests.Count >= _maxRequests)
            {
                return false;
            }
            
            _requests.Enqueue(now);
            return true;
        }
    }
}

// Extension pro vytvoření sub-loggeru
public static class LoggerExtensions
{
    public static ILogger<T> CreateLogger<T>(this ILogger logger)
    {
        if (logger is ILoggerFactory factory)
        {
            return factory.CreateLogger<T>();
        }
        // Fallback - vrátíme prázdný logger
        return new LoggerFactory().CreateLogger<T>();
    }
}
