using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AdvChkSys.Interfaces;

namespace AdvChkSys.Threading
{
    /// <summary>
    /// Provides a queue for sequential operations on chunks.
    /// </summary>
    public class ChunkOperationQueue
    {
        // Queue of pending operations for each chunk
        private readonly ConcurrentDictionary<IChunk, Queue<(Func<Task> Operation, TaskCompletionSource<object?> Completion)>> _pendingOperations = new();

        // Currently active operations
        private readonly ConcurrentDictionary<IChunk, Task> _activeOperations = new();

        // Semaphore to limit concurrent operations
        private readonly SemaphoreSlim _semaphore;

        // Cancellation for shutdown
        private readonly CancellationTokenSource _shutdownCts = new();

        /// <summary>
        /// Initializes a new instance of the ChunkOperationQueue class.
        /// </summary>
        /// <param name="maxConcurrentOperations">Maximum number of concurrent operations</param>
        public ChunkOperationQueue(int maxConcurrentOperations = 4)
        {
            _semaphore = new SemaphoreSlim(maxConcurrentOperations, maxConcurrentOperations);
        }

        /// <summary>
        /// Enqueues an operation to be performed on a chunk.
        /// </summary>
        /// <param name="chunk">The chunk to operate on</param>
        /// <param name="operation">The operation to perform</param>
        /// <returns>A task that completes when the operation is done</returns>
        public Task EnqueueOperationAsync(IChunk chunk, Func<Task> operation)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            if (operation == null)
                throw new ArgumentNullException(nameof(operation));

            // Create a completion source for this operation
            var completion = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);

            // Get or create the queue for this chunk
            var queue = _pendingOperations.GetOrAdd(chunk, _ => new Queue<(Func<Task>, TaskCompletionSource<object?>)>());

            // Add the operation to the queue
            lock (queue)
            {
                queue.Enqueue((operation, completion));

                // If this is the only operation, start processing
                if (queue.Count == 1 && !_activeOperations.ContainsKey(chunk))
                {
                    StartProcessingChunkOperations(chunk);
                }
            }

            return completion.Task;
        }

        /// <summary>
        /// Starts processing operations for a chunk.
        /// </summary>
        private async void StartProcessingChunkOperations(IChunk chunk)
        {
            // Wait for a semaphore slot
            await _semaphore.WaitAsync(_shutdownCts.Token).ConfigureAwait(false);

            try
            {
                // Process operations until the queue is empty
                while (!_shutdownCts.IsCancellationRequested)
                {
                    // Get the next operation
                    (Func<Task> operation, TaskCompletionSource<object?> completion) nextOperation;

                    var queue = _pendingOperations.GetOrAdd(chunk, _ => new Queue<(Func<Task>, TaskCompletionSource<object?>)>());

                    lock (queue)
                    {
                        if (queue.Count == 0)
                        {
                            // No more operations, remove the active task
                            _activeOperations.TryRemove(chunk, out _);
                            break;
                        }

                        nextOperation = queue.Peek();
                    }

                    // Execute the operation
                    var task = ExecuteOperationAsync(chunk, nextOperation.operation, nextOperation.completion);
                    _activeOperations[chunk] = task;

                    // Wait for completion
                    await task.ConfigureAwait(false);

                    // Remove the completed operation
                    lock (queue)
                    {
                        queue.Dequeue();
                    }
                }
            }
            catch (OperationCanceledException)
            {
                // Shutdown requested
            }
            finally
            {
                // Release the semaphore
                _semaphore.Release();
            }
        }

        /// <summary>
        /// Executes an operation and completes the task.
        /// </summary>
        private async Task ExecuteOperationAsync(IChunk chunk, Func<Task> operation, TaskCompletionSource<object?> completion)
        {
            try
            {
                // Track the operation for diagnostics
                var operationId = ChunkThreadingDiagnostics.TrackOperationStart("ChunkOperation", chunk);

                try
                {
                    // Execute the operation
                    await operation().ConfigureAwait(false);

                    // Complete the task
                    completion.TrySetResult(null);
                }
                catch (Exception ex)
                {
                    // Set the exception
                    completion.TrySetException(ex);

                    // Log the error
                    ChunkThreadingDiagnostics.LogEvent("OperationError", $"Error in chunk operation: {ex.Message}");
                }
                finally
                {
                    // End tracking
                    ChunkThreadingDiagnostics.TrackOperationEnd(operationId);
                }
            }
            catch (Exception ex)
            {
                // This should never happen, but just in case
                completion.TrySetException(ex);

                // Log the error
                ChunkThreadingDiagnostics.LogEvent("CriticalError", $"Critical error in operation execution: {ex.Message}");
            }
        }

        /// <summary>
        /// Gets the number of pending operations for a chunk.
        /// </summary>
        public int GetPendingOperationCount(IChunk chunk)
        {
            if (_pendingOperations.TryGetValue(chunk, out var queue))
            {
                lock (queue)
                {
                    return queue.Count;
                }
            }

            return 0;
        }

        /// <summary>
        /// Gets the total number of pending operations.
        /// </summary>
        public int PendingOperationCount
        {
            get
            {
                int count = 0;
                foreach (var queue in _pendingOperations.Values)
                {
                    lock (queue)
                    {
                        count += queue.Count;
                    }
                }
                return count;
            }
        }

        /// <summary>
        /// Gets the number of active operations.
        /// </summary>
        public int ActiveOperationCount => _activeOperations.Count;

        /// <summary>
        /// Cancels all pending operations for a chunk.
        /// </summary>
        /// <param name="chunk">The chunk to cancel operations for</param>
        /// <returns>The number of operations canceled</returns>
        public int CancelOperations(IChunk chunk)
        {
            if (_pendingOperations.TryGetValue(chunk, out var queue))
            {
                lock (queue)
                {
                    int count = queue.Count;

                    // Cancel all pending operations
                    while (queue.Count > 0)
                    {
                        var (Operation, Completion) = queue.Dequeue();
                        Completion.TrySetCanceled();
                    }

                    return count;
                }
            }

            return 0;
        }

        /// <summary>
        /// Cancels all pending operations.
        /// </summary>
        /// <returns>The number of operations canceled</returns>
        public int CancelAllOperations()
        {
            int count = 0;

            foreach (var chunk in _pendingOperations.Keys)
            {
                count += CancelOperations(chunk);
            }

            return count;
        }

        /// <summary>
        /// Shuts down the operation queue.
        /// </summary>
        public async Task ShutdownAsync()
        {
            // Cancel all operations
            _shutdownCts.Cancel();

            // Cancel all pending operations
            CancelAllOperations();

            // Wait for active operations to complete
            var tasks = new List<Task>(_activeOperations.Values);
            if (tasks.Count > 0)
            {
                await Task.WhenAll(tasks).ConfigureAwait(false);
            }

            // Dispose resources
            _shutdownCts.Dispose();
            _semaphore.Dispose();
        }
    }
}