using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AdvChkSys.Threading
{
    /// <summary>
    /// Provides utilities for running chunk-related tasks asynchronously.
    /// Supports scheduling, cancellation, and custom task options.
    /// </summary>
    public static class ChunkTaskScheduler
    {
        // Default maximum degree of parallelism
        private static int _maxDegreeOfParallelism = Environment.ProcessorCount;

        /// <summary>
        /// Gets or sets the maximum degree of parallelism for batch operations.
        /// </summary>
        public static int MaxDegreeOfParallelism
        {
            get => _maxDegreeOfParallelism;
            set => _maxDegreeOfParallelism = value > 0 ? value : Environment.ProcessorCount;
        }

        /// <summary>
        /// Runs the given action asynchronously on the thread pool.
        /// </summary>
        public static Task RunAsync(Action action, CancellationToken cancellationToken = default)
        {
            return Task.Run(action, cancellationToken);
        }

        /// <summary>
        /// Runs the given function asynchronously on the thread pool and returns a result.
        /// </summary>
        public static Task<TResult> RunAsync<TResult>(Func<TResult> func, CancellationToken cancellationToken = default)
        {
            return Task.Run(func, cancellationToken);
        }

        /// <summary>
        /// Runs the given asynchronous function.
        /// </summary>
        public static Task RunAsync(Func<Task> func, CancellationToken cancellationToken = default)
        {
            return Task.Run(func, cancellationToken);
        }

        /// <summary>
        /// Runs the given asynchronous function and returns a result.
        /// </summary>
        public static Task<TResult> RunAsync<TResult>(Func<Task<TResult>> func, CancellationToken cancellationToken = default)
        {
            return Task.Run(func, cancellationToken);
        }

        /// <summary>
        /// Creates a cancellation token that cancels after a timeout.
        /// </summary>
        /// <param name="timeout">The timeout</param>
        /// <param name="cancellationToken">Optional token to combine with the timeout</param>
        /// <returns>A cancellation token that cancels after the timeout or when the input token is canceled</returns>
        public static CancellationToken CreateTimeoutToken(
            TimeSpan timeout,
            CancellationToken cancellationToken = default)
        {
            if (timeout == Timeout.InfiniteTimeSpan && cancellationToken == CancellationToken.None)
                return CancellationToken.None;

            var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            if (timeout != Timeout.InfiniteTimeSpan)
                cts.CancelAfter(timeout);

            return cts.Token;
        }

        /// <summary>
        /// Runs a task with a timeout.
        /// </summary>
        /// <param name="task">The task to run</param>
        /// <param name="timeout">The timeout</param>
        /// <param name="cancellationToken">Optional cancellation token</param>
        /// <returns>The task result</returns>
        /// <exception cref="TimeoutException">Thrown if the task times out</exception>
        public static async Task<T> WithTimeoutAsync<T>(
            Task<T> task,
            TimeSpan timeout,
            CancellationToken cancellationToken = default)
        {
            using var timeoutCts = new CancellationTokenSource(timeout);
            using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
                timeoutCts.Token, cancellationToken);

            var completedTask = await Task.WhenAny(task, Task.Delay(timeout, linkedCts.Token))
                .ConfigureAwait(false);

            if (completedTask == task)
            {
                return await task.ConfigureAwait(false);
            }

            throw new TimeoutException($"The operation timed out after {timeout.TotalMilliseconds}ms");
        }

        /// <summary>
        /// Runs a task with a timeout.
        /// </summary>
        /// <param name="task">The task to run</param>
        /// <param name="timeout">The timeout</param>
        /// <param name="cancellationToken">Optional cancellation token</param>
        /// <exception cref="TimeoutException">Thrown if the task times out</exception>
        public static async Task WithTimeoutAsync(
            Task task,
            TimeSpan timeout,
            CancellationToken cancellationToken = default)
        {
            using var timeoutCts = new CancellationTokenSource(timeout);
            using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
                timeoutCts.Token, cancellationToken);

            var completedTask = await Task.WhenAny(task, Task.Delay(timeout, linkedCts.Token))
                .ConfigureAwait(false);

            if (completedTask == task)
            {
                await task.ConfigureAwait(false);
                return;
            }

            throw new TimeoutException($"The operation timed out after {timeout.TotalMilliseconds}ms");
        }

        /// <summary>
        /// Runs a task with a fallback value if it times out.
        /// </summary>
        /// <typeparam name="T">The type of result</typeparam>
        /// <param name="task">The task to run</param>
        /// <param name="timeout">The timeout</param>
        /// <param name="fallbackValue">The fallback value to return if the task times out</param>
        /// <param name="cancellationToken">Optional cancellation token</param>
        /// <returns>The task result or the fallback value if the task times out</returns>
        public static async Task<T> WithTimeoutOrDefaultAsync<T>(
            Task<T> task,
            TimeSpan timeout,
            T fallbackValue,
            CancellationToken cancellationToken = default)
        {
            try
            {
                return await WithTimeoutAsync(task, timeout, cancellationToken).ConfigureAwait(false);
            }
            catch (TimeoutException)
            {
                return fallbackValue;
            }
        }

        /// <summary>
        /// Runs multiple actions in parallel with a limit on the degree of parallelism.
        /// </summary>
        public static Task RunBatchParallelAsync(IEnumerable<Action> actions, int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            return Task.Run(() =>
            {
                var options = new ParallelOptions
                {
                    MaxDegreeOfParallelism = maxDegreeOfParallelism ?? MaxDegreeOfParallelism,
                    CancellationToken = cancellationToken
                };

                Parallel.ForEach(actions, options, action => action());
            }, cancellationToken);
        }

        /// <summary>
        /// Runs a batch of functions in parallel and returns the results.
        /// </summary>
        /// <typeparam name="T">The type of result</typeparam>
        /// <param name="functions">The functions to run</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        /// <returns>The results in the same order as the functions</returns>
        public static async Task<T[]> RunBatchParallelAsync<T>(
            IEnumerable<Func<T>> functions,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            var funcs = functions.ToArray();
            var results = new T[funcs.Length];

            await Task.Run(() =>
            {
                var options = new ParallelOptions
                {
                    MaxDegreeOfParallelism = maxDegreeOfParallelism ?? MaxDegreeOfParallelism,
                    CancellationToken = cancellationToken
                };

                Parallel.For(0, funcs.Length, options, i =>
                {
                    results[i] = funcs[i]();
                });
            }, cancellationToken).ConfigureAwait(false);

            return results;
        }

        /// <summary>
        /// Runs a batch of async functions in parallel.
        /// </summary>
        /// <param name="functions">The async functions to run</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static async Task RunBatchParallelAsync(
            IEnumerable<Func<Task>> functions,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            var funcs = functions.ToArray();

            if (funcs.Length == 0)
                return;

            // Use SemaphoreSlim to limit concurrency
            using var semaphore = new SemaphoreSlim(
                maxDegreeOfParallelism ?? MaxDegreeOfParallelism);

            // Create tasks for all functions
            var tasks = new List<Task>(funcs.Length);

            foreach (var func in funcs)
            {
                // Wait for a slot in the semaphore
                await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);

                // Create a task that releases the semaphore when done
                var task = Task.Run(async () =>
                {
                    try
                    {
                        await func().ConfigureAwait(false);
                    }
                    finally
                    {
                        semaphore.Release();
                    }
                }, cancellationToken);

                tasks.Add(task);
            }

            // Wait for all tasks to complete
            await Task.WhenAll(tasks).ConfigureAwait(false);
        }

        /// <summary>
        /// Runs a batch of async functions in parallel and returns the results.
        /// </summary>
        /// <typeparam name="T">The type of result</typeparam>
        /// <param name="functions">The async functions to run</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        /// <returns>The results in the same order as the functions</returns>
        public static async Task<T[]> RunBatchParallelAsync<T>(
            IEnumerable<Func<Task<T>>> functions,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            var funcs = functions.ToArray();

            if (funcs.Length == 0)
                return Array.Empty<T>();

            // Use SemaphoreSlim to limit concurrency
            using var semaphore = new SemaphoreSlim(
                maxDegreeOfParallelism ?? MaxDegreeOfParallelism);

            // Create tasks for all functions
            var tasks = new Task<T>[funcs.Length];

            for (int i = 0; i < funcs.Length; i++)
            {
                var func = funcs[i];
                var index = i;

                // Wait for a slot in the semaphore
                await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);

                // Create a task that releases the semaphore when done
                tasks[index] = Task.Run(async () =>
                {
                    try
                    {
                        return await func().ConfigureAwait(false);
                    }
                    finally
                    {
                        semaphore.Release();
                    }
                }, cancellationToken);
            }

            // Wait for all tasks to complete
            return await Task.WhenAll(tasks).ConfigureAwait(false);
        }
    }
}