using Microsoft.Extensions.Logging;
using System.Globalization;
using System.IO.Compression;
using System.Text.RegularExpressions;

namespace File_Folder_Helper.Helpers;

internal static partial class HelperZipFilesBy
{

    private static DateTimeOffset? GetDateTimeOffset(string keyFileExtension, FileInfo fileInfo, FileInfo extractKeyFileInfo)
    {
        DateTimeOffset? dateTimeOffset = null;
        using ZipArchive zip = ZipFile.Open(fileInfo.FullName, ZipArchiveMode.Read);
        foreach (ZipArchiveEntry zipArchiveEntry in zip.Entries)
        {
            if (!zipArchiveEntry.Name.EndsWith(keyFileExtension))
                continue;
            dateTimeOffset = zipArchiveEntry.LastWriteTime;
            if (fileInfo.FullName[0] != '\\')
            {
                zipArchiveEntry.ExtractToFile(extractKeyFileInfo.FullName);
                File.SetCreationTime(extractKeyFileInfo.FullName, fileInfo.CreationTime);
                File.SetLastWriteTime(extractKeyFileInfo.FullName, dateTimeOffset.Value.LocalDateTime);
            }
            break;
        }
        return dateTimeOffset;
    }

    [GeneratedRegex("[a-zA-Z0-9]{1,}")]
    private static partial Regex LowerAlphaAlphaAndNumber();

    private static bool ExtractKeyFileAndSetDateFromZipEntry(ILogger<Worker> logger, string[] zipFiles, string keyFileExtension, string keyFileExtensionB, string keyFileExtensionC, bool renameToLower)
    {
        bool result = false;
        string[] files;
        string checkFile;
        string? lowerName;
        FileInfo fileInfo;
        FileInfo extractKeyFileInfo;
        DateTimeOffset? dateTimeOffset;
        foreach (string zipFile in zipFiles)
        {
            fileInfo = new(zipFile);
            if (fileInfo.DirectoryName is null)
                throw new NullReferenceException(nameof(fileInfo.DirectoryName));
            lowerName = !renameToLower ? null : Path.Combine(fileInfo.DirectoryName, fileInfo.Name.ToLower());
            if (renameToLower && lowerName is not null && lowerName != fileInfo.FullName)
            {
                files = Directory.GetFiles(fileInfo.DirectoryName, $"{Path.GetFileNameWithoutExtension(fileInfo.Name)}*", SearchOption.TopDirectoryOnly);
                foreach (string file in files)
                    File.Move(file, Path.Combine(fileInfo.DirectoryName, Path.GetFileName(file).ToLower()));
                fileInfo = new(lowerName);
                if (fileInfo.DirectoryName is null)
                    throw new NullReferenceException(nameof(fileInfo.DirectoryName));
            }
            extractKeyFileInfo = new(Path.Combine(fileInfo.DirectoryName, $"{Path.GetFileNameWithoutExtension(fileInfo.Name)}{keyFileExtension}"));
            if (extractKeyFileInfo.Exists)
            {
                if (extractKeyFileInfo.CreationTime.ToString("yyyy-MM-dd") == fileInfo.CreationTime.ToString("yyyy-MM-dd") && extractKeyFileInfo.LastWriteTime.ToString("yyyy-MM-dd") == fileInfo.LastWriteTime.ToString("yyyy-MM-dd"))
                    continue;
                File.Delete(extractKeyFileInfo.FullName);
            }
            try
            {
                dateTimeOffset = GetDateTimeOffset(keyFileExtension, fileInfo, extractKeyFileInfo);
                if (dateTimeOffset is null)
                    continue;
                if (fileInfo.LastWriteTime != dateTimeOffset.Value.LocalDateTime)
                {
                    File.SetLastWriteTime(fileInfo.FullName, dateTimeOffset.Value.LocalDateTime);
                    if (!result)
                        result = true;
                }
                if (string.IsNullOrEmpty(keyFileExtensionB))
                    continue;
                files = Directory.GetFiles(fileInfo.DirectoryName, keyFileExtensionB, SearchOption.TopDirectoryOnly);
                foreach (string file in files)
                {
                    fileInfo = new(file);
                    if (fileInfo.LastWriteTime != dateTimeOffset.Value.LocalDateTime)
                    {
                        File.SetLastWriteTime(fileInfo.FullName, dateTimeOffset.Value.LocalDateTime);
                        if (!result)
                            result = true;
                    }
                }
                if (string.IsNullOrEmpty(keyFileExtensionC))
                    continue;
                if (fileInfo.DirectoryName is null)
                    throw new NullReferenceException(nameof(fileInfo.DirectoryName));
                files = Directory.GetFiles(fileInfo.DirectoryName, keyFileExtensionC, SearchOption.TopDirectoryOnly);
                foreach (string file in files)
                {
                    fileInfo = new(file);
                    if (fileInfo.LastWriteTime != dateTimeOffset.Value.LocalDateTime)
                    {
                        File.SetLastWriteTime(fileInfo.FullName, dateTimeOffset.Value.LocalDateTime);
                        if (!result)
                            result = true;
                    }
                }
            }
            catch (Exception)
            {
                logger.LogInformation("<{zipFile}> is invalid!", zipFile);
                checkFile = string.Concat(zipFile, ".err");
                for (int e = 0; e < short.MaxValue; e++)
                {
                    if (!File.Exists(checkFile))
                        break;
                    checkFile = string.Concat(checkFile, e);
                }
                try
                { File.Move(zipFile, checkFile); }
                catch (Exception) { logger.LogInformation("<{zipFile}> couldn't be moved!", zipFile); }
            }
        }
        return result;
    }

    private static void ZipDirectory(ILogger<Worker> logger, string directory)
    {
        logger.LogInformation("{directory}", directory);
        string zipFile = $"{directory}.zip";
        int skipChars = directory.Length + 1;
        string[] files = Directory.GetFiles(directory, "*", SearchOption.AllDirectories);
        string[] directories = Directory.GetDirectories(directory, "*", SearchOption.AllDirectories);
        ZipArchiveMode zipArchiveMode = File.Exists(zipFile) ? ZipArchiveMode.Update : ZipArchiveMode.Create;
        for (int i = 1; i < 3; i++)
        {
            try
            {
                using ZipArchive zip = ZipFile.Open(zipFile, zipArchiveMode);
                for (int j = 0; j < directories.Length; j++)
                    _ = zip.CreateEntry($"{directories[j][skipChars..]}/");
                foreach (string file in files)
                {
                    _ = zip.CreateEntryFromFile(file, file[skipChars..]);
                    File.Delete(file);
                }
                break;
            }
            catch (Exception)
            {
                File.Delete(zipFile);
                zipArchiveMode = ZipArchiveMode.Create;
            }
        }
        Directory.Delete(directory, recursive: true);
    }

    internal static bool ZipFilesByDate(ILogger<Worker> logger, string sourceDirectory, SearchOption searchOption = SearchOption.TopDirectoryOnly, string dayFormat = "")
    {
        bool result = false;
        string key;
        bool addFile;
        string fileName;
        string? zipPath;
        FileInfo fileInfo;
        string weekOfYear;
        string[] segments;
        string[] subFiles;
        string zipDirectory;
        DateTime creationTime;
        string? directoryName;
        DateTime lastWriteTime;
        DateTime nowDateTime = DateTime.Now;
        DateTime dateTime = DateTime.MinValue;
        DateTime firstEmail = new(2019, 3, 8);
        CultureInfo cultureInfo = new("en-US");
        Dictionary<string, DateTime> weeks = [];
        Calendar calendar = cultureInfo.Calendar;
        Regex regex = LowerAlphaAlphaAndNumber();
        int ticksLength = nowDateTime.AddDays(-6).Ticks.ToString().Length;
        for (int i = 0; i < int.MaxValue; i++)
        {
            dateTime = firstEmail.AddDays(i);
            if (dateTime > nowDateTime)
                break;
            weekOfYear = calendar.GetWeekOfYear(dateTime, CalendarWeekRule.FirstDay, DayOfWeek.Sunday).ToString("00");
            key = string.Concat(dateTime.ToString("yyyy"), "_Week_", weekOfYear);
            if (!weeks.ContainsKey(key))
                weeks.Add(key, dateTime);
        }
        weekOfYear = calendar.GetWeekOfYear(nowDateTime, CalendarWeekRule.FirstDay, DayOfWeek.Sunday).ToString("00");
        string skipKey = string.Concat(nowDateTime.ToString("yyyy"), "_Week_", weekOfYear);
        Dictionary<string, List<string>> keyValuePairs = [];
        string[] topDirectories = Directory.GetDirectories(sourceDirectory, "*", SearchOption.TopDirectoryOnly);
        if (topDirectories.Length == 0)
            topDirectories = [sourceDirectory];
        foreach (string topDirectory in topDirectories)
        {
            keyValuePairs.Clear();
            directoryName = Path.GetDirectoryName(topDirectory);
            subFiles = Directory.GetFiles(topDirectory, "*", searchOption);
            zipPath = string.IsNullOrEmpty(directoryName) ? null : Path.Combine(directoryName, "ZipPath");
            zipDirectory = zipPath is not null && Directory.Exists(zipPath) ? zipPath : topDirectory;
            foreach (string subFile in subFiles)
            {
                addFile = false;
                if (subFile.EndsWith(".zip"))
                    continue;
                fileName = Path.GetFileName(subFile);
                fileInfo = new FileInfo(subFile);
                creationTime = fileInfo.CreationTime;
                if (creationTime > dateTime)
                    continue;
                lastWriteTime = fileInfo.LastWriteTime;
                if (fileName.Contains(lastWriteTime.ToString("yyyyMMdd")) || fileName.Contains(lastWriteTime.ToString("yyyy-MM-dd")) ||
                    fileName.Contains(creationTime.ToString("yyyyMMdd")) || fileName.Contains(creationTime.ToString("yyyy-MM-dd")) ||
                    fileName.Contains(lastWriteTime.ToString("yyMMdd")) || fileName.Contains(lastWriteTime.ToString("yy-MM-dd")) ||
                    fileName.Contains(creationTime.ToString("yyMMdd")) || fileName.Contains(creationTime.ToString("yy-MM-dd")) ||
                    fileName.Contains(lastWriteTime.AddDays(-1).ToString("yyyyMMdd")) || fileName.Contains(lastWriteTime.AddDays(-1).ToString("yyyy-MM-dd")) ||
                    fileName.Contains(creationTime.AddDays(-1).ToString("yyyyMMdd")) || fileName.Contains(creationTime.AddDays(-1).ToString("yyyy-MM-dd")) ||
                    fileName.Contains(lastWriteTime.AddDays(-1).ToString("yyMMdd")) || fileName.Contains(lastWriteTime.AddDays(-1).ToString("yy-MM-dd")) ||
                    fileName.Contains(creationTime.AddDays(-1).ToString("yyMMdd")) || fileName.Contains(creationTime.AddDays(-1).ToString("yy-MM-dd")))
                    addFile = true;
                if (!addFile && fileName.Length > ticksLength)
                {
                    MatchCollection matches = regex.Matches(fileName);
                    foreach (Match match in matches.Cast<Match>())
                    {
                        if (match.Value.Length != ticksLength)
                            continue;
                        if (!long.TryParse(match.Value, out long ticks))
                            continue;
                        addFile = true;
                        break;
                    }
                    if (addFile)
                        break;
                }
                if (addFile)
                {
                    weekOfYear = calendar.GetWeekOfYear(lastWriteTime, CalendarWeekRule.FirstDay, DayOfWeek.Sunday).ToString("00");
                    if (string.IsNullOrEmpty(dayFormat))
                        key = string.Concat(lastWriteTime.ToString("yyyy"), "_Week_", weekOfYear);
                    else
                        key = string.Concat(lastWriteTime.ToString("yyyy"), "_Week_", weekOfYear, "_", lastWriteTime.ToString(dayFormat));
                    if (key == skipKey)
                        continue;
                    if (!keyValuePairs.ContainsKey(key))
                        keyValuePairs.Add(key, []);
                    keyValuePairs[key].Add(subFile);
                }
            }
            foreach (KeyValuePair<string, List<string>> element in keyValuePairs)
            {
                key = Path.Combine(zipDirectory, $"{element.Key}.zip");
                if (File.Exists(key))
                    for (short i = 101; i < short.MaxValue; i++)
                    {
                        key = Path.Combine(zipDirectory, $"{element.Key}_{i}.zip");
                        if (!File.Exists(key))
                            break;
                    }
                using ZipArchive zip = ZipFile.Open(key, ZipArchiveMode.Create);
                foreach (string file in element.Value)
                {
                    _ = zip.CreateEntryFromFile(file, Path.GetFileName(file));
                    File.Delete(file);
                }
                if (zipPath is not null && Directory.Exists(zipPath) && !string.IsNullOrEmpty(directoryName))
                    try
                    { Directory.SetLastWriteTime(directoryName, DateTime.Now); }
                    catch (Exception) { }
            }
            subFiles = Directory.GetFiles(zipDirectory, "*.zip", SearchOption.TopDirectoryOnly);
            foreach (string subFile in subFiles)
            {
                fileName = Path.GetFileNameWithoutExtension(subFile);
                segments = fileName.Split('_');
                if (segments.Length > 2)
                    fileName = string.Concat(segments[0], '_', segments[1], '_', segments[2]);
                if (weeks.TryGetValue(fileName, out DateTime value))
                    try
                    {
                        if (!result)
                            result = true;
                        File.SetLastWriteTime(subFile, value);
                    }
                    catch (Exception) { }
            }
            if (topDirectory != sourceDirectory)
                try
                { HelperDeleteEmptyDirectories.DeleteEmptyDirectories(logger, topDirectory); }
                catch (Exception) { }
            logger.LogInformation("{topDirectory}", topDirectory);
        }
        return result;
    }

    internal static bool ExportNuspecAndSetDateFromZipEntry(ILogger<Worker> logger, string[] files, bool renameToLower) =>
        ExtractKeyFileAndSetDateFromZipEntry(logger, files, ".nuspec", "icon", "readme", renameToLower);

    internal static bool ExtractKeyFileAndSetDateFromZipEntry(ILogger<Worker> logger, string sourceDirectory, SearchOption searchOption = SearchOption.AllDirectories, bool renameToLower = false)
    {
        bool result = false;
        bool loop;
        string[] zipFiles;
        string searchPattern;
        string keyFileExtension;
        string keyFileExtensionB;
        string keyFileExtensionC;
        if (!Directory.Exists(sourceDirectory))
            _ = Directory.CreateDirectory(sourceDirectory);
        for (int i = 1; i < 3; i++)
        {
            (searchPattern, keyFileExtension, keyFileExtensionB, keyFileExtensionC) = i switch
            {
                1 => ("*.nupkg", ".nuspec", "icon", "readme"),
                2 => ("*.vsix", ".vsixmanifest", string.Empty, string.Empty),
                _ => throw new NotSupportedException()
            };
            zipFiles = Directory.GetFiles(sourceDirectory, searchPattern, searchOption);
            loop = ExtractKeyFileAndSetDateFromZipEntry(logger, zipFiles, keyFileExtension, keyFileExtensionB, keyFileExtensionC, renameToLower);
            if (loop && !result)
                result = true;
        }
        return result;
    }

    internal static void ZipFilesByDirectoryWithFile(ILogger<Worker> logger, string sourceDirectory)
    {
        string[] files1;
        string[] files2;
        string[] files3;
        string[] files4;
        string[] files5;
        string[] directories2;
        string[] directories3;
        string[] directories4;
        string[] directories5;
        string[] directories1 = Directory.GetDirectories(sourceDirectory, "*", SearchOption.TopDirectoryOnly);
        foreach (string directory1 in directories1)
        {
            files1 = Directory.GetFiles(directory1, "*", SearchOption.TopDirectoryOnly).Where(l => !l.EndsWith(".zip")).ToArray();
            if (files1.Length > 0)
            {
                ZipDirectory(logger, directory1);
                continue;
            }
            directories2 = Directory.GetDirectories(directory1, "*", SearchOption.TopDirectoryOnly);
            foreach (string directory2 in directories2)
            {
                files2 = Directory.GetFiles(directory2, "*", SearchOption.TopDirectoryOnly).Where(l => !l.EndsWith(".zip")).ToArray();
                if (files2.Length > 0)
                {
                    ZipDirectory(logger, directory2);
                    continue;
                }
                directories3 = Directory.GetDirectories(directory2, "*", SearchOption.TopDirectoryOnly);
                foreach (string directory3 in directories3)
                {
                    files3 = Directory.GetFiles(directory3, "*", SearchOption.TopDirectoryOnly).Where(l => !l.EndsWith(".zip")).ToArray();
                    if (files3.Length > 0)
                    {
                        ZipDirectory(logger, directory3);
                        continue;
                    }
                    directories4 = Directory.GetDirectories(directory3, "*", SearchOption.TopDirectoryOnly);
                    foreach (string directory4 in directories4)
                    {
                        files4 = Directory.GetFiles(directory4, "*", SearchOption.TopDirectoryOnly).Where(l => !l.EndsWith(".zip")).ToArray();
                        if (files4.Length > 0)
                        {
                            ZipDirectory(logger, directory4);
                            continue;
                        }
                        directories5 = Directory.GetDirectories(directory4, "*", SearchOption.TopDirectoryOnly);
                        foreach (string directory5 in directories5)
                        {
                            files5 = Directory.GetFiles(directory5, "*", SearchOption.TopDirectoryOnly);
                            if (files5.Length == 0)
                                throw new NotSupportedException("Files are too deep!");
                            else
                            {
                                ZipDirectory(logger, directory5);
                                continue;
                            }
                        }
                    }
                }
            }
        }
    }

}