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

namespace FireflyClient
{
    /// <summary>
    /// FireflyClient provides a clean API for interacting with the Firefly server.
    /// This can be used as a library for integrating with other applications.
    /// </summary>
    public partial class FireflyClient : IDisposable
    {
        private readonly TcpClient _client;
        private readonly NetworkStream _stream;
        private readonly byte[] _responseBuffer = new byte[1024 * 1024]; // 1MB buffer for responses
        private int _maxBatchSize = 1000;
        private readonly int _maxPipelineSize = 10000;
        private readonly Queue<string> _commandQueue = new();
        private bool _isPipelineMode;
        private bool _isAuthenticated;
        
        // Static instance for native interop
        // private static FireflyClient? _instance;
        
        /// <summary>
        /// Creates a new FireflyClient and connects to the specified server
        /// </summary>
        public FireflyClient(string host = "127.0.0.1", int port = 6379)
        {
            _client = new TcpClient();
            _client.Connect(host, port);
            _stream = _client.GetStream();
        }
        
        /// <summary>
        /// Creates a new FireflyClient, connects to the server and authenticates
        /// </summary>
        public FireflyClient(string host, int port, string password) : this(host, port)
        {
            if (!string.IsNullOrEmpty(password))
            {
                Authenticate(password);
            }
        }
        
        /// <summary>
        /// Authenticates with the server using the provided password
        /// </summary>
        public bool Authenticate(string password)
        {
            var response = ExecuteCommand("AUTH", password);
            _isAuthenticated = response.StartsWith("+OK");
            return _isAuthenticated;
        }
        
        /// <summary>
        /// Executes a raw command with any number of arguments
        /// </summary>
        public string ExecuteCommand(string command, params string[] args)
        {
            if (!_client.Connected)
            {
                throw new InvalidOperationException("Not connected to server");
            }

            // Build the command string
            var fullCommand = $"{command.ToUpperInvariant()}{string.Join("", args.Select(arg => $" {QuoteIfNeeded(arg)}"))}";

            // Handle special commands that should not be pipelined
            if (command.Equals("AUTH", StringComparison.OrdinalIgnoreCase) ||
                command.Equals("PING", StringComparison.OrdinalIgnoreCase) ||
                command.Equals("QUIT", StringComparison.OrdinalIgnoreCase))
            {
                return SendCommandInternal(fullCommand);
            }

            // Add command to queue
            _commandQueue.Enqueue(fullCommand);

            // Process queue if we've reached batch size or if pipeline mode is disabled
            if (_commandQueue.Count >= _maxBatchSize || !_isPipelineMode)
            {
                return ProcessCommandQueue();
            }

            return "+QUEUED\r\n";
        }
        
        // Helper method to quote a string if needed
        private static string QuoteIfNeeded(string input)
        {
            if (input.Contains(' ') || input.Contains('\''))
            {
                // If the input already has quotes, don't add more
                if (input.StartsWith('"') && input.EndsWith('"'))
                {
                    return input;
                }
                
                // Escape any quotes in the input
                return $"\"{input.Replace("\"", "\\\"")}\"";
            }
            return input;
        }
        
        // Private method to send a command to the server
        private string SendCommandInternal(string command)
        {
            // Add CRLF if not present
            if (!command.EndsWith("\r\n"))
            {
                command += "\r\n";
            }
            
            var commandBytes = Encoding.UTF8.GetBytes(command);
            _stream.Write(commandBytes, 0, commandBytes.Length);
            
            // Read response
            var bytesRead = _stream.Read(_responseBuffer, 0, _responseBuffer.Length);
            return bytesRead > 0 ? Encoding.UTF8.GetString(_responseBuffer, 0, bytesRead) : string.Empty;
        }
        
        private string ProcessCommandQueue()
        {
            if (_commandQueue.Count == 0) return string.Empty;

            // Build the pipeline command string
            var pipelineCommand = string.Join("\r\n", _commandQueue) + "\r\n";
            _commandQueue.Clear();

            return SendCommandInternal(pipelineCommand);
        }

        /// <summary>
        /// Enables or disables pipeline mode
        /// </summary>
        private void SetPipelineMode(bool enabled)
        {
            _isPipelineMode = enabled;
            if (!enabled)
            {
                ProcessCommandQueue();
            }
        }

        /// <summary>
        /// Flushes any queued commands in pipeline mode
        /// </summary>
        private string FlushPipeline()
        {
            return ProcessCommandQueue();
        }

        /// <summary>
        /// Sets the maximum number of commands to batch before sending
        /// </summary>
        private void SetBatchSize(int size)
        {
            if (size <= 0)
            {
                throw new ArgumentException("Batch size must be greater than 0");
            }
            _maxBatchSize = Math.Min(size, _maxPipelineSize);
        }

        /// <summary>
        /// Gets the current number of queued commands
        /// </summary>
        private int QueuedCommandCount => _commandQueue.Count;

        /// <summary>
        /// Gets whether pipeline mode is enabled
        /// </summary>
        private bool IsPipelineMode => _isPipelineMode;

        /// <summary>
        /// Gets the maximum batch size
        /// </summary>
        private int MaxBatchSize => _maxBatchSize;

        /// <summary>
        /// Gets whether the client is connected to the server
        /// </summary>
        public bool IsConnected => _client.Connected;

        /// <summary>
        /// Gets whether the client is authenticated
        /// </summary>
        public bool IsAuthenticated => _isAuthenticated;
        
        /// <summary>
        /// Gets all keys matching the specified pattern
        /// </summary>
        /// <param name="pattern">Pattern to match against keys. Use * for wildcard matches.</param>
        /// <returns>List of matching keys, or empty list if none found or on error</returns>
        private List<string> Keys(string pattern = "*")
        {
            try
            {
                var response = ExecuteCommand("KEYS", pattern);
                if (string.IsNullOrEmpty(response) || !response.StartsWith('+'))
                {
                    return [];
                }

                // Remove the '+' prefix and split by newlines
                var keysStr = response[1..].Trim();
                return string.IsNullOrEmpty(keysStr) 
                    ? [] 
                    : [.. keysStr.Split('\n')];
            }
            catch
            {
                return [];
            }
        }
        
        /// <summary>
        /// Parses an array response from the server
        /// </summary>
        private static List<string> ParseArrayResponse(string response)
        {
            var result = new List<string>();
            
            // If not an array response, return empty list
            if (!response.StartsWith('*'))
            {
                return result;
            }
            
            string[] parts = response.Split("\r\n");
            
            // Skip the first line which just contains the * prefix
            for (var i = 1; i < parts.Length; i++)
            {
                if (!string.IsNullOrEmpty(parts[i]) && (parts[i].StartsWith('+') || parts[i].StartsWith('$')))
                {
                    // Extract the value part (after the + or $ prefix)
                    result.Add(parts[i][1..]);
                }
            }
            
            return result;
        }
        
        /// <inheritdoc/>
        public void Dispose()
        {
            try
            {
                // Try to gracefully disconnect by sending QUIT command
                if (_client.Connected)
                {
                    try
                    {
                        ExecuteCommand("QUIT");
                    }
                    catch
                    {
                        // Ignore exceptions during QUIT command
                    }
                }
            }
            finally
            {
                _stream.Dispose();
                _client.Dispose();
                GC.SuppressFinalize(this);
            }
        }
    }
}