using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Dasync.Collections;
using Microsoft.Extensions.Logging;

namespace MaddoShared
{
    [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
    public class ImageCreationService(
        ILogger<ImageCreationService> logger,
        PicSettings picSettings,
        IImageCreator imageCreatorService)
    {
        public class Options
        {
            public bool AggiornaSottodirectory { get; set; }
            public bool CreaSottocartelle { get; set; }

            public int FilePerCartella { get; set; }
            public string SuffissoCartelle { get; set; }
            public int CifreContatore { get; set; }
            public NumerazioneType NumerazioneType { get; set; }
            public string SourcePath { get; set; }
            public string DestinationPath { get; set; }

            public int MaxThreads { get; set; }
            public int ChunksSize { get; set; }
            public bool LinearExecution { get; set; }
        }

        public async Task<string> CreaCatalogoParallel(Options options, ConcurrentBag<string> results,
            EventHandler<Tuple<string, int>> updateEvent,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();

            await ProcessImagesParallel(options, results, updateEvent, cancellationToken);

            stopwatch.Stop();

            return
                $"{stopwatch.Elapsed.Hours}h {stopwatch.Elapsed.Minutes}m {stopwatch.Elapsed.Seconds}s ({stopwatch.Elapsed.TotalSeconds}s)";
        }

        /// <summary>
        /// Gets the list of files that will be processed based on the provided options.
        /// Useful for benchmarking and testing to understand the scope of work.
        /// </summary>
        public List<FileData> GetFilesToProcessPublic(Options options)
        {
            return GetFilesToProcess(options);
        }

        public async Task ProcessImagesParallel(
            Options options,
            ConcurrentBag<string> results,
            EventHandler<Tuple<string, int>> updateEvent,
            CancellationToken cancellationToken = default)
        {
            var dataToProcess = GetFilesToProcess(options);

            // int threads = options.MaxThreads == 0 ? Environment.ProcessorCount * 2 : options.MaxThreads;
            int threads = options.MaxThreads;

            Bitmap logoBmp = null;
            // Load Logo (short-circuit)
            if (picSettings.LogoAggiungi && File.Exists(picSettings.LogoNomeFile))
            {
                logoBmp = new Bitmap(picSettings.LogoNomeFile);
            }

            Func<FileData, Task> processFile = async fileData =>
            {
                Bitmap logoCopy = logoBmp is null
                    ? null
                    : logoBmp.Clone(new Rectangle(0, 0, logoBmp.Width, logoBmp.Height), logoBmp.PixelFormat);

                var imgState = new ImageState
                {
                    WorkFile = fileData.File,
                    DestDir = fileData.Directory,
                };

                try
                {
                    // Ensure CreateImageAsync can accept a null logoCopy value.
                    await imageCreatorService.CreateImageAsync(imgState, logoCopy);

                    results.Add(fileData.File.Name);

                    try
                    {
                        updateEvent?.Invoke(this, new Tuple<string, int>(fileData.File.Name, dataToProcess.Count));
                    }
                    catch (Exception e)
                    {
                        logger.LogError(e, "Error in reporting update");
                        // Don't rethrow - continue processing other images
                    }
                }
                finally
                {
                    // Dispose the clone if it was created
                    logoCopy?.Dispose();
                }
            };

            if (options.LinearExecution)
            {
                foreach (var fileData in dataToProcess)
                    await processFile(fileData);
            }
            else
            {
                var chunks = options.ChunksSize > 0
                    ? SplitList(dataToProcess, options.ChunksSize)
                    : new List<List<FileData>> { dataToProcess };

                foreach (var chunk in chunks)
                {
                    await chunk.ParallelForEachAsync(
                        processFile,
                        maxDegreeOfParallelism: threads,
                        false,
                        cancellationToken);

                    chunk.Clear();
                    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: false, compacting: false);
                }
            }

            try
            {
                logoBmp?.Dispose();
            }
            catch (Exception e)
            {
                logger.LogError(e, "Error in disposing the logo");
            }
        }

        private List<FileData> GetFilesToProcess(Options options)
        {
            // Support multiple common JPEG patterns so files named .jpeg, .jpe, etc. are included
            var jpgPatterns = new[] { "*.jpg", "*.jpeg", "*.jpe", "*.jfif", "*.pjpeg", "*.pjp" };

            if (options.AggiornaSottodirectory && options.CreaSottocartelle)
            {
                var helper = new FileHelperSharp();
                // Pass patterns joined by ';' - FileHelperSharp will split and handle multiple patterns
                return helper.GetFilesRecursive(
                    new DirectoryInfo(options.SourcePath),
                    new DirectoryInfo(options.DestinationPath),
                    string.Join(";", jpgPatterns),
                    new FileHelperOptions
                    {
                        FilesPerFolder = options.FilePerCartella,
                        Suffix = options.SuffissoCartelle,
                        CounterSize = options.CifreContatore,
                        NumerationType = options.NumerazioneType
                    });
            }

            // For non-recursive or recursive enumeration without using the helper, enumerate for each pattern
            var searchOption = options.AggiornaSottodirectory ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;

            var files = jpgPatterns
                .SelectMany(p => Directory.EnumerateFiles(options.SourcePath, p, searchOption))
                .Distinct()
                .ToList();

            return files.Select(x =>
            {
                var fInfo = new FileInfo(x);
                var filePath = fInfo.DirectoryName;
                var trimmedSourcePath = options.SourcePath.TrimEnd('\\');
                var newFilePath = fInfo.FullName.Replace(trimmedSourcePath, "").TrimStart('\\');
                newFilePath = Path.Combine(options.DestinationPath, newFilePath);

                var destFolderPath = new FileInfo(newFilePath).DirectoryName;
                var destFolderInfo = new DirectoryInfo(destFolderPath);
                destFolderInfo.EnsureDirectoryExists();

                return new FileData(fInfo, new DirectoryInfo(new FileInfo(newFilePath).DirectoryName));

                // var destDir = new FileInfo(newFilePath).Directory!;
                // destDir.Create(); // Ensure exists
                //
                // return new FileData(fInfo, destDir);
            }).ToList();
        }

        private static IEnumerable<List<T>> SplitList<T>(List<T> bigList, int nSize = 3)
        {
            for (int i = 0; i < bigList.Count; i += nSize)
            {
                yield return bigList.GetRange(i, Math.Min(nSize, bigList.Count - i));
            }
        }
    }
}