namespace GeoSus.Server;

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

/// <summary>
/// Admin Panel - Webová administrace serveru
/// 
/// Funkce:
/// - Přehled stavu serveru
/// - Real-time spectate mode pro lobby
/// - Konfigurace serveru za běhu
/// - Informace o hráčích
/// - WebSocket pro live updates
/// </summary>
public class AdminPanel
{
    private readonly ILogger<AdminPanel> _logger;
    private readonly ServerConfig _config;
    private readonly LobbyManager _lobbyManager;
    private readonly StatsDb _statsDb;
    
    // Bezpečnost
    private readonly string _adminPasswordHash;
    private readonly ConcurrentDictionary<string, AdminSession> _sessions = new();
    private readonly TimeSpan _sessionTimeout = TimeSpan.FromHours(8);
    
    // WebSocket connections pro spectate
    private readonly ConcurrentDictionary<string, SpectatorConnection> _spectators = new();
    
    public AdminPanel(
        ILogger<AdminPanel> logger,
        ServerConfig config,
        LobbyManager lobbyManager,
        StatsDb statsDb)
    {
        _logger = logger;
        _config = config;
        _lobbyManager = lobbyManager;
        _statsDb = statsDb;
        
        // Hash admin hesla (z env nebo default)
        var adminPassword = Environment.GetEnvironmentVariable("GEOSUS_ADMIN_PASSWORD") ?? "admin123";
        _adminPasswordHash = HashPassword(adminPassword);
        
        _logger.LogInformation("Admin Panel inicializován");
    }
    
    #region ═══════════════════════════════════════════════════════════════════
    //                         AUTENTIZACE
    // ════════════════════════════════════════════════════════════════════════
    #endregion
    
    private static string HashPassword(string password)
    {
        using var sha256 = SHA256.Create();
        var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(password + "GeoSus_Salt_2026"));
        return Convert.ToBase64String(bytes);
    }
    
    private string? ValidateSession(HttpListenerRequest request)
    {
        var cookie = request.Cookies["geosus_admin_session"];
        if (cookie == null) return null;
        
        if (_sessions.TryGetValue(cookie.Value, out var session))
        {
            if (DateTime.UtcNow - session.LastActivity < _sessionTimeout)
            {
                session.LastActivity = DateTime.UtcNow;
                return cookie.Value;
            }
            _sessions.TryRemove(cookie.Value, out _);
        }
        return null;
    }
    
    private string CreateSession()
    {
        var sessionId = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
        _sessions[sessionId] = new AdminSession { LastActivity = DateTime.UtcNow };
        return sessionId;
    }
    
    #region ═══════════════════════════════════════════════════════════════════
    //                         HTTP HANDLING
    // ════════════════════════════════════════════════════════════════════════
    #endregion
    
    public async Task HandleRequestAsync(HttpListenerContext context)
    {
        var request = context.Request;
        var response = context.Response;
        var path = request.Url?.AbsolutePath ?? "/";
        
        try
        {
            // Login endpoint - vždy přístupný
            if (path == "/admin/login" && request.HttpMethod == "POST")
            {
                await HandleLoginAsync(request, response);
                return;
            }
            
            // Statické soubory pro login stránku - bez autentizace
            if (path == "/admin" || path == "/admin/")
            {
                await ServeStaticFileAsync(response, "admin.html", "text/html");
                return;
            }
            
            // CSS a JS musí být dostupné bez autentizace (pro login stránku)
            if (path == "/admin/app.js")
            {
                await ServeStaticFileAsync(response, "admin.js", "application/javascript");
                return;
            }
            
            if (path == "/admin/style.css")
            {
                await ServeStaticFileAsync(response, "admin.css", "text/css");
                return;
            }
            
            // Všechno ostatní vyžaduje autentizaci
            var sessionId = ValidateSession(request);
            if (sessionId == null)
            {
                response.StatusCode = 401;
                await WriteJsonAsync(response, new { error = "Unauthorized", redirect = "/admin" });
                return;
            }
            
            // WebSocket pro spectate
            if (path.StartsWith("/admin/ws/spectate/") && request.IsWebSocketRequest)
            {
                var lobbyId = path["/admin/ws/spectate/".Length..];
                await HandleSpectateWebSocketAsync(context, lobbyId, sessionId);
                return;
            }
            
            // WebSocket pro server stats
            if (path == "/admin/ws/stats" && request.IsWebSocketRequest)
            {
                await HandleStatsWebSocketAsync(context, sessionId);
                return;
            }
            
            // REST API endpointy
            switch (path)
            {
                case "/admin/api/status":
                    await HandleStatusAsync(response);
                    break;
                    
                case "/admin/api/lobbies":
                    await HandleLobbiesAsync(response);
                    break;
                    
                case "/admin/api/config":
                    if (request.HttpMethod == "GET")
                        await HandleGetConfigAsync(response);
                    else if (request.HttpMethod == "POST")
                        await HandleSetConfigAsync(request, response);
                    break;
                    
                case "/admin/api/players":
                    await HandlePlayersAsync(request, response);
                    break;
                    
                case "/admin/api/kick":
                    await HandleKickAsync(request, response);
                    break;
                    
                case "/admin/api/broadcast":
                    await HandleBroadcastAsync(request, response);
                    break;
                    
                case "/admin/api/logout":
                    _sessions.TryRemove(sessionId, out _);
                    response.SetCookie(new Cookie("geosus_admin_session", "", "/admin") { Expired = true });
                    await WriteJsonAsync(response, new { success = true });
                    break;
                    
                default:
                    response.StatusCode = 404;
                    await WriteJsonAsync(response, new { error = "Not found" });
                    break;
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Admin panel error: {Path}", path);
            
            // Don't try to write response for WebSocket requests (already submitted)
            if (!request.IsWebSocketRequest)
            {
                try
                {
                    response.StatusCode = 500;
                    await WriteJsonAsync(response, new { error = ex.Message });
                }
                catch { /* Response may already be sent */ }
            }
        }
    }
    
    #region ═══════════════════════════════════════════════════════════════════
    //                         LOGIN
    // ════════════════════════════════════════════════════════════════════════
    #endregion
    
    private async Task HandleLoginAsync(HttpListenerRequest request, HttpListenerResponse response)
    {
        using var reader = new StreamReader(request.InputStream);
        var body = await reader.ReadToEndAsync();
        var data = JsonSerializer.Deserialize<JsonElement>(body);
        
        var password = data.GetProperty("password").GetString() ?? "";
        var hash = HashPassword(password);
        
        if (hash == _adminPasswordHash)
        {
            var sessionId = CreateSession();
            response.SetCookie(new Cookie("geosus_admin_session", sessionId, "/admin")
            {
                HttpOnly = true,
                Expires = DateTime.Now.Add(_sessionTimeout)
            });
            
            _logger.LogInformation("Admin login successful from {IP}", request.RemoteEndPoint);
            await WriteJsonAsync(response, new { success = true });
        }
        else
        {
            _logger.LogWarning("Admin login failed from {IP}", request.RemoteEndPoint);
            response.StatusCode = 401;
            await WriteJsonAsync(response, new { error = "Invalid password" });
        }
    }
    
    #region ═══════════════════════════════════════════════════════════════════
    //                         API ENDPOINTS
    // ════════════════════════════════════════════════════════════════════════
    #endregion
    
    private async Task HandleStatusAsync(HttpListenerResponse response)
    {
        var uptime = DateTime.UtcNow - Program.StartTime;
        var status = new
        {
            status = "online",
            version = "1.0.0",
            uptime = new
            {
                days = uptime.Days,
                hours = uptime.Hours,
                minutes = uptime.Minutes,
                seconds = uptime.Seconds,
                totalSeconds = (long)uptime.TotalSeconds
            },
            lobbies = new
            {
                active = _lobbyManager.LobbyCount,
                players = _lobbyManager.TotalPlayerCount
            },
            memory = new
            {
                usedMb = GC.GetTotalMemory(false) / 1024 / 1024,
                gcCollections = new
                {
                    gen0 = GC.CollectionCount(0),
                    gen1 = GC.CollectionCount(1),
                    gen2 = GC.CollectionCount(2)
                }
            },
            sessions = _sessions.Count,
            spectators = _spectators.Count
        };
        
        await WriteJsonAsync(response, status);
    }
    
    private async Task HandleLobbiesAsync(HttpListenerResponse response)
    {
        var lobbies = _lobbyManager.GetAllLobbies().Select(l => new
        {
            id = l.LobbyId,
            joinCode = l.JoinCode,
            phase = l.Phase.ToString(),
            players = l.GetPlayers().Select(p => new
            {
                id = p.PlayerId,
                name = p.DisplayName,
                state = p.State.ToString(),
                role = p.Role.ToString(),
                position = new { lat = p.Position.Lat, lng = p.Position.Lon },
                isHost = p.IsHost
            }),
            playerCount = l.PlayerCount,
            settings = new
            {
                impostorCount = l.Settings.ImpostorCount,
                taskCount = l.Settings.TaskCount,
                center = new { lat = l.Settings.PlayAreaCenter.Lat, lng = l.Settings.PlayAreaCenter.Lon },
                radius = l.Settings.PlayAreaRadius
            },
            createdAt = l.CreatedAt,
            activeSabotage = l.ActiveSabotage?.Type.ToString()
        });
        
        await WriteJsonAsync(response, new { lobbies });
    }
    
    private async Task HandleGetConfigAsync(HttpListenerResponse response)
    {
        // Vrátíme pouze bezpečné konfigurace (ne hesla apod.)
        var config = new
        {
            tcpPort = _config.TcpPort,
            httpPort = _config.HttpPort,
            maxPlayersPerLobby = _config.MaxPlayersPerLobby,
            killDistanceM = _config.KillDistanceM,
            killCooldownMs = _config.KillCooldownMs,
            discussionPhaseMs = _config.DiscussionPhaseMs,
            votingPhaseMs = _config.VotingPhaseMs,
            taskStartDistanceM = _config.TaskStartDistanceM,
            sabotageCooldownMs = _config.SabotageCooldownMs,
            criticalMeltdownDeadlineMs = _config.CriticalMeltdownDeadlineMs,
            maxSpeedMps = _config.MaxSpeedMps,
            teleportThresholdMeters = _config.TeleportThresholdMeters
        };
        
        await WriteJsonAsync(response, config);
    }
    
    private async Task HandleSetConfigAsync(HttpListenerRequest request, HttpListenerResponse response)
    {
        using var reader = new StreamReader(request.InputStream);
        var body = await reader.ReadToEndAsync();
        var data = JsonSerializer.Deserialize<JsonElement>(body);
        
        // Aplikuj změny
        if (data.TryGetProperty("killDistanceM", out var kd)) _config.KillDistanceM = kd.GetDouble();
        if (data.TryGetProperty("killCooldownMs", out var kc)) _config.KillCooldownMs = kc.GetInt32();
        if (data.TryGetProperty("discussionPhaseMs", out var dp)) _config.DiscussionPhaseMs = dp.GetInt32();
        if (data.TryGetProperty("votingPhaseMs", out var vp)) _config.VotingPhaseMs = vp.GetInt32();
        if (data.TryGetProperty("maxSpeedMps", out var ms)) _config.MaxSpeedMps = ms.GetDouble();
        
        _logger.LogInformation("Config updated via admin panel");
        
        await WriteJsonAsync(response, new { success = true });
    }
    
    private async Task HandlePlayersAsync(HttpListenerRequest request, HttpListenerResponse response)
    {
        var query = request.QueryString;
        var search = query["search"] ?? "";
        
        var allPlayers = _lobbyManager.GetAllLobbies()
            .SelectMany(l => l.GetPlayers().Select(p => new
            {
                playerId = p.PlayerId,
                displayName = p.DisplayName,
                lobbyId = l.LobbyId,
                joinCode = l.JoinCode,
                state = p.State.ToString(),
                role = p.Role.ToString(),
                position = new { lat = p.Position.Lat, lng = p.Position.Lon },
                isHost = p.IsHost,
                cheatScore = p.CheatScore
            }))
            .Where(p => string.IsNullOrEmpty(search) || 
                        p.displayName.Contains(search, StringComparison.OrdinalIgnoreCase) ||
                        p.playerId.Contains(search, StringComparison.OrdinalIgnoreCase))
            .ToList();
        
        await WriteJsonAsync(response, new { players = allPlayers, total = allPlayers.Count });
    }
    
    private async Task HandleKickAsync(HttpListenerRequest request, HttpListenerResponse response)
    {
        using var reader = new StreamReader(request.InputStream);
        var body = await reader.ReadToEndAsync();
        var data = JsonSerializer.Deserialize<JsonElement>(body);
        
        var playerId = data.GetProperty("playerId").GetString();
        var reason = data.TryGetProperty("reason", out var r) ? r.GetString() : "Kicked by admin";
        
        // Najdi hráče a kickni ho
        foreach (var lobby in _lobbyManager.GetAllLobbies())
        {
            if (lobby.TryKickPlayer(playerId!, reason!))
            {
                _logger.LogInformation("Player {PlayerId} kicked by admin: {Reason}", playerId, reason);
                await WriteJsonAsync(response, new { success = true });
                return;
            }
        }
        
        response.StatusCode = 404;
        await WriteJsonAsync(response, new { error = "Player not found" });
    }
    
    private async Task HandleBroadcastAsync(HttpListenerRequest request, HttpListenerResponse response)
    {
        using var reader = new StreamReader(request.InputStream);
        var body = await reader.ReadToEndAsync();
        var data = JsonSerializer.Deserialize<JsonElement>(body);
        
        var message = data.GetProperty("message").GetString();
        var lobbyId = data.TryGetProperty("lobbyId", out var l) ? l.GetString() : null;
        
        if (string.IsNullOrEmpty(lobbyId))
        {
            // Broadcast všem
            foreach (var lobby in _lobbyManager.GetAllLobbies())
            {
                lobby.BroadcastSystemMessage(message!);
            }
        }
        else
        {
            // Broadcast do konkrétní lobby
            var lobby = _lobbyManager.GetLobby(lobbyId);
            lobby?.BroadcastSystemMessage(message!);
        }
        
        _logger.LogInformation("Admin broadcast: {Message}", message);
        await WriteJsonAsync(response, new { success = true });
    }
    
    #region ═══════════════════════════════════════════════════════════════════
    //                         WEBSOCKET - SPECTATE
    // ════════════════════════════════════════════════════════════════════════
    #endregion
    
    private async Task HandleSpectateWebSocketAsync(HttpListenerContext context, string lobbyId, string sessionId)
    {
        var wsContext = await context.AcceptWebSocketAsync(null);
        var ws = wsContext.WebSocket;
        var spectatorId = Guid.NewGuid().ToString("N")[..8];
        
        var lobby = _lobbyManager.GetLobby(lobbyId);
        if (lobby == null)
        {
            try
            {
                await ws.CloseAsync(WebSocketCloseStatus.InvalidPayloadData, "Lobby not found", CancellationToken.None);
            }
            catch (WebSocketException) { /* Client already disconnected */ }
            return;
        }
        
        var connection = new SpectatorConnection
        {
            WebSocket = ws,
            LobbyId = lobbyId,
            SessionId = sessionId
        };
        
        _spectators[spectatorId] = connection;
        _logger.LogInformation("Spectator {Id} connected to lobby {LobbyId}", spectatorId, lobbyId);
        
        try
        {
            // Pošli initial state
            await SendLobbyStateAsync(ws, lobby);
            
            var buffer = new byte[4096];
            var updateInterval = TimeSpan.FromMilliseconds(200);
            var cts = new CancellationTokenSource();
            var receivedMessage = new TaskCompletionSource<(bool closed, string? message)>();
            
            // Spustíme receive task separátně
            var receiveTask = Task.Run(async () =>
            {
                try
                {
                    while (ws.State == WebSocketState.Open && !cts.Token.IsCancellationRequested)
                    {
                        var result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), cts.Token);
                        if (result.MessageType == WebSocketMessageType.Close)
                        {
                            return true; // closed
                        }
                        
                        if (result.Count > 0)
                        {
                            var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
                            await HandleSpectatorMessageAsync(ws, lobby!, message);
                        }
                    }
                }
                catch (OperationCanceledException) { }
                catch (WebSocketException) { }
                return ws.State != WebSocketState.Open;
            });
            
            // Hlavní smyčka posílá state
            while (ws.State == WebSocketState.Open && !receiveTask.IsCompleted)
            {
                lobby = _lobbyManager.GetLobby(lobbyId);
                if (lobby != null)
                {
                    await SendLobbyStateAsync(ws, lobby);
                }
                else
                {
                    break;
                }
                
                try
                {
                    await Task.WhenAny(receiveTask, Task.Delay(updateInterval, cts.Token));
                }
                catch (OperationCanceledException) { break; }
            }
            
            cts.Cancel();
            
            // Gracefully close if still open
            if (ws.State == WebSocketState.Open)
            {
                try
                {
                    await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, lobby == null ? "Lobby closed" : "Closing", CancellationToken.None);
                }
                catch (WebSocketException) { /* Already closed by client */ }
            }
        }
        catch (WebSocketException)
        {
            // Client disconnected - this is normal
        }
        catch (Exception ex)
        {
            _logger.LogDebug(ex, "Spectate WebSocket error");
        }
        finally
        {
            _spectators.TryRemove(spectatorId, out _);
            _logger.LogInformation("Spectator {Id} disconnected", spectatorId);
        }
    }
    
    private async Task SendLobbyStateAsync(WebSocket ws, LobbyActor lobby)
    {
        var state = new
        {
            type = "state",
            lobby = new
            {
                id = lobby.LobbyId,
                joinCode = lobby.JoinCode,
                phase = lobby.Phase.ToString(),
                phaseEndTime = lobby.PhaseEndTime?.ToString("o"),
                
                players = lobby.GetPlayers().Select(p => new
                {
                    id = p.PlayerId,
                    name = p.DisplayName,
                    state = p.State.ToString(),
                    role = p.Role.ToString(),
                    lat = p.Position.Lat,
                    lng = p.Position.Lon,
                    isHost = p.IsHost,
                    cheatScore = p.CheatScore,
                    killCooldownEnd = p.KillCooldownEnd?.ToString("o"),
                    votedFor = p.VotedFor
                }),
                
                tasks = lobby.GetTasks().Select(t => new
                {
                    id = t.TaskId,
                    name = t.Name,
                    lat = t.Location.Lat,
                    lng = t.Location.Lon,
                    completedBy = t.CompletedBy.ToList()
                }),
                
                bodies = lobby.GetBodies().Select(b => new
                {
                    victimId = b.VictimId,
                    lat = b.Position.Lat,
                    lng = b.Position.Lon,
                    reportedAt = b.ReportedAt?.ToString("o")
                }),
                
                sabotage = lobby.ActiveSabotage != null ? new
                {
                    type = lobby.ActiveSabotage.Type.ToString(),
                    deadline = lobby.ActiveSabotage.Deadline?.ToString("o"),
                    repairStations = lobby.ActiveSabotage.RepairStations.Select(rs => new
                    {
                        id = rs.StationId,
                        lat = rs.Location.Lat,
                        lng = rs.Location.Lon,
                        isRepaired = rs.IsRepaired
                    })
                } : null,
                
                votes = lobby.Phase == GamePhase.Voting ? lobby.GetVotes() : null,
                
                settings = new
                {
                    center = new { lat = lobby.Settings.PlayAreaCenter.Lat, lng = lobby.Settings.PlayAreaCenter.Lon },
                    radius = lobby.Settings.PlayAreaRadius,
                    impostorCount = lobby.Settings.ImpostorCount,
                    taskCount = lobby.Settings.TaskCount
                },
                
                mapData = lobby.MapData // Overpass data pro vykreslení mapy
            },
            timestamp = DateTime.UtcNow.ToString("o")
        };
        
        var json = JsonSerializer.Serialize(state, JsonOptions.Default);
        var bytes = Encoding.UTF8.GetBytes(json);
        
        if (ws.State == WebSocketState.Open)
        {
            await ws.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);
        }
    }
    
    private async Task HandleSpectatorMessageAsync(WebSocket ws, LobbyActor lobby, string message)
    {
        try
        {
            var data = JsonSerializer.Deserialize<JsonElement>(message);
            var type = data.GetProperty("type").GetString();
            
            switch (type)
            {
                case "ping":
                    await SendJsonAsync(ws, new { type = "pong" });
                    break;
                    
                case "getPlayerHistory":
                    var playerId = data.GetProperty("playerId").GetString();
                    var stats = _statsDb.GetPlayerStats(playerId!);
                    await SendJsonAsync(ws, new { type = "playerHistory", stats });
                    break;
            }
        }
        catch { }
    }
    
    #region ═══════════════════════════════════════════════════════════════════
    //                         WEBSOCKET - SERVER STATS
    // ════════════════════════════════════════════════════════════════════════
    #endregion
    
    private async Task HandleStatsWebSocketAsync(HttpListenerContext context, string sessionId)
    {
        var wsContext = await context.AcceptWebSocketAsync(null);
        var ws = wsContext.WebSocket;
        
        try
        {
            var buffer = new byte[1024];
            var updateInterval = TimeSpan.FromSeconds(1);
            var cts = new CancellationTokenSource();
            
            // Spustíme receive task separátně - bude běžet celou dobu
            var receiveTask = Task.Run(async () =>
            {
                try
                {
                    while (ws.State == WebSocketState.Open && !cts.Token.IsCancellationRequested)
                    {
                        var result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), cts.Token);
                        if (result.MessageType == WebSocketMessageType.Close)
                        {
                            return;
                        }
                    }
                }
                catch (OperationCanceledException) { }
                catch (WebSocketException) { }
            });
            
            // Hlavní smyčka posílá stats
            while (ws.State == WebSocketState.Open && !receiveTask.IsCompleted)
            {
                var uptime = DateTime.UtcNow - Program.StartTime;
                var stats = new
                {
                    type = "stats",
                    uptime = (long)uptime.TotalSeconds,
                    lobbies = _lobbyManager.LobbyCount,
                    players = _lobbyManager.TotalPlayerCount,
                    memoryMb = GC.GetTotalMemory(false) / 1024 / 1024,
                    spectators = _spectators.Count,
                    timestamp = DateTime.UtcNow.ToString("o")
                };
                
                await SendJsonAsync(ws, stats);
                
                // Čekáme na interval nebo ukončení receive tasku
                var delayTask = Task.Delay(updateInterval, cts.Token);
                try
                {
                    await Task.WhenAny(receiveTask, delayTask);
                }
                catch (OperationCanceledException) { break; }
            }
            
            cts.Cancel();
            
            if (ws.State == WebSocketState.Open)
            {
                try
                {
                    await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
                }
                catch (WebSocketException) { /* Already closed */ }
            }
        }
        catch (WebSocketException) { }
        catch (Exception ex)
        {
            _logger.LogDebug(ex, "Stats WebSocket error");
        }
    }
    
    #region ═══════════════════════════════════════════════════════════════════
    //                         HELPERS
    // ════════════════════════════════════════════════════════════════════════
    #endregion
    
    private static async Task WriteJsonAsync(HttpListenerResponse response, object data)
    {
        response.ContentType = "application/json";
        var json = JsonSerializer.Serialize(data, JsonOptions.Default);
        var bytes = Encoding.UTF8.GetBytes(json);
        response.ContentLength64 = bytes.Length;
        await response.OutputStream.WriteAsync(bytes);
    }
    
    private static async Task SendJsonAsync(WebSocket ws, object data)
    {
        var json = JsonSerializer.Serialize(data, JsonOptions.Default);
        var bytes = Encoding.UTF8.GetBytes(json);
        if (ws.State == WebSocketState.Open)
        {
            await ws.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);
        }
    }
    
    private async Task ServeStaticFileAsync(HttpListenerResponse response, string filename, string contentType)
    {
        var content = GetEmbeddedResource(filename);
        if (content == null)
        {
            response.StatusCode = 404;
            return;
        }
        
        response.ContentType = contentType + "; charset=utf-8";
        var bytes = Encoding.UTF8.GetBytes(content);
        response.ContentLength64 = bytes.Length;
        await response.OutputStream.WriteAsync(bytes);
    }
    
    private string? GetEmbeddedResource(string filename)
    {
        // Embedded resources z AdminResources třídy
        return filename switch
        {
            "admin.html" => AdminResources.Html,
            "admin.css" => AdminResources.Css,
            "admin.js" => AdminResources.JavaScript,
            _ => null
        };
    }
}

public class AdminSession
{
    public DateTime LastActivity { get; set; }
}

public class SpectatorConnection
{
    public required WebSocket WebSocket { get; set; }
    public required string LobbyId { get; set; }
    public required string SessionId { get; set; }
}
