using System.Text;

namespace Firefly
{
    public partial class Firefly
    {
        #region List Operations
        /// <summary>
        /// Handles the LPUSH command which adds an element to the left of a list.
        /// </summary>
        /// <param name="args">Command arguments in format: "key value"</param>
        /// <returns>The length of the list after the push operation</returns>
        static byte[] HandleLPushCommand(string args)
        {
            string[] parts = SplitRespectingQuotes(args);
            if (parts.Length < 2)
            {
                return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'lpush' command\r\n");
            }

            string key = parts[0];
            string value = parts[1];

            try
            {
                // Use helper function to safely modify the list with write lock
                int newLength = 0;
                ListStoreWithWriteLock(key, list =>
                {
                    list.Insert(0, value);
                    newLength = list.Count;
                });

                return Encoding.UTF8.GetBytes($":{newLength}\r\n");
            }
            catch (InvalidOperationException ex)
            {
                // Handle the case where the key already exists with a different type
                string? existingType = GetKeyType(key);
                if (existingType != null)
                {
                    return Encoding.UTF8.GetBytes($"-ERR key '{key}' already exists as type '{existingType}'\r\n");
                }
                return Encoding.UTF8.GetBytes($"-ERR {ex.Message}\r\n");
            }
        }

        /// <summary>
        /// Handles the RPUSH command which adds values to the tail of a list.
        /// </summary>
        /// <param name="args">Command arguments in format: "key value1 [value2 ...]"</param>
        /// <returns>Response indicating the new length of the list</returns>
        static byte[] HandleRPushCommand(string args)
        {
            string[] parts = SplitRespectingQuotes(args);
            if (parts.Length < 2)
            {
                return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'rpush' command\r\n");
            }

            string key = parts[0];
            List<string> result = [];
            
            // Use helper function to safely modify the list with write lock
            ListStoreWithWriteLock(key, list =>
            {
                // Add all values to the end of the list
                for (int i = 1; i < parts.Length; i++)
                {
                    list.Add(parts[i]);
                }
                result.Add(list.Count.ToString());
            });

            return Encoding.UTF8.GetBytes($":{result[0]}\r\n");
        }

        /// <summary>
        /// Handles the LPOP command which removes and returns the first element of a list.
        /// </summary>
        /// <param name="args">Command arguments containing the key</param>
        /// <returns>The popped value or nil if the list is empty</returns>
        static byte[] HandleLPopCommand(string args)
        {
            if (string.IsNullOrWhiteSpace(args))
            {
                return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'lpop' command\r\n");
            }

            string key = args.Trim();
            List<string> result = [];
            
            // Use helper function to safely modify the list with write lock
            ListStoreWithWriteLock(key, list =>
            {
                if (list.Count > 0)
                {
                    // Remove and store the first element
                    string value = list[0];
                    list.RemoveAt(0);
                    result.Add(value);

                    // Clean up empty lists
                    if (list.Count == 0)
                    {
                        ListStoreRemove(key);
                    }
                }
            });

            return result.Count > 0 
                ? Encoding.UTF8.GetBytes($"+{result[0]}\r\n")
                : Encoding.UTF8.GetBytes("$-1\r\n");
        }

        /// <summary>
        /// Handles the RPOP command which removes and returns the last element of a list.
        /// </summary>
        /// <param name="args">Command arguments containing the key</param>
        /// <returns>The popped value or nil if the list is empty</returns>
        static byte[] HandleRPopCommand(string args)
        {
            if (string.IsNullOrWhiteSpace(args))
            {
                return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'rpop' command\r\n");
            }

            string key = args.Trim();
            List<string> result = [];
            
            // Use helper function to safely modify the list with write lock
            ListStoreWithWriteLock(key, list =>
            {
                if (list.Count > 0)
                {
                    // Remove and store the last element
                    int lastIndex = list.Count - 1;
                    string value = list[lastIndex];
                    list.RemoveAt(lastIndex);
                    result.Add(value);

                    // Clean up empty lists
                    if (list.Count == 0)
                    {
                        ListStoreRemove(key);
                    }
                }
            });

            return result.Count > 0 
                ? Encoding.UTF8.GetBytes($"+{result[0]}\r\n")
                : Encoding.UTF8.GetBytes("$-1\r\n");
        }

        /// <summary>
        /// Handles the LLEN command which returns the length of a list.
        /// </summary>
        /// <param name="args">Command arguments containing the key</param>
        /// <returns>The length of the list or 0 if the key does not exist</returns>
        static byte[] HandleLLengthCommand(string args)
        {
            if (string.IsNullOrWhiteSpace(args))
            {
                return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'llen' command\r\n");
            }

            string key = args.Trim();
            
            // Change to use the generic method correctly by returning the length
            int length = ListStoreWithReadLock(key, list => list.Count);

            return Encoding.UTF8.GetBytes($":{length}\r\n");
        }

        /// <summary>
        /// Handles the LRANGE command which returns a range of elements from a list.
        /// </summary>
        /// <param name="args">Command arguments in format: "key start stop"</param>
        /// <returns>Array of elements in the specified range</returns>
        static byte[] HandleLRangeCommand(string args)
        {
            string[] parts = SplitRespectingQuotes(args);
            if (parts.Length < 3)
            {
                return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'lrange' command\r\n");
            }

            string key = parts[0];
            if (!int.TryParse(parts[1], out int start) || !int.TryParse(parts[2], out int stop))
            {
                return Encoding.UTF8.GetBytes("-ERR value is not an integer or out of range\r\n");
            }

            // Use helper function to safely read the list with read lock
            return ListStoreWithReadLock(key, list =>
            {
                if (list.Count == 0)
                {
                    return Encoding.UTF8.GetBytes("*\r\n");
                }

                // Handle negative indices (counting from the end)
                if (start < 0) start = list.Count + start;
                if (stop < 0) stop = list.Count + stop;

                // Ensure indices are within bounds
                start = Math.Max(start, 0);
                stop = Math.Min(stop, list.Count - 1);

                if (start > stop)
                {
                    return Encoding.UTF8.GetBytes("*\r\n");
                }

                // Build response with all elements in range
                StringBuilder response = new();
                response.Append("*\r\n");

                for (int i = start; i <= stop; i++)
                {
                    response.Append($"+{list[i]}\r\n");
                }

                return Encoding.UTF8.GetBytes(response.ToString());
            }) ?? Encoding.UTF8.GetBytes("*\r\n");
        }

        /// <summary>
        /// Handles the LINDEX command which returns an element from a list by its index.
        /// </summary>
        /// <param name="args">Command arguments in format: "key index"</param>
        /// <returns>The element at the specified index or nil if not found</returns>
        static byte[] HandleLIndexCommand(string args)
        {
            string[] parts = SplitRespectingQuotes(args);
            if (parts.Length < 2)
            {
                return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'lindex' command\r\n");
            }

            string key = parts[0];
            if (!int.TryParse(parts[1], out int index))
            {
                return Encoding.UTF8.GetBytes("-ERR value is not an integer or out of range\r\n");
            }

            // Use helper function to safely read the list with read lock
            return ListStoreWithReadLock(key, list =>
            {
                if (list.Count == 0)
                {
                    return Encoding.UTF8.GetBytes("$-1\r\n");
                }

                // Handle negative index (counting from the end)
                if (index < 0)
                {
                    index = list.Count + index;
                }

                // Check if index is within bounds
                if (index < 0 || index >= list.Count)
                {
                    return Encoding.UTF8.GetBytes("$-1\r\n");
                }

                return Encoding.UTF8.GetBytes($"+{list[index]}\r\n");
            }) ?? Encoding.UTF8.GetBytes("$-1\r\n");
        }

        /// <summary>
        /// Handles the LSET command which sets the value of an element in a list by its index.
        /// </summary>
        /// <param name="args">Command arguments in format: "key index value"</param>
        /// <returns>OK on success, error if index is out of range</returns>
        static byte[] HandleLSetCommand(string args)
        {
            string[] parts = SplitRespectingQuotes(args);
            if (parts.Length < 3)
            {
                return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'lset' command\r\n");
            }

            string key = parts[0];
            if (!int.TryParse(parts[1], out int index))
            {
                return Encoding.UTF8.GetBytes("-ERR value is not an integer or out of range\r\n");
            }
            string value = parts[2];

            bool success = false;
            // Use helper function to safely modify the list with write lock
            ListStoreWithWriteLock(key, list =>
            {
                if (list.Count == 0)
                {
                    return;
                }

                // Handle negative index (counting from the end)
                if (index < 0)
                {
                    index = list.Count + index;
                }

                // Check if index is within bounds
                if (index < 0 || index >= list.Count)
                {
                    return;
                }

                list[index] = value;
                success = true;
            });

            return success 
                ? Encoding.UTF8.GetBytes("+OK\r\n")
                : Encoding.UTF8.GetBytes("-ERR index out of range\r\n");
        }

        /// <summary>
        /// Handles the LPOS command which returns the position of an element in a list.
        /// </summary>
        /// <param name="args">Command arguments in format: "key element [RANK rank] [MAXLEN len]"</param>
        /// <returns>The position of the element or nil if not found</returns>
        static byte[] HandleLPosCommand(string args)
        {
            string[] parts = SplitRespectingQuotes(args);
            if (parts.Length < 2)
            {
                return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'lpos' command\r\n");
            }

            string key = parts[0];
            string element = parts[1];
            
            // Optional parameters
            int rank = 1;  // Default to first occurrence
            int maxlen = 0; // 0 means no limit
            
            // Parse optional parameters
            for (int i = 2; i < parts.Length; i++)
            {
                if (parts[i].Equals("RANK", StringComparison.OrdinalIgnoreCase) && i + 1 < parts.Length)
                {
                    if (int.TryParse(parts[++i], out int parsedRank))
                    {
                        rank = parsedRank;
                    }
                }
                else if (parts[i].Equals("MAXLEN", StringComparison.OrdinalIgnoreCase) && i + 1 < parts.Length)
                {
                    if (int.TryParse(parts[++i], out int parsedMaxlen))
                    {
                        maxlen = parsedMaxlen;
                    }
                }
            }

            int shardIndex = GetShardIndex(key);
            listStoreLocks[shardIndex].EnterReadLock();
            try
            {
                if (!listStoreShards[shardIndex].TryGetValue(key, out List<string>? list) || list == null)
                {
                    return Encoding.UTF8.GetBytes("$-1\r\n"); // Key doesn't exist
                }

                int found = 0;
                for (int i = 0; i < list.Count; i++)
                {
                    if (maxlen > 0 && i >= maxlen)
                    {
                        break;
                    }

                    if (list[i] == element)
                    {
                        found++;
                        if (found == Math.Abs(rank))
                        {
                            return Encoding.UTF8.GetBytes($":{i}\r\n");
                        }
                    }
                }

                return Encoding.UTF8.GetBytes("$-1\r\n"); // Element not found
            }
            finally
            {
                listStoreLocks[shardIndex].ExitReadLock();
            }
        }


        /// <summary>
        /// Handles the LTRIM command which trims a list to the specified range.
        /// </summary>
        /// <param name="args">Command arguments in format: "key start stop"</param>
        /// <returns>OK on success, error if arguments are invalid</returns>
        static byte[] HandleLTrimCommand(string args)
        {
            string[] parts = SplitRespectingQuotes(args);
            if (parts.Length < 3)
            {
                return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'ltrim' command\r\n");
            }

            string key = parts[0];
            if (!int.TryParse(parts[1], out int start) || !int.TryParse(parts[2], out int stop))
            {
                return Encoding.UTF8.GetBytes("-ERR value is not an integer or out of range\r\n");
            }

            int shardIndex = GetShardIndex(key);
            listStoreLocks[shardIndex].EnterWriteLock();
            try
            {
                if (!listStoreShards[shardIndex].TryGetValue(key, out List<string>? list) || list == null)
                {
                    return Encoding.UTF8.GetBytes("+OK\r\n"); // Non-existent key is treated as empty list
                }

                // Handle negative indices
                if (start < 0) start = list.Count + start;
                if (stop < 0) stop = list.Count + stop;

                // Normalize boundaries
                start = Math.Max(start, 0);
                stop = Math.Min(stop, list.Count - 1);

                if (start > stop || start >= list.Count)
                {
                    // Clear the list if range is invalid
                    list.Clear();
                    listStoreShards[shardIndex].TryRemove(key, out _);
                }
                else
                {
                    // Calculate the new range
                    int newLength = stop - start + 1;
                    if (start > 0 || stop < list.Count - 1)
                    {
                        var trimmed = list.GetRange(start, newLength);
                        list.Clear();
                        list.AddRange(trimmed);
                    }
                }

                return Encoding.UTF8.GetBytes("+OK\r\n");
            }
            finally
            {
                listStoreLocks[shardIndex].ExitWriteLock();
            }
        }

        /// <summary>
        /// Handles the LREM command which removes elements equal to the given value from a list.
        /// </summary>
        /// <param name="args">Command arguments in format: "key count element"</param>
        /// <returns>The number of removed elements</returns>
        static byte[] HandleLRemCommand(string args)
        {
            string[] parts = SplitRespectingQuotes(args);
            if (parts.Length < 3)
            {
                return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'lrem' command\r\n");
            }

            string key = parts[0];
            if (!int.TryParse(parts[1], out int count))
            {
                return Encoding.UTF8.GetBytes("-ERR value is not an integer or out of range\r\n");
            }
            string element = parts[2];

            int shardIndex = GetShardIndex(key);
            listStoreLocks[shardIndex].EnterWriteLock();
            try
            {
                if (!listStoreShards[shardIndex].TryGetValue(key, out List<string>? list) || list == null)
                {
                    return Encoding.UTF8.GetBytes(":0\r\n"); // Key doesn't exist
                }

                int removed = 0;
                if (count > 0)
                {
                    // Remove count occurrences from head to tail
                    for (int i = 0; i < list.Count && removed < count; i++)
                    {
                        if (list[i] == element)
                        {
                            list.RemoveAt(i);
                            removed++;
                            i--; // Adjust index after removal
                        }
                    }
                }
                else if (count < 0)
                {
                    // Remove |count| occurrences from tail to head
                    for (int i = list.Count - 1; i >= 0 && removed < -count; i--)
                    {
                        if (list[i] == element)
                        {
                            list.RemoveAt(i);
                            removed++;
                        }
                    }
                }
                else // count == 0
                {
                    // Remove all occurrences
                    removed = list.RemoveAll(x => x == element);
                }

                // Remove the key if the list is empty
                if (list.Count == 0)
                {
                    listStoreShards[shardIndex].TryRemove(key, out _);
                }

                return Encoding.UTF8.GetBytes($":{removed}\r\n");
            }
            finally
            {
                listStoreLocks[shardIndex].ExitWriteLock();
            }
        }

        #region List Store Helpers
        /// <summary>
        /// Gets or creates a list for a given key.
        /// </summary>
        /// <param name="key">The key to get or create the list for</param>
        /// <returns>The list</returns>
        private static List<string> GetOrCreateList(string key)
        {
            int shardIndex = GetShardIndex(key);
            
            // If the key doesn't exist in this shard, check if it exists in any other store
            if (!listStoreShards[shardIndex].ContainsKey(key))
            {
                // Check if key exists in any other store
                if (!EnsureKeyDoesNotExist(key, "list"))
                {
                    throw new InvalidOperationException($"Key '{key}' already exists with a different type");
                }

                var newList = new List<string>();
                if (listStoreShards[shardIndex].TryAdd(key, newList))
                {
                    MarkDataAsModified();
                    return newList;
                }
            }
            
            return listStoreShards[shardIndex].GetOrAdd(key, _ => []);
        }

        /// <summary>
        /// Checks if a list exists for a given key.
        /// </summary>
        /// <param name="key">The key to check the list for</param>
        /// <returns>True if the list exists, false otherwise</returns>
        private static bool ListStoreExists(string key)
        {
            int shardIndex = GetShardIndex(key);
            return listStoreShards[shardIndex].ContainsKey(key);
        }

        /// <summary>
        /// Gets a list for a given key.
        /// </summary>
        /// <param name="key">The key to get the list for</param>
        /// <param name="value">The list</param>
        /// <returns>True if the list was found, false otherwise</returns>
        private static bool ListStoreGet(string key, out List<string>? value)
        {
            int shardIndex = GetShardIndex(key);
            return listStoreShards[shardIndex].TryGetValue(key, out value);
        }

        /// <summary>
        /// Removes a list for a given key.
        /// </summary>
        /// <param name="key">The key to remove the list for</param>
        /// <returns>True if the list was removed, false otherwise</returns>
        private static bool ListStoreRemove(string key)
        {
            int shardIndex = GetShardIndex(key);
            listStoreLocks[shardIndex].EnterWriteLock();
            try
            {
                bool result = listStoreShards[shardIndex].TryRemove(key, out _);

                if (result)
                    MarkDataAsModified();

                return result;
            }
            finally
            {
                listStoreLocks[shardIndex].ExitWriteLock();
            }
        }

        /// <summary>
        /// Executes an action with a write lock on a list for a given key.
        /// </summary>
        /// <param name="key">The key to execute the action on</param>
        /// <param name="action">The action to execute</param>
        private static void ListStoreWithWriteLock(string key, Action<List<string>> action)
        {
            int shardIndex = GetShardIndex(key);
            listStoreLocks[shardIndex].EnterWriteLock();
            try
            {
                var list = GetOrCreateList(key);
                action(list);

                MarkDataAsModified();
            }
            finally
            {
                listStoreLocks[shardIndex].ExitWriteLock();
            }
        }

        /// <summary>
        /// Executes an action with a read lock on a list for a given key.
        /// </summary>
        /// <typeparam name="T">The type of the result</typeparam>
        /// <param name="key">The key to execute the action on</param>
        /// <param name="action">The action to execute</param>
        private static T ListStoreWithReadLock<T>(string key, Func<List<string>, T> action)
        {
            int shardIndex = GetShardIndex(key);
            listStoreLocks[shardIndex].EnterReadLock();
            try
            {
                if (ListStoreGet(key, out List<string>? list) && list != null)
                {
                    return action(list);
                }
                return default!;
            }
            finally
            {
                listStoreLocks[shardIndex].ExitReadLock();
            }
        }
        #endregion
        #endregion
    }
}