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

namespace AdvChkSys.Threading
{
    /// <summary>
    /// Provides advanced parallel processing for chunks.
    /// </summary>
    public static class ChunkParallelProcessor
    {
        /// <summary>
        /// Processes chunks with dependency awareness.
        /// </summary>
        /// <param name="chunks">The chunks to process</param>
        /// <param name="processor">The function to process each chunk</param>
        /// <param name="getDependencies">Function to get dependencies for a chunk</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static async Task ProcessChunksWithDependenciesAsync(
            IEnumerable<IChunk> chunks,
            Func<IChunk, Task> processor,
            Func<IChunk, IEnumerable<IChunk>> getDependencies,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            // Build dependency graph
            var dependencyGraph = BuildDependencyGraph(chunks, getDependencies);

            // Process in dependency order
            await ProcessDependencyGraphAsync(
                dependencyGraph,
                processor,
                maxDegreeOfParallelism,
                cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// Builds a dependency graph for chunks.
        /// </summary>
        private static DependencyGraph BuildDependencyGraph(
            IEnumerable<IChunk> chunks,
            Func<IChunk, IEnumerable<IChunk>> getDependencies)
        {
            var graph = new DependencyGraph();

            // Add all chunks to the graph
            foreach (var chunk in chunks)
            {
                graph.AddNode(chunk);
            }

            // Add dependencies
            foreach (var chunk in chunks)
            {
                var dependencies = getDependencies(chunk);
                foreach (var dependency in dependencies)
                {
                    graph.AddDependency(chunk, dependency);
                }
            }

            return graph;
        }

        /// <summary>
        /// Processes a dependency graph in parallel.
        /// </summary>
        private static async Task ProcessDependencyGraphAsync(
            DependencyGraph graph,
            Func<IChunk, Task> processor,
            int? maxDegreeOfParallelism,
            CancellationToken cancellationToken)
        {
            // Set up semaphore for parallelism control
            var semaphore = new SemaphoreSlim(
                maxDegreeOfParallelism ?? ChunkThreadingConfiguration.DefaultMaxDegreeOfParallelism);

            // Track completed chunks
            var completed = new ConcurrentDictionary<IChunk, bool>();

            // Get initial set of chunks with no dependencies
            var readyChunks = new ConcurrentQueue<IChunk>(graph.GetNodesWithNoDependencies());

            // Track active tasks
            var activeTasks = new ConcurrentDictionary<IChunk, Task>();

            // Process until all chunks are completed
            while (!readyChunks.IsEmpty || activeTasks.Count > 0)
            {
                // Check for cancellation
                cancellationToken.ThrowIfCancellationRequested();

                // Start processing ready chunks
                while (readyChunks.TryDequeue(out var chunk))
                {
                    // Wait for a semaphore slot
                    await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);

                    // Start processing this chunk
                    var task = ProcessChunkAsync(
                        chunk,
                        processor,
                        graph,
                        completed,
                        readyChunks,
                        semaphore,
                        cancellationToken);

                    activeTasks[chunk] = task;

                    // When the task completes, remove it from active tasks
                    _ = task.ContinueWith(_ =>
                    {
                        activeTasks.TryRemove(chunk, out _);
                    }, TaskContinuationOptions.ExecuteSynchronously);
                }

                // If no ready chunks but active tasks, wait for one to complete
                if (readyChunks.IsEmpty && activeTasks.Count > 0)
                {
                    await Task.WhenAny(activeTasks.Values).ConfigureAwait(false);
                }
            }

            // Clean up
            semaphore.Dispose();
        }

        /// <summary>
        /// Processes a single chunk and updates the ready queue.
        /// </summary>
        private static async Task ProcessChunkAsync(
            IChunk chunk,
            Func<IChunk, Task> processor,
            DependencyGraph graph,
            ConcurrentDictionary<IChunk, bool> completed,
            ConcurrentQueue<IChunk> readyChunks,
            SemaphoreSlim semaphore,
            CancellationToken cancellationToken)
        {
            try
            {
                // Check for cancellation before processing
                cancellationToken.ThrowIfCancellationRequested();

                // Process the chunk
                await processor(chunk).ConfigureAwait(false);

                // Check for cancellation after processing but before updating dependencies
                cancellationToken.ThrowIfCancellationRequested();

                // Mark as completed
                completed[chunk] = true;

                // Find dependents that are now ready
                var dependents = graph.GetDependents(chunk);
                foreach (var dependent in dependents)
                {
                    // Check for cancellation during dependency processing
                    cancellationToken.ThrowIfCancellationRequested();

                    // Check if all dependencies are completed
                    var dependencies = graph.GetDependencies(dependent);
                    if (dependencies.All(d => completed.ContainsKey(d)))
                    {
                        // All dependencies completed, add to ready queue
                        readyChunks.Enqueue(dependent);
                    }
                }
            }
            finally
            {
                // Release the semaphore
                semaphore.Release();
            }
        }

        /// <summary>
        /// Represents a dependency graph for chunks.
        /// </summary>
        private class DependencyGraph
        {
            // Map of chunk to its dependencies
            private readonly Dictionary<IChunk, HashSet<IChunk>> _dependencies = new();

            // Map of chunk to chunks that depend on it
            private readonly Dictionary<IChunk, HashSet<IChunk>> _dependents = new();

            /// <summary>
            /// Adds a node to the graph.
            /// </summary>
            public void AddNode(IChunk chunk)
            {
                if (!_dependencies.ContainsKey(chunk))
                {
                    _dependencies[chunk] = new HashSet<IChunk>();
                }

                if (!_dependents.ContainsKey(chunk))
                {
                    _dependents[chunk] = new HashSet<IChunk>();
                }
            }

            /// <summary>
            /// Adds a dependency between two nodes.
            /// </summary>
            public void AddDependency(IChunk dependent, IChunk dependency)
            {
                // Add nodes if they don't exist
                AddNode(dependent);
                AddNode(dependency);

                // Add dependency
                _dependencies[dependent].Add(dependency);

                // Add dependent
                _dependents[dependency].Add(dependent);
            }

            /// <summary>
            /// Gets all nodes with no dependencies.
            /// </summary>
            public IEnumerable<IChunk> GetNodesWithNoDependencies()
            {
                return _dependencies.Where(kvp => kvp.Value.Count == 0).Select(kvp => kvp.Key);
            }

            /// <summary>
            /// Gets all dependencies of a node.
            /// </summary>
            public IEnumerable<IChunk> GetDependencies(IChunk chunk)
            {
                if (_dependencies.TryGetValue(chunk, out var dependencies))
                {
                    return dependencies;
                }

                return Enumerable.Empty<IChunk>();
            }

            /// <summary>
            /// Gets all dependents of a node.
            /// </summary>
            public IEnumerable<IChunk> GetDependents(IChunk chunk)
            {
                if (_dependents.TryGetValue(chunk, out var dependents))
                {
                    return dependents;
                }

                return Enumerable.Empty<IChunk>();
            }
        }
    }
}