#nullable enable
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AdvChkSys.Interfaces;
using AdvChkSys.Threading;

namespace AdvChkSys.Loading
{
    /// <summary>
    /// Manages prioritized loading of chunks.
    /// </summary>
    public class ChunkLoadingPriority
    {
        /// <summary>
        /// Priority levels for chunk loading.
        /// </summary>
        public enum Priority
        {
            /// <summary>
            /// Immediate priority - process as soon as possible
            /// </summary>
            Immediate,

            /// <summary>
            /// High priority - process after immediate requests
            /// </summary>
            High,

            /// <summary>
            /// Normal priority - standard processing order
            /// </summary>
            Normal,

            /// <summary>
            /// Low priority - process after normal requests
            /// </summary>
            Low,

            /// <summary>
            /// Background priority - process when system is idle
            /// </summary>
            Background
        }

        /// <summary>
        /// Represents a chunk loading request with priority.
        /// </summary>
        public class ChunkLoadRequest
        {
            /// <summary>
            /// The X coordinate of the chunk.
            /// </summary>
            public int X { get; }

            /// <summary>
            /// The Y coordinate of the chunk.
            /// </summary>
            public int Y { get; }

            /// <summary>
            /// The Z coordinate of the chunk (optional, for 3D chunks).
            /// </summary>
            public int Z { get; }

            /// <summary>
            /// The priority of this loading request.
            /// </summary>
            public Priority LoadPriority { get; }

            /// <summary>
            /// The width of the chunk.
            /// </summary>
            public int Width { get; }

            /// <summary>
            /// The height of the chunk.
            /// </summary>
            public int Height { get; }

            /// <summary>
            /// The depth of the chunk (for 3D chunks).
            /// </summary>
            public int Depth { get; }

            /// <summary>
            /// Timestamp when the request was created.
            /// </summary>
            public DateTime Timestamp { get; }

            /// <summary>
            /// Unique identifier for this request.
            /// </summary>
            public Guid RequestId { get; }

            /// <summary>
            /// Creates a new 2D chunk loading request.
            /// </summary>
            public ChunkLoadRequest(int x, int y, int width, int height, Priority priority)
            {
                X = x;
                Y = y;
                Z = 0;
                Width = width;
                Height = height;
                Depth = 1;
                LoadPriority = priority;
                Timestamp = DateTime.UtcNow;
                RequestId = Guid.NewGuid();
            }

            /// <summary>
            /// Creates a new 3D chunk loading request.
            /// </summary>
            public ChunkLoadRequest(int x, int y, int z, int width, int height, int depth, Priority priority)
            {
                X = x;
                Y = y;
                Z = z;
                Width = width;
                Height = height;
                Depth = depth;
                LoadPriority = priority;
                Timestamp = DateTime.UtcNow;
                RequestId = Guid.NewGuid();
            }
        }

        // Priority queues for each priority level
        private readonly Dictionary<Priority, Queue<ChunkLoadRequest>> _requestQueues = new();

        // Lookup for fast cancellation and status checks
        private readonly Dictionary<Guid, ChunkLoadRequest> _requestLookup = new();

        // Active tasks to prevent duplicate loading
        private readonly Dictionary<(int X, int Y, int Z), Task> _activeTasks = new();

        // Synchronization
        private readonly SemaphoreSlim _queueSemaphore = new(1, 1);
        private readonly SemaphoreSlim _processSemaphore;
        private readonly CancellationTokenSource _cancellationSource = new();

        // Processing state
        private bool _isProcessing;
        private Task? _processingTask;

        // Configuration
        private readonly int _maxConcurrentLoads;
        private readonly TimeSpan _requestTimeout;

        /// <summary>
        /// Event raised when a chunk load request is completed.
        /// </summary>
        public event EventHandler<ChunkLoadRequest>? RequestCompleted;

        /// <summary>
        /// Event raised when a chunk load request fails.
        /// </summary>
        public event EventHandler<(ChunkLoadRequest Request, Exception Exception)>? RequestFailed;

        /// <summary>
        /// Initializes a new instance of the ChunkLoadingPriority class.
        /// </summary>
        /// <param name="maxConcurrentLoads">Maximum number of chunks to load concurrently</param>
        /// <param name="requestTimeoutSeconds">Timeout in seconds for chunk load requests</param>
        public ChunkLoadingPriority(int maxConcurrentLoads = 4, int requestTimeoutSeconds = 30)
        {
            _maxConcurrentLoads = maxConcurrentLoads;
            _requestTimeout = TimeSpan.FromSeconds(requestTimeoutSeconds);
            _processSemaphore = new SemaphoreSlim(maxConcurrentLoads, maxConcurrentLoads);

            // Initialize queues for each priority level
            foreach (Priority priority in Enum.GetValues(typeof(Priority)))
            {
                _requestQueues[priority] = new Queue<ChunkLoadRequest>();
            }
        }

        /// <summary>
        /// Enqueues a chunk load request with the specified priority.
        /// </summary>
        /// <returns>The request object that can be used to track or cancel the request</returns>
        public ChunkLoadRequest EnqueueRequest(int x, int y, int width, int height, Priority priority = Priority.Normal)
        {
            var request = new ChunkLoadRequest(x, y, width, height, priority);
            EnqueueRequest(request);
            return request;
        }

        /// <summary>
        /// Enqueues a 3D chunk load request with the specified priority.
        /// </summary>
        /// <returns>The request object that can be used to track or cancel the request</returns>
        public ChunkLoadRequest EnqueueRequest(int x, int y, int z, int width, int height, int depth, Priority priority = Priority.Normal)
        {
            var request = new ChunkLoadRequest(x, y, z, width, height, depth, priority);
            EnqueueRequest(request);
            return request;
        }

        /// <summary>
        /// Enqueues a pre-created chunk load request.
        /// </summary>
        public void EnqueueRequest(ChunkLoadRequest request)
        {
            _queueSemaphore.Wait();
            try
            {
                // Check if we already have a request for this chunk
                var key = (request.X, request.Y, request.Z);
                if (_activeTasks.ContainsKey(key))
                {
                    return; // Already loading this chunk
                }

                // Add to appropriate queue
                _requestQueues[request.LoadPriority].Enqueue(request);
                _requestLookup[request.RequestId] = request;

                // Start processing if not already running
                EnsureProcessingStarted();
            }
            finally
            {
                _queueSemaphore.Release();
            }

            // For immediate priority, wait for processing to start
            if (request.LoadPriority == Priority.Immediate)
            {
                TriggerImmediateProcessing();
            }
        }

        /// <summary>
        /// Cancels a pending chunk load request.
        /// </summary>
        /// <returns>True if the request was found and canceled, false otherwise</returns>
        public bool CancelRequest(Guid requestId)
        {
            _queueSemaphore.Wait();
            try
            {
                if (_requestLookup.TryGetValue(requestId, out var request))
                {
                    _requestLookup.Remove(requestId);
                    // Note: We don't remove from the queue as that would be inefficient
                    // Instead, we'll skip it when we dequeue
                    return true;
                }
                return false;
            }
            finally
            {
                _queueSemaphore.Release();
            }
        }

        /// <summary>
        /// Cancels all pending chunk load requests.
        /// </summary>
        public void CancelAllRequests()
        {
            _queueSemaphore.Wait();
            try
            {
                foreach (var queue in _requestQueues.Values)
                {
                    queue.Clear();
                }
                _requestLookup.Clear();
            }
            finally
            {
                _queueSemaphore.Release();
            }
        }

        /// <summary>
        /// Starts the processing of chunk load requests if not already running.
        /// </summary>
        private void EnsureProcessingStarted()
        {
            if (_isProcessing)
                return;

            _isProcessing = true;
            _processingTask = Task.Run(ProcessRequestsAsync);
        }

        /// <summary>
        /// Triggers immediate processing of high-priority requests.
        /// </summary>
        private void TriggerImmediateProcessing()
        {
            // This is a hint to the processor to check for immediate requests now
            // We don't need to do anything special as the processor checks immediate first
        }

        /// <summary>
        /// Main processing loop for chunk load requests.
        /// </summary>
        private async Task ProcessRequestsAsync()
        {
            while (!_cancellationSource.IsCancellationRequested)
            {
                ChunkLoadRequest? request = null;

                // Get the next request from the highest priority queue
                await _queueSemaphore.WaitAsync();
                try
                {
                    request = DequeueNextRequest();

                    if (request == null)
                    {
                        // No requests to process, pause briefly
                        _isProcessing = false;
                        _queueSemaphore.Release();
                        await Task.Delay(50);
                        continue;
                    }

                    // Mark this chunk as being processed
                    var key = (request.X, request.Y, request.Z);
                    if (_activeTasks.ContainsKey(key))
                    {
                        // Already processing this chunk, skip
                        continue;
                    }

                    // Create a task for this request but don't start it yet
                    var loadTask = ProcessRequestAsync(request);
                    _activeTasks[key] = loadTask;
                }
                finally
                {
                    if (_isProcessing)
                    {
                        _queueSemaphore.Release();
                    }
                }

                // Wait for a processing slot
                await _processSemaphore.WaitAsync();

                // Start the task and continue without waiting
                _ = Task.Run(async () =>
                {
                    try
                    {
                        await ProcessRequestAsync(request!);
                    }
                    finally
                    {
                        _processSemaphore.Release();

                        // Remove from active tasks
                        await _queueSemaphore.WaitAsync();
                        try
                        {
                            _activeTasks.Remove((request!.X, request.Y, request.Z));
                        }
                        finally
                        {
                            _queueSemaphore.Release();
                        }
                    }
                });
            }
        }

        /// <summary>
        /// Dequeues the next request from the highest priority queue.
        /// </summary>
        private ChunkLoadRequest? DequeueNextRequest()
        {
            // Check each priority level in order
            foreach (Priority priority in Enum.GetValues(typeof(Priority)))
            {
                var queue = _requestQueues[priority];

                while (queue.Count > 0)
                {
                    var request = queue.Dequeue();

                    // Skip if the request has been canceled
                    if (!_requestLookup.ContainsKey(request.RequestId))
                        continue;

                    // Skip if the request has timed out
                    if (DateTime.UtcNow - request.Timestamp > _requestTimeout)
                    {
                        _requestLookup.Remove(request.RequestId);
                        continue;
                    }

                    // Valid request found
                    return request;
                }
            }

            return null; // No valid requests found
        }

        /// <summary>
        /// Processes a single chunk load request.
        /// </summary>
        private async Task ProcessRequestAsync(ChunkLoadRequest request)
        {
            try
            {
                // This would be integrated with your chunk manager
                // For now, we'll just simulate the loading with a delay

                // Simulate different loading times based on priority
                int delayMs = request.LoadPriority switch
                {
                    Priority.Immediate => 50,
                    Priority.High => 100,
                    Priority.Normal => 200,
                    Priority.Low => 500,
                    Priority.Background => 1000,
                    _ => 200
                };

                await Task.Delay(delayMs);

                // Notify completion
                RequestCompleted?.Invoke(this, request);
            }
            catch (Exception ex)
            {
                // Notify failure
                RequestFailed?.Invoke(this, (request, ex));
            }
            finally
            {
                // Remove from lookup
                await _queueSemaphore.WaitAsync();
                try
                {
                    _requestLookup.Remove(request.RequestId);
                }
                finally
                {
                    _queueSemaphore.Release();
                }
            }
        }

        /// <summary>
        /// Gets the number of pending requests for each priority level.
        /// </summary>
        public Dictionary<Priority, int> GetQueueSizes()
        {
            _queueSemaphore.Wait();
            try
            {
                var result = new Dictionary<Priority, int>();
                foreach (var kvp in _requestQueues)
                {
                    result[kvp.Key] = kvp.Value.Count;
                }
                return result;
            }
            finally
            {
                _queueSemaphore.Release();
            }
        }

        /// <summary>
        /// Gets the total number of pending requests.
        /// </summary>
        public int GetTotalQueueSize()
        {
            _queueSemaphore.Wait();
            try
            {
                int total = 0;
                foreach (var queue in _requestQueues.Values)
                {
                    total += queue.Count;
                }
                return total;
            }
            finally
            {
                _queueSemaphore.Release();
            }
        }

        /// <summary>
        /// Stops processing and releases resources.
        /// </summary>
        public async Task ShutdownAsync()
        {
            _cancellationSource.Cancel();

            if (_processingTask != null)
            {
                try
                {
                    await _processingTask;
                }
                catch (OperationCanceledException)
                {
                    // Expected
                }
                catch (Exception)
                {
                    // Ignore other exceptions during shutdown
                }
            }

            _queueSemaphore.Dispose();
            _processSemaphore.Dispose();
            _cancellationSource.Dispose();
        }

        /// <summary>
        /// Creates a ChunkLoadingPriority instance with default settings.
        /// </summary>
        public static ChunkLoadingPriority CreateDefault()
        {
            return new ChunkLoadingPriority();
        }

        /// <summary>
        /// Creates a ChunkLoadingPriority instance optimized for high throughput.
        /// </summary>
        public static ChunkLoadingPriority CreateHighThroughput()
        {
            return new ChunkLoadingPriority(
                maxConcurrentLoads: Environment.ProcessorCount * 2,
                requestTimeoutSeconds: 60);
        }

        /// <summary>
        /// Creates a ChunkLoadingPriority instance optimized for low latency.
        /// </summary>
        public static ChunkLoadingPriority CreateLowLatency()
        {
            return new ChunkLoadingPriority(
                maxConcurrentLoads: Math.Max(2, Environment.ProcessorCount / 2),
                requestTimeoutSeconds: 15);
        }

        /// <summary>
        /// Integrates with a chunk manager to process load requests.
        /// </summary>
        /// <typeparam name="T">The type of data in the chunks</typeparam>
        /// <param name="chunkManager">The chunk manager to use for loading chunks</param>
        public void IntegrateWith<T>(IChunkManager chunkManager)
        {
            // Replace the ProcessRequestAsync method with one that uses the chunk manager
            // This is a simplified example - in a real implementation, you'd need to handle
            // both 2D and 3D chunk managers and properly cast the interface

            RequestCompleted += (sender, request) =>
            {
                // The chunk has been loaded, you might want to do something with it
                var chunk = chunkManager.GetChunk(request.X, request.Y);
                // Additional processing if needed
            };
        }

        /// <summary>
        /// Gets the estimated time until a request with the given priority would be processed.
        /// </summary>
        /// <returns>Estimated wait time in milliseconds, or -1 if cannot be determined</returns>
        public int GetEstimatedWaitTime(Priority priority)
        {
            _queueSemaphore.Wait();
            try
            {
                int totalHigherPriorityRequests = 0;

                // Count requests with higher or equal priority
                foreach (Priority p in Enum.GetValues(typeof(Priority)))
                {
                    if (p <= priority)
                    {
                        totalHigherPriorityRequests += _requestQueues[p].Count;
                    }
                }

                // If no requests or no active tasks, return 0
                if (totalHigherPriorityRequests == 0 || _activeTasks.Count == 0)
                {
                    return 0;
                }

                // Estimate based on current processing rate
                // This is a very simple estimate and could be improved with actual metrics
                int averageProcessingTimeMs = priority switch
                {
                    Priority.Immediate => 50,
                    Priority.High => 100,
                    Priority.Normal => 200,
                    Priority.Low => 500,
                    Priority.Background => 1000,
                    _ => 200
                };

                // Calculate how many batches of concurrent requests we'll need
                int batches = (int)Math.Ceiling(totalHigherPriorityRequests / (double)_maxConcurrentLoads);

                return batches * averageProcessingTimeMs;
            }
            finally
            {
                _queueSemaphore.Release();
            }
        }

        /// <summary>
        /// Adjusts the priority of an existing request.
        /// </summary>
        /// <returns>True if the request was found and its priority adjusted, false otherwise</returns>
        public bool AdjustPriority(Guid requestId, Priority newPriority)
        {
            // Note: This is not an efficient operation as we don't directly modify the queue
            // Instead, we mark the request for priority change when it's processed

            _queueSemaphore.Wait();
            try
            {
                if (_requestLookup.TryGetValue(requestId, out var request))
                {
                    // If the request is of lower priority than requested, we'll re-queue it
                    if (request.LoadPriority > newPriority)
                    {
                        // Remove from lookup (it will be skipped when dequeued)
                        _requestLookup.Remove(requestId);

                        // Create a new request with the same parameters but higher priority
                        var newRequest = request.Depth > 1
                            ? new ChunkLoadRequest(request.X, request.Y, request.Z, request.Width, request.Height, request.Depth, newPriority)
                            : new ChunkLoadRequest(request.X, request.Y, request.Width, request.Height, newPriority);

                        // Add the new request
                        _requestQueues[newPriority].Enqueue(newRequest);
                        _requestLookup[newRequest.RequestId] = newRequest;

                        // If upgrading to immediate, trigger processing
                        if (newPriority == Priority.Immediate)
                        {
                            TriggerImmediateProcessing();
                        }
                    }

                    return true;
                }

                return false;
            }
            finally
            {
                _queueSemaphore.Release();
            }
        }

        /// <summary>
        /// Gets statistics about the current state of the chunk loading system.
        /// </summary>
        public Dictionary<string, object> GetStatistics()
        {
            _queueSemaphore.Wait();
            try
            {
                var stats = new Dictionary<string, object>
                {
                    ["TotalPendingRequests"] = _requestLookup.Count,
                    ["ActiveTasks"] = _activeTasks.Count,
                    ["IsProcessing"] = _isProcessing,
                    ["MaxConcurrentLoads"] = _maxConcurrentLoads
                };

                // Add queue sizes for each priority
                foreach (Priority priority in Enum.GetValues(typeof(Priority)))
                {
                    stats[$"Queue_{priority}"] = _requestQueues[priority].Count;
                }

                return stats;
            }
            finally
            {
                _queueSemaphore.Release();
            }
        }
    }
}
