using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Firefly
{
    public partial class Firefly
    {
        #region Server Management
        static async Task StartServerAsync()
        {
            // Display Firefly ASCII art
            DisplayFireflyArt();
            
            // Set up server on specified port
            var listener = new TcpListener(IPAddress.Parse(bindAddress), serverPort);
            listener.Start();
            Console.WriteLine($"Server is listening on {bindAddress}, port {serverPort}");

            try
            {
                while (true)
                {
                    // Accept client connection
                    var client = await listener.AcceptTcpClientAsync();

                    // Configure client
                    client.ReceiveBufferSize = bufferSize;
                    client.SendBufferSize = bufferSize;
                    client.ReceiveTimeout = connectionTimeoutSeconds * 1000;
                    client.SendTimeout = connectionTimeoutSeconds * 1000;

                    Console.WriteLine("Client connected!");

                    // Handle client in a separate task
                    _ = HandleClientAsync(client);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Server error: {ex.Message}");
            }
            finally
            {
                listener.Stop();
            }
        }

        static async Task HandleClientAsync(TcpClient client)
        {
            string clientId = client.Client.RemoteEndPoint?.ToString() ?? Guid.NewGuid().ToString();
            
            try
            {
                using (client)
                using (var stream = client.GetStream())
                {
                    var buffer = new byte[bufferSize];
                    var lastActivity = DateTime.Now;
                    var commandQueue = new Queue<string>();
                    clientCommandQueues[clientId] = commandQueue;
                    clientPipelineMode[clientId] = false; // Initialize pipeline mode to false

                    while (client.Connected)
                    {
                        if ((DateTime.Now - lastActivity).TotalSeconds > connectionTimeoutSeconds)
                        {
                            Console.WriteLine($"Client {clientId} connection timed out due to inactivity");
                            break;
                        }

                        if (!stream.DataAvailable)
                        {
                            await Task.Delay(1);
                            continue;
                        }

                        int bytesRead = await stream.ReadAsync(buffer);
                        if (bytesRead <= 0)
                            break;

                        lastActivity = DateTime.Now;
                        var message = Encoding.UTF8.GetString(buffer, 0, bytesRead);

                        // Split message into individual commands
                        var commands = message.Split(["\r\n"], StringSplitOptions.RemoveEmptyEntries);
                        
                        foreach (var cmd in commands)
                        {
                            if (string.IsNullOrWhiteSpace(cmd)) continue;
                            
                            // Check if this is a QUIT command
                            if (cmd.Trim().Equals("QUIT", StringComparison.OrdinalIgnoreCase))
                            {
                                await ProcessCommandBatch(clientId, commandQueue, stream);
                                await stream.WriteAsync(Encoding.UTF8.GetBytes("+OK\r\n"));
                                Console.WriteLine($"Client {clientId} requested to quit");
                                return;
                            }

                            // Check if this is a PIPELINE command
                            if (cmd.Trim().Equals("PIPELINE", StringComparison.OrdinalIgnoreCase))
                            {
                                clientPipelineMode[clientId] = true;
                                await stream.WriteAsync(Encoding.UTF8.GetBytes("+OK\r\n"));
                                Console.WriteLine($"Client {clientId} entered pipeline mode");
                                continue;
                            }

                            // Check if this is an EXEC command (end pipeline mode)
                            if (cmd.Trim().Equals("EXEC", StringComparison.OrdinalIgnoreCase))
                            {
                                clientPipelineMode[clientId] = false;
                                await ProcessCommandBatch(clientId, commandQueue, stream);
                                await stream.WriteAsync(Encoding.UTF8.GetBytes("+OK\r\n"));
                                Console.WriteLine($"Client {clientId} executed pipeline");
                                continue;
                            }

                            commandQueue.Enqueue(cmd);

                            // Process commands if:
                            // 1. We've reached batch size
                            // 2. This is a non-pipeline command
                            // 3. We've reached max pipeline size
                            // 4. Client is not in pipeline mode
                            if (commandQueue.Count >= maxBatchSize || 
                                IsNonPipelineCommand(cmd) || 
                                commandQueue.Count >= maxPipelineSize ||
                                !clientPipelineMode[clientId])
                            {
                                await ProcessCommandBatch(clientId, commandQueue, stream);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                if (ex is SocketException socketEx &&
                    (socketEx.SocketErrorCode == SocketError.ConnectionReset ||
                     socketEx.SocketErrorCode == SocketError.ConnectionAborted))
                {
                    Console.WriteLine($"Client {clientId} disconnected unexpectedly");
                }
                else
                {
                    Console.WriteLine($"Error handling client {clientId}: {ex.Message}");
                }
            }
            finally
            {
                clientCommandQueues.TryRemove(clientId, out _);
                clientPipelineMode.TryRemove(clientId, out _);
                authenticatedClients.Remove(clientId);
                Console.WriteLine($"Client {clientId} disconnected.");
            }
        }

        private static bool IsNonPipelineCommand(string command)
        {
            var cmd = command.Trim().Split(' ')[0].ToUpperInvariant();
            return cmd == "AUTH" || cmd == "PING" || cmd == "QUIT" || cmd == "PIPELINE" || cmd == "EXEC";
        }

        private static string GetCommandType(string command)
        {
            var cmd = command.Trim().Split(' ')[0].ToUpperInvariant();
            
            // Categorize commands by data type they work with
            if (cmd == "SET" || cmd == "GET" || cmd == "DEL") 
                return "string";
            else if (cmd.StartsWith('L') || cmd == "RPUSH" || cmd == "RPOP") 
                return "list";
            else if (cmd.StartsWith('H')) 
                return "hash";
            else 
                return "other"; // Commands that don't fit clearly into a category
        }

        private static async Task ProcessCommandBatch(string clientId, Queue<string> commandQueue, NetworkStream stream)
        {
            if (commandQueue.Count == 0) return;

            var responses = new List<byte[]>();
            var batchSize = Math.Min(commandQueue.Count, maxBatchSize);
            var isPipelineMode = clientPipelineMode.TryGetValue(clientId, out bool pipelineMode) && pipelineMode;

            if (isPipelineMode)
            {
                // Group commands by type when in pipeline mode
                var stringCommands = new List<string>();
                var listCommands = new List<string>();
                var hashCommands = new List<string>();
                var otherCommands = new List<string>();
                
                // Extract all commands from the queue and categorize
                int commandsToProcess = Math.Min(commandQueue.Count, maxBatchSize);
                for (int i = 0; i < commandsToProcess; i++)
                {
                    if (commandQueue.Count == 0) break;
                    string command = commandQueue.Dequeue();
                    
                    string commandType = GetCommandType(command);
                    switch (commandType)
                    {
                        case "string":
                            stringCommands.Add(command);
                            break;
                        case "list":
                            listCommands.Add(command);
                            break;
                        case "hash":
                            hashCommands.Add(command);
                            break;
                        default:
                            otherCommands.Add(command);
                            break;
                    }
                }
                
                // Process each category in separate batches
                var allResults = new List<byte[]>();
                
                // Process string commands
                if (stringCommands.Count > 0)
                {
                    var stringTasks = stringCommands.Select(cmd => Task.Run(() => ProcessCommand(cmd, clientId))).ToList();
                    var stringResults = await Task.WhenAll(stringTasks);
                    allResults.AddRange(stringResults);
                    Console.WriteLine($"Processed {stringCommands.Count} string commands for client {clientId}");
                }
                
                // Process list commands
                if (listCommands.Count > 0)
                {
                    var listTasks = listCommands.Select(cmd => Task.Run(() => ProcessCommand(cmd, clientId))).ToList();
                    var listResults = await Task.WhenAll(listTasks);
                    allResults.AddRange(listResults);
                    Console.WriteLine($"Processed {listCommands.Count} list commands for client {clientId}");
                }
                
                // Process hash commands
                if (hashCommands.Count > 0)
                {
                    var hashTasks = hashCommands.Select(cmd => Task.Run(() => ProcessCommand(cmd, clientId))).ToList();
                    var hashResults = await Task.WhenAll(hashTasks);
                    allResults.AddRange(hashResults);
                    Console.WriteLine($"Processed {hashCommands.Count} hash commands for client {clientId}");
                }
                
                // Process other commands
                if (otherCommands.Count > 0)
                {
                    var otherTasks = otherCommands.Select(cmd => Task.Run(() => ProcessCommand(cmd, clientId))).ToList();
                    var otherResults = await Task.WhenAll(otherTasks);
                    allResults.AddRange(otherResults);
                    Console.WriteLine($"Processed {otherCommands.Count} other commands for client {clientId}");
                }
                
                responses = allResults;
            }
            else
            {
                // Non-pipeline mode - process in original order as before
                var tasks = new List<Task<byte[]>>();
                var commands = new List<string>();

                // Extract commands to process
                for (int i = 0; i < batchSize; i++)
                {
                    if (commandQueue.Count == 0) break;
                    commands.Add(commandQueue.Dequeue());
                }

                // Process commands in parallel
                foreach (var command in commands)
                {
                    tasks.Add(Task.Run(() => ProcessCommand(command, clientId)));
                }

                // Wait for all tasks to complete
                var results = await Task.WhenAll(tasks);
                responses.AddRange(results);
            }

            // Send all responses in a single write
            if (responses.Count > 0)
            {
                var combinedResponse = new byte[responses.Sum(r => r.Length)];
                var offset = 0;
                foreach (var response in responses)
                {
                    Buffer.BlockCopy(response, 0, combinedResponse, offset, response.Length);
                    offset += response.Length;
                }

                await stream.WriteAsync(combinedResponse);
                
                // Log batch processing information
                if (isPipelineMode)
                {
                    Console.WriteLine($"Processed grouped pipeline batch of {responses.Count} commands for client {clientId}");
                }
                else if (responses.Count > 1)
                {
                    Console.WriteLine($"Processed batch of {responses.Count} commands for client {clientId}");
                }
            }
        }

        /// <summary>
        /// Displays Firefly ASCII art in the console
        /// </summary>
        static void DisplayFireflyArt()
        {
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine(
"                   ,,_\n" +
"                zd$$??=\n" +
"              z$$P? F:`c,                _\n" +
"             d$$, `c'cc$$i           ,cd$?R\n" +
"            $$$$ cud$,?$$$i       ,=P'2?z '\n" +
"             $` ` ?$$$,?$$$.    ,-''`>, bzP\n" +
"              'cLdb,?$$,?$$$   ,h' 'I$'J$P\n" +
"           ... `?$$$,`$$,`$$h  $$PxrF'd$'\n" +
"         d$PP``?-,`?$$,?$h`$$,,$$'$F44'\n" +
"         ?,,_`=4c,?=,`?hu?$`?L4$'? '\n" +
"            ```?==``=-`` ```-`'_,,,,\n" +
"                    .ccu?m?e?JC,-,\"=?\n" +
"                            ```=='?'\n");

            Console.WriteLine(@"
    ███████╗██╗██████╗ ███████╗███████╗██╗  ██╗   ██╗
    ██╔════╝██║██╔══██╗██╔════╝██╔════╝██║  ╚██╗ ██╔╝
    █████╗  ██║██████╔╝█████╗  █████╗  ██║   ╚████╔╝ 
    ██╔══╝  ██║██╔══██╗██╔══╝  ██╔══╝  ██║    ╚██╔╝  
    ██║     ██║██║  ██║███████╗██║     ███████╗██║   
    ╚═╝     ╚═╝╚═╝  ╚═╝╚══════╝╚═╝     ╚══════╝╚═╝   
");
            Console.ResetColor();
        }
        #endregion
    }
}