namespace GeoSus.Server;

using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;

// Hlavní vstupní bod serveru
public class Program
{
    static readonly ConcurrentDictionary<string, ConnectedClient> _clients = new();
    static readonly DateTime _startTime = DateTime.UtcNow;
    static ServerConfig _config = null!;
    static ServerEncryption _encryption = null!;
    static LobbyManager _lobbyManager = null!;
    static StatsDb _statsDb = null!;
    static Persistence _persistence = null!;
    static OverpassService _overpassService = null!;
    static AdminPanel _adminPanel = null!;
    static ILoggerFactory _loggerFactory = null!;
    static ILogger<Program> _logger = null!;
    static CancellationTokenSource _cts = new();
    
    // Public accessor for admin panel
    public static DateTime StartTime => _startTime;
    
    public static async Task Main(string[] args)
    {
        Console.OutputEncoding = Encoding.UTF8;
        
        // Inicializace
        _loggerFactory = LoggerFactory.Create(builder =>
        {
            builder.AddConsole();
            builder.SetMinimumLevel(LogLevel.Information);
        });
        _logger = _loggerFactory.CreateLogger<Program>();
        
        // Načtení konfigurace
        _config = ServerConfig.Load("appsettings.json");
        
        // Uložíme výchozí config pokud neexistuje
        if (!File.Exists("appsettings.json"))
        {
            _config.Save("appsettings.json");
            _logger.LogInformation("Vytvořen výchozí appsettings.json");
        }
        
        // Inicializace komponent
        _encryption = new ServerEncryption(_config.RsaKeySizeBits);
        _statsDb = new StatsDb(Path.Combine(_config.DataPath, "stats.db"), _loggerFactory.CreateLogger<StatsDb>());
        _persistence = new Persistence(_config, _loggerFactory.CreateLogger<Persistence>());
        _overpassService = new OverpassService(_config, _loggerFactory.CreateLogger<OverpassService>());
        _lobbyManager = new LobbyManager(_config, _loggerFactory.CreateLogger<LobbyManager>(), _statsDb, _persistence, _overpassService);
        _adminPanel = new AdminPanel(_loggerFactory.CreateLogger<AdminPanel>(), _config, _lobbyManager, _statsDb);
        
        _logger.LogInformation("GeoSus Server spouštím...");
        _logger.LogInformation("TCP port: {TcpPort}, HTTP port: {HttpPort}", _config.TcpPort, _config.HttpPort);
        
        // Handle shutdown signals - but only on explicit request
        var shutdownRequested = false;
        Console.CancelKeyPress += (s, e) =>
        {
            if (!shutdownRequested)
            {
                shutdownRequested = true;
                e.Cancel = true;
                _logger.LogInformation("Shutdown requested (Ctrl+C)... press again to force");
            }
            else
            {
                _logger.LogInformation("Force shutdown...");
                _cts.Cancel();
            }
        };
        
        // Spustíme TCP listener a HTTP API paralelně
        var tcpTask = RunTcpServerAsync();
        var httpTask = RunHttpServerAsync();
        var cleanupTask = RunCleanupLoopAsync();
        var broadcastTask = RunBroadcastLoopAsync();
        
        _logger.LogInformation("Server běží. Stiskni Ctrl+C dvakrát pro ukončení.");
        
        // Just wait forever until cancellation - don't monitor individual tasks
        try
        {
            await Task.Delay(Timeout.Infinite, _cts.Token);
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("Shutting down...");
        }
        
        _statsDb.Dispose();
        _encryption.Dispose();
        
        _logger.LogInformation("Server ukončen");
    }
    
    #region TCP Server
    
    static async Task RunTcpServerAsync()
    {
        var listener = new TcpListener(IPAddress.Any, _config.TcpPort);
        listener.Start();
        
        _logger.LogInformation("TCP server naslouchá na portu {Port}", _config.TcpPort);
        
        try
        {
            while (!_cts.IsCancellationRequested)
            {
                try
                {
                    var tcpClient = await listener.AcceptTcpClientAsync(_cts.Token);
                    _ = HandleClientAsync(tcpClient);
                }
                catch (SocketException ex)
                {
                    _logger.LogWarning(ex, "Socket error while accepting client");
                }
            }
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("TCP server ukončen (cancelled)");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "TCP server fatal error");
        }
        finally
        {
            listener.Stop();
        }
    }
    
    static async Task HandleClientAsync(TcpClient tcpClient)
    {
        var endpoint = tcpClient.Client.RemoteEndPoint?.ToString() ?? "unknown";
        _logger.LogDebug("Nové spojení z {Endpoint}", endpoint);
        
        using var stream = tcpClient.GetStream();
        ConnectedClient? client = null;
        
        try
        {
            // Handshake - plain JSON
            client = await PerformHandshakeAsync(stream, endpoint);
            
            if (client == null)
            {
                _logger.LogWarning("Handshake selhal pro {Endpoint}", endpoint);
                return;
            }
            
            _clients[client.ClientUuid] = client;
            _logger.LogInformation("Klient {Uuid} připojen z {Endpoint}", client.ClientUuid, endpoint);
            
            // Hlavní smyčka zpráv - šifrované
            await ProcessMessagesAsync(stream, client);
        }
        catch (Exception ex) when (ex is not OperationCanceledException)
        {
            _logger.LogError(ex, "Chyba při zpracování klienta {Endpoint}", endpoint);
        }
        finally
        {
            if (client != null)
            {
                _clients.TryRemove(client.ClientUuid, out _);
                
                // Odebrat z lobby pokud byl přihlášen
                if (client.Lobby != null)
                {
                    client.Lobby.UnregisterConnection(client.ClientUuid);
                    await client.Lobby.RemovePlayerAsync(client.ClientUuid, "Disconnected");
                }
                
                client.Session?.Dispose();
                _logger.LogInformation("Klient {Uuid} odpojen", client.ClientUuid);
            }
            
            tcpClient.Close();
        }
    }
    
    static async Task<ConnectedClient?> PerformHandshakeAsync(NetworkStream stream, string endpoint)
    {
        // 1. Čekáme na ClientHello (plain)
        var helloData = await ReadMessageAsync(stream);
        if (helloData == null) return null;
        
        // Debug: log co přichází
        _logger.LogInformation("Handshake from {Endpoint}: {Length} bytes, first: 0x{First:X2}, content: {Content}", 
            endpoint, helloData.Length, helloData[0], 
            System.Text.Encoding.UTF8.GetString(helloData.AsSpan(0, Math.Min(200, helloData.Length))));
        
        var hello = MessageSerializer.Deserialize(helloData) as ClientHello;
        if (hello == null)
        {
            _logger.LogWarning("Neplatný ClientHello z {Endpoint}", endpoint);
            return null;
        }
        
        // 2. Posíláme ServerHello s RSA public key (plain)
        var serverHello = new ServerHello
        {
            RsaPublicKeyPem = _encryption.PublicKeyPem,
            ServerId = Environment.MachineName
        };
        await SendMessagePlainAsync(stream, serverHello);
        
        // 3. Čekáme na KeyExchange (plain - obsahuje RSA šifrovaný session key)
        var keyExData = await ReadMessageAsync(stream);
        if (keyExData == null) return null;
        
        // Debug: hex dump prvních 20 bajtů
        var hexDump = BitConverter.ToString(keyExData, 0, Math.Min(20, keyExData.Length));
        _logger.LogInformation("KeyExchange from {Endpoint}: {Length} bytes, hex: {Hex}", 
            endpoint, keyExData.Length, hexDump);
        
        var keyEx = MessageSerializer.Deserialize(keyExData) as KeyExchange;
        if (keyEx == null)
        {
            _logger.LogWarning("Neplatný KeyExchange z {Endpoint}", endpoint);
            return null;
        }
        
        // 4. Dešifrujeme session key
        byte[] sessionKey, sessionIv;
        try
        {
            (sessionKey, sessionIv) = _encryption.DecryptSessionKey(keyEx.EncryptedSessionKey, keyEx.EncryptedIV);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Chyba při dešifrování session key z {Endpoint}", endpoint);
            return null;
        }
        
        var session = new SessionCrypto(sessionKey, sessionIv);
        
        // 5. Posíláme KeyExchangeAck (šifrovaně)
        var ack = new KeyExchangeAck { Status = "success" };
        await SendMessageEncryptedAsync(stream, ack, session);
        
        return new ConnectedClient
        {
            ClientUuid = hello.ClientUuid,
            DisplayName = hello.DisplayName ?? $"Player_{hello.ClientUuid[..4]}",
            Endpoint = endpoint,
            Session = session,
            Stream = stream
        };
    }
    
    static async Task ProcessMessagesAsync(NetworkStream stream, ConnectedClient client)
    {
        int decryptFailures = 0;
        
        while (!_cts.IsCancellationRequested)
        {
            var encrypted = await ReadMessageAsync(stream);
            if (encrypted == null) break;
            
            // Dešifrovat
            var decrypted = client.Session!.Decrypt(encrypted);
            if (decrypted == null)
            {
                decryptFailures++;
                _logger.LogWarning("Dešifrování selhalo pro {Uuid} ({Count}x)", client.ClientUuid, decryptFailures);
                
                if (decryptFailures >= 3)
                {
                    _logger.LogWarning("Příliš mnoho chyb dešifrování, odpojuji {Uuid}", client.ClientUuid);
                    break;
                }
                continue;
            }
            
            decryptFailures = 0;
            
            var message = MessageSerializer.Deserialize(decrypted);
            if (message == null)
            {
                _logger.LogWarning("Neplatná zpráva od {Uuid}", client.ClientUuid);
                continue;
            }
            
            await HandleMessageAsync(client, message);
        }
    }
    
    static async Task HandleMessageAsync(ConnectedClient client, Message message)
    {
        switch (message)
        {
            case CreateLobby m:
                await HandleCreateLobbyAsync(client, m);
                break;
                
            case JoinLobby m:
                await HandleJoinLobbyAsync(client, m);
                break;
                
            case Reconnect m:
                await HandleReconnectAsync(client, m);
                break;
                
            default:
                // Předat do lobby pokud je přihlášen
                if (client.Lobby != null)
                {
                    await client.Lobby.HandleMessageAsync(client.ClientUuid, message);
                }
                else
                {
                    var error = new ErrorMessage 
                    { 
                        ErrorCode = "NOT_IN_LOBBY", 
                        ErrorText = "Nejsi v žádném lobby" 
                    };
                    await SendToClientAsync(client, error);
                }
                break;
        }
    }
    
    static async Task HandleCreateLobbyAsync(ConnectedClient client, CreateLobby message)
    {
        var (lobby, joinCode, error) = await _lobbyManager.CreateLobbyAsync(
            client.ClientUuid, client.DisplayName, message);
        
        var response = new CreateLobbyResponse
        {
            ClientSeq = message.ClientSeq,
            Success = lobby != null,
            JoinCode = joinCode,
            LobbyId = lobby?.Settings.LobbyId,
            Error = error,
            LobbyState = lobby?.GetLobbyState()
        };
        
        if (lobby != null)
        {
            client.Lobby = lobby;
            lobby.RegisterConnection(client.ClientUuid, new ClientConnection
            {
                ClientUuid = client.ClientUuid,
                SendAsync = msg => SendToClientAsync(client, msg)
            });
        }
        
        await SendToClientAsync(client, response);
    }
    
    static async Task HandleJoinLobbyAsync(ConnectedClient client, JoinLobby message)
    {
        var clientIp = client.Endpoint.Split(':')[0];
        var (lobby, error) = await _lobbyManager.JoinLobbyAsync(
            clientIp, client.ClientUuid, client.DisplayName, message.JoinCode, message.Password);
        
        var response = new JoinLobbyResponse
        {
            ClientSeq = message.ClientSeq,
            Success = lobby != null,
            LobbyId = lobby?.Settings.LobbyId,
            Error = error,
            LobbyState = lobby?.GetLobbyState()
        };
        
        if (lobby != null)
        {
            client.Lobby = lobby;
            lobby.RegisterConnection(client.ClientUuid, new ClientConnection
            {
                ClientUuid = client.ClientUuid,
                SendAsync = msg => SendToClientAsync(client, msg)
            });
        }
        
        await SendToClientAsync(client, response);
    }
    
    static async Task HandleReconnectAsync(ConnectedClient client, Reconnect message)
    {
        var lobby = _lobbyManager.GetLobby(message.LobbyId);
        
        if (lobby == null)
        {
            await SendToClientAsync(client, new ReconnectResponse
            {
                Success = false,
                Error = "Lobby neexistuje"
            });
            return;
        }
        
        // Načteme snapshot a eventy
        var snapshot = _persistence.LoadLatestSnapshot(message.LobbyId);
        var events = _persistence.ReadWal(message.LobbyId, message.LastEventId);
        
        client.Lobby = lobby;
        lobby.RegisterConnection(client.ClientUuid, new ClientConnection
        {
            ClientUuid = client.ClientUuid,
            SendAsync = msg => SendToClientAsync(client, msg)
        });
        
        await lobby.AddPlayerAsync(client.ClientUuid, client.DisplayName, isOwner: false);
        
        await SendToClientAsync(client, new ReconnectResponse
        {
            Success = true,
            Snapshot = snapshot,
            MissedEvents = events
        });
    }
    
    #endregion
    
    #region HTTP Stats API
    
    static async Task RunHttpServerAsync()
    {
        HttpListener? listener = null;
        bool isDocker = Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true" 
                     || Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") == "Production";
        
        // V Dockeru/produkci bindujeme na 0.0.0.0 (všechny interfaces)
        // Lokálně bindujeme na localhost (nevyžaduje admin práva na Windows)
        var prefixes = isDocker
            ? new[] { $"http://+:{_config.HttpPort}/", $"http://*:{_config.HttpPort}/" }
            : new[] { $"http://localhost:{_config.HttpPort}/", $"http://127.0.0.1:{_config.HttpPort}/" };
        
        foreach (var prefixSet in new[] { prefixes })
        {
            try
            {
                listener = new HttpListener();
                foreach (var prefix in prefixSet)
                    listener.Prefixes.Add(prefix);
                listener.Start();
                _logger.LogInformation("HTTP Stats API naslouchá na portu {Port} (Docker: {IsDocker})", _config.HttpPort, isDocker);
                break;
            }
            catch (HttpListenerException ex)
            {
                _logger.LogWarning("Nelze spustit HTTP Stats API: {Error}", ex.Message);
                listener?.Close();
                listener = null;
            }
        }
        
        if (listener == null)
        {
            _logger.LogWarning("HTTP Stats API není dostupné - nelze otevřít port {Port}. Server běží bez Stats API.", _config.HttpPort);
            // Just wait for cancellation instead of crashing
            try { await Task.Delay(Timeout.Infinite, _cts.Token); } catch (OperationCanceledException) { }
            return;
        }
        
        try
        {
            while (!_cts.IsCancellationRequested)
            {
                var context = await listener.GetContextAsync();
                _ = HandleHttpRequestAsync(context);
            }
        }
        catch (HttpListenerException) when (_cts.IsCancellationRequested)
        {
            // Normal shutdown
        }
        catch (ObjectDisposedException) when (_cts.IsCancellationRequested)
        {
            // Normal shutdown  
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "HTTP listener error");
        }
        finally
        {
            listener?.Stop();
        }
    }
    
    static async Task HandleHttpRequestAsync(HttpListenerContext context)
    {
        var request = context.Request;
        var response = context.Response;
        
        // CORS headers
        response.Headers.Add("Access-Control-Allow-Origin", "*");
        response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
        response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization");
        
        if (request.HttpMethod == "OPTIONS")
        {
            response.StatusCode = 200;
            response.Close();
            return;
        }
        
        try
        {
            var path = request.Url?.AbsolutePath ?? "/";
            
            // Admin panel routes
            if (path.StartsWith("/admin"))
            {
                await _adminPanel.HandleRequestAsync(context);
                return;
            }
            
            if (path.StartsWith("/stats/leaderboard"))
            {
                await HandleLeaderboardAsync(request, response);
            }
            else if (path.StartsWith("/stats/"))
            {
                var clientUuid = path[7..]; // Remove "/stats/"
                await HandlePlayerStatsAsync(clientUuid, response);
            }
            else if (path == "/health")
            {
                var uptime = DateTime.UtcNow - _startTime;
                await WriteJsonResponseAsync(response, new 
                { 
                    status = "ok", 
                    version = "1.0.0",
                    uptimeSeconds = (long)uptime.TotalSeconds,
                    activeLobbies = _lobbyManager.LobbyCount,
                    connectedPlayers = _lobbyManager.TotalPlayerCount
                });
            }
            else
            {
                response.StatusCode = 404;
                await WriteJsonResponseAsync(response, new { error = "Not found" });
            }
            
            response.Close();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "HTTP error");
            try
            {
                response.StatusCode = 500;
                await WriteJsonResponseAsync(response, new { error = "Internal server error" });
                response.Close();
            }
            catch { /* Already closed */ }
        }
    }
    
    static async Task HandlePlayerStatsAsync(string clientUuid, HttpListenerResponse response)
    {
        var stats = _statsDb.GetPlayerStats(clientUuid);
        
        if (stats == null)
        {
            response.StatusCode = 404;
            await WriteJsonResponseAsync(response, new { error = "Player not found" });
            return;
        }
        
        var result = new
        {
            clientUuid = stats.ClientUuid,
            displayName = stats.DisplayName,
            totalGames = stats.TotalGames,
            gamesAsCrew = stats.GamesAsCrew,
            gamesAsImpostor = stats.GamesAsImpostor,
            crewWins = stats.CrewWins,
            impostorWins = stats.ImpostorWins,
            crewWinRate = Math.Round(stats.CrewWinRate, 2),
            impostorWinRate = Math.Round(stats.ImpostorWinRate, 2),
            totalKills = stats.TotalKills,
            totalDeaths = stats.TotalDeaths,
            killDeathRatio = Math.Round(stats.KillDeathRatio, 2),
            tasksCompleted = stats.TasksCompleted,
            averageTasksPerGame = Math.Round(stats.AverageTasksPerGame, 2),
            bodiesReported = stats.BodiesReported,
            emergencyMeetingsCalled = stats.EmergencyMeetingsCalled,
            timesVotedOut = stats.TimesVotedOut,
            successfulVotes = stats.SuccessfulVotes,
            totalPlaytimeSeconds = stats.TotalPlaytimeSeconds,
            cheatIncidents = stats.CheatIncidents,
            lastSeen = stats.LastSeenUtc
        };
        
        await WriteJsonResponseAsync(response, result);
    }
    
    static async Task HandleLeaderboardAsync(HttpListenerRequest request, HttpListenerResponse response)
    {
        var sortBy = request.QueryString["sortBy"] ?? "total_games";
        var limitStr = request.QueryString["limit"] ?? "100";
        var limit = int.TryParse(limitStr, out var l) ? l : 100;
        limit = Math.Min(limit, 1000);
        
        var leaderboard = _statsDb.GetLeaderboard(sortBy, limit);
        
        var result = leaderboard.Select(s => new
        {
            clientUuid = s.ClientUuid,
            displayName = s.DisplayName,
            totalGames = s.TotalGames,
            crewWins = s.CrewWins,
            impostorWins = s.ImpostorWins,
            totalKills = s.TotalKills,
            tasksCompleted = s.TasksCompleted
        });
        
        await WriteJsonResponseAsync(response, result);
    }
    
    static async Task WriteJsonResponseAsync(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);
    }
    
    #endregion
    
    #region Background tasks
    
    static async Task RunCleanupLoopAsync()
    {
        try
        {
            using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
            
            while (await timer.WaitForNextTickAsync(_cts.Token))
            {
                try
                {
                    await _lobbyManager.CleanupIdleLobbiesAsync();
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Chyba při cleanup");
                }
            }
        }
        catch (OperationCanceledException)
        {
            // Normal shutdown
        }
    }
    
    static async Task RunBroadcastLoopAsync()
    {
        try
        {
            using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(_config.PositionBroadcastRateMs));
            
            while (await timer.WaitForNextTickAsync(_cts.Token))
            {
                foreach (var client in _clients.Values)
                {
                    client.Lobby?.BroadcastPositions();
                }
            }
        }
        catch (OperationCanceledException)
        {
            // Normal shutdown
        }
    }
    
    #endregion
    
    #region Network helpers
    
    static async Task<byte[]?> ReadMessageAsync(NetworkStream stream)
    {
        // 4 bytes length prefix (big-endian)
        var lengthBuffer = new byte[4];
        var read = await stream.ReadAsync(lengthBuffer, 0, 4);
        if (read < 4) return null;
        
        if (BitConverter.IsLittleEndian)
            Array.Reverse(lengthBuffer);
        var length = BitConverter.ToInt32(lengthBuffer, 0);
        
        if (length <= 0 || length > _config.MaxPacketSizeBytes)
            return null;
            
        var buffer = new byte[length];
        var totalRead = 0;
        while (totalRead < length)
        {
            read = await stream.ReadAsync(buffer, totalRead, length - totalRead);
            if (read == 0) return null;
            totalRead += read;
        }
        
        return buffer;
    }
    
    static async Task SendMessagePlainAsync(NetworkStream stream, Message message)
    {
        var data = MessageSerializer.Serialize(message);
        await SendDataAsync(stream, data);
    }
    
    static async Task SendMessageEncryptedAsync(NetworkStream stream, Message message, SessionCrypto session)
    {
        var plain = MessageSerializer.Serialize(message);
        var encrypted = session.Encrypt(plain);
        await SendDataAsync(stream, encrypted);
    }
    
    static async Task SendDataAsync(NetworkStream stream, byte[] data)
    {
        var lengthBuffer = BitConverter.GetBytes(data.Length);
        if (BitConverter.IsLittleEndian)
            Array.Reverse(lengthBuffer);
            
        await stream.WriteAsync(lengthBuffer);
        await stream.WriteAsync(data);
        await stream.FlushAsync();
    }
    
    static async Task SendToClientAsync(ConnectedClient client, Message message)
    {
        if (client.Session == null || client.Stream == null) return;
        
        var plain = MessageSerializer.Serialize(message);
        var encrypted = client.Session.Encrypt(plain);
        
        lock (client.SendLock)
        {
            try
            {
                SendDataAsync(client.Stream, encrypted).Wait();
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Chyba při odesílání do {Uuid}", client.ClientUuid);
            }
        }
    }
    
    #endregion
}

// Connected client state
class ConnectedClient
{
    public required string ClientUuid { get; set; }
    public required string DisplayName { get; set; }
    public required string Endpoint { get; set; }
    public SessionCrypto? Session { get; set; }
    public NetworkStream? Stream { get; set; }
    public LobbyActor? Lobby { get; set; }
    public object SendLock { get; } = new();
}
