using ImageMagick;
using System.IO.Compression;
using SevenZip;
using CompressionLevel = SevenZip.CompressionLevel;
using CompressionMethod = SevenZip.CompressionMethod;
using ImageMagick.Formats;

namespace WebPnCbzMaker
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");

            SevenZipBase.SetLibraryPath(@"C:\Program Files\7-Zip\7z.dll");
            //input folder
            // $root
            //   |- 123456-name
            //        |- 000001.jpg or png
            //        |- 000002.jpg or png
            //        |- thumb.jpg or thumb.webp
            //        |- ComicInfo.xml
            //   |- 56789-name
            //        |- 000001.jpg or png
            //        |- 000002.jpg or png
            //        |- thumb.jpg or thumb.webp
            //        |- ComicInfo.xml

            //output folder
            // $root
            //   |- 123456-name
            //        |- ComicInfo.xml <- copy
            //        |- thumb.jpg or thumb.webp <- copy
            //        |- 123456.cbz <- create zip from [seq img:convert to webP] and [ComicInfo.xml]
            //   |- 56789-name
            //        |- ComicInfo.xml <- copy
            //        |- thumb.jpg or thumb.webp <- copy
            //        |- 56789.cbz <- create zip from [seq img:convert to webP] and [ComicInfo.xml]

            var inputFolder = "./In";
            var outputFolder = "./Out";

            // 遍历输入文件夹中的所有子文件夹
            var inDirs = Directory.GetDirectories(inputFolder);

            for (var index = 0; index < inDirs.Length; index++)
            {
                var dir = inDirs[index];
                // 获取目录名称(例如:123456-name)
                var folderName = Path.GetFileName(dir);
                Console.WriteLine($" >>> [{index + 1:0000}/{inDirs.Length:0000}] Enter folder: {folderName}");

                // 提取前面的数字部分作为 CBZ 文件名
                var folderNameDigits = folderName.Split('-', 2)[0];
                if (folderNameDigits.All(char.IsAsciiDigit) == false)
                {
                    Console.WriteLine($"[WARN] skip folder: {folderName}");
                    continue;
                }

                var cbzFileName = $"{folderNameDigits}.cbz";

                var outputPath = Path.Combine(outputFolder, folderName);
                var destCbzPath = Path.Combine(outputPath, cbzFileName);
                var cbz7MarkerPath = Path.Combine(outputPath, "WebPnCbz.7");

                if (File.Exists(destCbzPath))
                {
                    if (File.Exists(cbz7MarkerPath))
                    {
                        Console.WriteLine($"[SKIP] skip folder, exist {cbzFileName} and 7");
                    }
                    else
                    {
                        //TODO: Conv cbz(zip) to webp in cbz(7z)
                        //then TouchFile(cbz7MarkerPath);
                    }
                    continue;
                }

                var cbzAlreadyExistPath = Path.Combine(dir, cbzFileName);
                var flagFoundExistedCbz = File.Exists(cbzAlreadyExistPath);

                var lstFilesToCbz = new List<string>();
                var lstFilesCopy = new List<string>();

                var flagSeqExist = false;

                if (flagFoundExistedCbz) lstFilesCopy.Add(cbzAlreadyExistPath);

                foreach (var filePath in Directory.GetFiles(dir))
                {
                    //ComicInfo.xml 要复制到输出目录并且也要压入归档
                    if (Path.GetFileName(filePath).Equals("ComicInfo.xml", StringComparison.InvariantCultureIgnoreCase))
                    {
                        lstFilesToCbz.Add(filePath);
                        lstFilesCopy.Add(filePath);
                        continue;
                    }

                    //序列文件名压入归档
                    var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath);
                    if (fileNameWithoutExtension.Length > 0 && fileNameWithoutExtension.All(char.IsDigit))
                    {
                        if (flagFoundExistedCbz == false) lstFilesToCbz.Add(filePath);
                        flagSeqExist = true;
                        continue;
                    }

                    //其他直接拷贝
                    lstFilesCopy.Add(filePath);
                }

                if (flagSeqExist == false)
                {
                    Console.WriteLine($"[WARN] skip folder: {folderName}, no SEQ file found");
                    continue;
                }

                // 创建输出文件夹
                Directory.CreateDirectory(outputPath);

                // 处理直接拷贝
                foreach (var copy in lstFilesCopy)
                {
                    var destFilePath = Path.Combine(outputPath, Path.GetFileName(copy));
                    if (File.Exists(destFilePath))
                    {
                        Console.WriteLine($"[SKIP] Copy file: {Path.GetFileName(copy)}");
                        continue;
                    }

                    Console.WriteLine($"Copy file: {Path.GetFileName(copy)}");
                    File.Copy(copy, destFilePath);
                }

                // 创建 webP 7z 作为 cbz
                if (flagFoundExistedCbz == false)
                {
                    var dictEntries = new Dictionary<string, Stream>();

                    Parallel.ForEach(lstFilesToCbz, toCbz =>
                    {
                        var bytes = File.ReadAllBytes(toCbz);
                        try
                        {
                            bytes = ConvertToWebP(bytes);
                            lock (dictEntries) dictEntries[Path.GetFileNameWithoutExtension(toCbz) + ".webp"] = new MemoryStream(bytes);
                            Console.WriteLine($"Convert (WebP) file to archive: {Path.GetFileName(toCbz)}");
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e);
                            Console.WriteLine($"Add file to archive: {Path.GetFileName(toCbz)}");
                            lock (dictEntries) dictEntries[Path.GetFileName(toCbz)] = new MemoryStream(bytes);
                        }
                    });

                    Console.WriteLine($"Creating archive file: {destCbzPath}");
                    var cbzBytes = MakeSevenZip(dictEntries);
                    Console.WriteLine($"Writing archive file: {destCbzPath}");
                    File.WriteAllBytes(destCbzPath, cbzBytes);
                }

                TouchFile(cbz7MarkerPath);

                Console.WriteLine($" <<< Leave folder: {folderName}");
            }

            Console.WriteLine("Bye, World!");
        }

        private static readonly WebPWriteDefines WebPDefines = new()
        {
            Lossless = false,
            Method = 6,
        };

        private static byte[] ConvertToWebP(byte[] imageBytes)
        {
            using var image = new MagickImage(imageBytes);
            using var ms = new MemoryStream();
            image.Format = MagickFormat.WebP;

            image.Write(ms, WebPDefines);
            return ms.ToArray();  // 返回 WebP 图像字节流
        }

        private static byte[] MakeSevenZip(Dictionary<string, Stream> entries)
        {
            var szc = new SevenZipCompressor();

            szc.ArchiveFormat = OutArchiveFormat.SevenZip;
            szc.CompressionLevel = CompressionLevel.Ultra;
            szc.CompressionMethod = CompressionMethod.Lzma2;
            szc.CustomParameters["s"] = "off";

            var outStream = new MemoryStream();
            szc.CompressStreamDictionary(entries, outStream);

            return outStream.ToArray();
        }

        /// <summary>
        /// 模拟 Linux 的 touch 命令。
        /// 如果文件不存在,则创建文件;
        /// 如果文件已存在,则更新文件的最后访问时间和修改时间。
        /// </summary>
        /// <param name="path">文件的路径。</param>
        /// <exception cref="UnauthorizedAccessException">当没有权限访问路径时抛出。</exception>
        /// <exception cref="IOException">当发生 I/O 错误时抛出。</exception>
        private static void TouchFile(string path)
        {
            try
            {
                // 如果文件存在,更新最后访问时间和修改时间。
                if (File.Exists(path))
                {
                    File.SetLastAccessTime(path, DateTime.Now);
                    File.SetLastWriteTime(path, DateTime.Now);
                }
                else
                {
                    // 如果文件不存在,创建一个空文件。
                    using var _ = File.Create(path);
                }
            }
            catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException)
            {
                throw new InvalidOperationException($"无法 touch 文件: {path}", ex);
            }
        }
    }
}