using Humanizer;
using ShellProgressBar;
using System.Buffers;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Stateless.Methods;

namespace View_by_Distance.Map.Models.Stateless;

internal record Record(int DirectoryNumber,
                       bool? IsDefault,
                       int? LinksCount,
                       FilePath MappedFaceFilePath,
                       string? PersonDisplayDirectoryName,
                       string PersonKeyFormatted);

internal abstract class DistanceLogic
{

    internal record TicksDirectory(DateTime AlternateDirectoryDateTime,
                                   string Directory,
                                   DateTime DirectoryDateTime,
                                   string DirectoryName,
                                   bool? IsLocationContainerDebugDirectory,
                                   float? TotalDays);

    private static void MoveTo(string actionDirectory, TicksDirectory ticksDirectory, string directory, string personKeyFormatted, string yearDirectoryName, string alphaDirectoryName, string[] files, string[] facesFileNames)
    {
        string checkFile;
        string actionDirectoryName = Path.GetFileName(actionDirectory);
        string checkDirectory = actionDirectoryName.StartsWith("y", StringComparison.CurrentCultureIgnoreCase) ? Path.Combine(ticksDirectory.Directory, personKeyFormatted, yearDirectoryName, alphaDirectoryName) : Path.Combine(directory, actionDirectoryName);
        if (!Directory.Exists(checkDirectory))
            _ = Directory.CreateDirectory(checkDirectory);
        foreach (string file in files)
        {
            if (facesFileNames.Contains(file))
            {
                checkFile = Path.Combine(checkDirectory, Path.GetFileName(file));
                if (File.Exists(checkFile))
                    continue;
                File.Move(file, checkFile);
                continue;
            }
            File.Delete(file);
        }
    }

    private static void MoveFiles(string personKeyFormatted, string personKeyDirectory, string newestPersonKeyFormatted, string newestPersonKeyDirectory)
    {
        string[] files;
        string checkFile;
        string? checkDirectory;
        string[] directories = Directory.GetDirectories(personKeyDirectory, "*", SearchOption.TopDirectoryOnly);
        foreach (string directory in directories)
        {
            checkDirectory = Path.Combine(newestPersonKeyDirectory, Path.GetFileName(directory));
            if (!Directory.Exists(checkDirectory))
                Directory.Move(directory, checkDirectory);
            else
            {
                files = Directory.GetFiles(directory, "*", SearchOption.AllDirectories);
                foreach (string file in files)
                {
                    if (file.Split(personKeyFormatted).Length != 2 || file.Contains(newestPersonKeyFormatted))
                        continue;
                    checkFile = file.Replace(personKeyFormatted, newestPersonKeyFormatted);
                    checkDirectory = Path.GetDirectoryName(checkFile);
                    if (checkDirectory is null)
                        continue;
                    if (File.Exists(checkFile))
                        continue;
                    if (!Directory.Exists(checkDirectory))
                        _ = Directory.CreateDirectory(checkDirectory);
                    File.Move(file, checkFile);
                }
            }
        }
        _ = IPath.DeleteEmptyDirectories(personKeyDirectory);
    }

    private static List<TicksDirectory> UpdateDateVerifyAndGetTicksDirectories(Configuration configuration, string eDistanceContentDirectory)
    {
        List<TicksDirectory> results = [];
        float? totalDays;
        long? next = null;
        string? checkDirectory;
        string ticksDirectoryName;
        DateTime directoryDateTime;
        DirectoryInfo directoryInfo;
        long? lastDirectoryTicks = null;
        DateTime dateTime = DateTime.Now;
        DateTime alternateDirectoryDateTime;
        bool? isLocationContainerDebugDirectory;
        long month = dateTime.AddMonths(1).Ticks - dateTime.Ticks;
        for (int i = 1; i < 5; i++)
            _ = IPath.DeleteEmptyDirectories(eDistanceContentDirectory);
        if (!Directory.Exists(eDistanceContentDirectory))
            _ = Directory.CreateDirectory(eDistanceContentDirectory);
        string[] ticksDirectories = Directory.GetDirectories(eDistanceContentDirectory, "*", SearchOption.TopDirectoryOnly);
        foreach (string ticksDirectory in ticksDirectories)
        {
            ticksDirectoryName = Path.GetFileName(ticksDirectory);
            if (ticksDirectoryName.Length < 3)
                continue;
            if (!long.TryParse(ticksDirectoryName, out long directoryTicks))
                throw new NotSupportedException();
            if (next is null)
                next = new DateTime(directoryTicks).Ticks;
            else
            {
                next += month;
                checkDirectory = Path.GetDirectoryName(ticksDirectory);
                if (string.IsNullOrEmpty(checkDirectory))
                {
                    if (string.IsNullOrEmpty(checkDirectory))
                        continue;
                    checkDirectory = Path.Combine(checkDirectory, next.Value.ToString());
                    if (ticksDirectory == checkDirectory || !checkDirectory.EndsWith(configuration.LocationContainerDirectoryPattern))
                        continue;
                    Directory.Move(ticksDirectory, checkDirectory);
                    continue;
                }
            }
            directoryInfo = new(ticksDirectory);
            directoryDateTime = new DateTime(directoryTicks);
            if (directoryInfo.CreationTime.Ticks != directoryTicks)
                Directory.SetCreationTime(ticksDirectory, new DateTime(directoryTicks));
            if (directoryInfo.LastWriteTime.Ticks != directoryTicks)
                Directory.SetLastWriteTime(ticksDirectory, new DateTime(directoryTicks));
            alternateDirectoryDateTime = new DateTime(directoryDateTime.Year, directoryDateTime.Month, directoryDateTime.Day).AddMonths(1);
            isLocationContainerDebugDirectory = configuration.LocationContainerDebugDirectory is null ? null : ticksDirectoryName.EndsWith(configuration.LocationContainerDebugDirectory);
            totalDays = lastDirectoryTicks is null || new TimeSpan(dateTime.Ticks - directoryTicks).TotalDays < 1 ? null : (float)new TimeSpan(directoryTicks - lastDirectoryTicks.Value).TotalDays;
            results.Add(new(alternateDirectoryDateTime, ticksDirectory, new(directoryTicks), ticksDirectoryName, isLocationContainerDebugDirectory, totalDays));
            if (directoryDateTime.Hour == 0 && directoryDateTime.Minute == 0 && directoryDateTime.Second == 0)
                continue;
            lastDirectoryTicks = directoryTicks;
        }
        string[] compare = (from l in results where l.TotalDays is not null and < 9.95f select l.Directory).ToArray();
        if (compare.Length > 0 && configuration.ReMap)
            throw new Exception($"Please Consolidate <{string.Join(Environment.NewLine, compare)}>");
        return results;
    }

    private static void Individually(Configuration configuration, TicksDirectory ticksDirectory, string directory)
    {
        bool isDefault;
        string[] files;
        FileInfo[] collection;
        string[] facesFileNames;
        string yearDirectoryName;
        string[] yearDirectories;
        string alphaDirectoryName;
        string matchDirectoryName;
        string personKeyFormatted;
        string[] alphaDirectories;
        string[] matchDirectories;
        string[] actionDirectories;
        string personDisplayDirectory;
        string[] personKeyDirectories;
        string[] segmentCDirectories = Directory.GetDirectories(directory, "*", SearchOption.TopDirectoryOnly);
        foreach (string segmentCDirectory in segmentCDirectories)
        {
            personKeyDirectories = Directory.GetDirectories(segmentCDirectory, "*", SearchOption.TopDirectoryOnly);
            foreach (string personKeyDirectory in personKeyDirectories)
            {
                personKeyFormatted = Path.GetFileName(personKeyDirectory);
                yearDirectories = Directory.GetDirectories(personKeyDirectory, "*", SearchOption.TopDirectoryOnly);
                foreach (string yearDirectory in yearDirectories)
                {
                    yearDirectoryName = Path.GetFileName(yearDirectory);
                    if (yearDirectoryName.StartsWith('='))
                        Directory.Move(yearDirectory, yearDirectory.Replace('=', '~'));
                }
                yearDirectories = Directory.GetDirectories(personKeyDirectory, "*", SearchOption.TopDirectoryOnly);
                foreach (string yearDirectory in yearDirectories)
                {
                    yearDirectoryName = Path.GetFileName(yearDirectory);
                    matchDirectories = Directory.GetDirectories(yearDirectory, "*", SearchOption.TopDirectoryOnly);
                    alphaDirectories = matchDirectories.Where(l => !long.TryParse(Path.GetFileName(l), out long a)).ToArray();
                    if (alphaDirectories.Length == 0)
                        continue;
                    alphaDirectoryName = Path.GetFileName(alphaDirectories[0]);
                    foreach (string matchDirectory in matchDirectories)
                    {
                        matchDirectoryName = Path.GetFileName(matchDirectory);
                        files = Directory.GetFiles(matchDirectory, "*", SearchOption.TopDirectoryOnly);
                        if (files.Length != 4)
                            continue;
                        collection = files.Select(l => new FileInfo(l)).ToArray();
                        isDefault = IPerson.IsDefaultName(alphaDirectoryName) && IPersonBirthday.IsCounterPersonYear(personKeyFormatted[..4]);
                        if (isDefault)
                            facesFileNames = (from l in collection where l.Extension == configuration.FacesFileNameExtension select l.FullName).ToArray();
                        else
                            facesFileNames = (from l in collection where l.Extension == configuration.FacesFileNameExtension && l.Name.Contains(matchDirectoryName) select l.FullName).ToArray();
                        if (facesFileNames.Length == 0)
                            continue;
                        personDisplayDirectory = Path.Combine(matchDirectory, alphaDirectoryName);
                        if (!Directory.Exists(personDisplayDirectory) || !Directory.Exists(matchDirectory))
                            continue;
                        _ = Process.Start("explorer", matchDirectory);
                        for (int i = 0; i < int.MaxValue; i++)
                        {
                            Thread.Sleep(500);
                            actionDirectories = Directory.GetDirectories(matchDirectory, "*", SearchOption.TopDirectoryOnly).Where(l => l != personDisplayDirectory && !l.EndsWith("Maybe")).ToArray();
                            if (actionDirectories.Length > 0)
                            {
                                MoveTo(actionDirectories[0], ticksDirectory, directory, personKeyFormatted, yearDirectoryName, alphaDirectoryName, files, facesFileNames);
                                break;
                            }
                        }
                    }
                }
            }
        }
    }

    private static List<Record> GetRecords(Shared.Models.Properties.IPropertyConfiguration propertyConfiguration, Configuration configuration, bool? isDefault, string[] files, int directoryNumber, string personKeyFormatted, int? linksCount, List<string> distinct, string? personDisplayDirectoryName)
    {
        List<Record> results = [];
        string fileName;
        string checkFile;
        FilePath filePath;
        FileHolder fileHolder;
        int? wholePercentages;
        foreach (string file in files)
        {
            if (file.EndsWith(".lnk"))
                continue;
            fileHolder = IFileHolder.Get(file);
            filePath = FilePath.Get(propertyConfiguration, fileHolder, index: null);
            if (filePath.Id is null)
                continue;
            wholePercentages = IMapping.GetWholePercentages(configuration.FacesFileNameExtension, filePath);
            if (wholePercentages is null)
                continue;
            fileName = Path.GetFileName(file);
            if (distinct.Contains(fileName))
            {
                checkFile = $"{file}.dup";
                if (File.Exists(checkFile))
                    continue;
                File.Move(file, checkFile);
                continue;
            }
            distinct.Add(fileName);
            results.Add(new(directoryNumber, isDefault, linksCount, filePath, personDisplayDirectoryName, personKeyFormatted));
        }
        return results;
    }

    private static string[] RenameBirth(string[] files)
    {
        List<string> results = [];
        string checkFile;
        foreach (string file in files)
        {
            if (file.EndsWith(".brt"))
            {
                results.Add(file);
                continue;
            }
            checkFile = $"{file}.brt";
            if (File.Exists(checkFile))
            {
                results.Add(file);
                continue;
            }
            File.Move(file, checkFile);
            results.Add(checkFile);
        }
        return results.ToArray();
    }

    private static void MovedToNewestPersonKeyFormatted(string personKeyFormatted, string newestPersonKeyFormatted, TicksDirectory ticksDirectory, string personKeyDirectory)
    {
        string newestPersonKeyDirectory = Path.Combine(ticksDirectory.Directory, newestPersonKeyFormatted);
        if (Directory.Exists(newestPersonKeyDirectory))
            MoveFiles(personKeyFormatted, personKeyDirectory, newestPersonKeyFormatted, newestPersonKeyDirectory);
        else
            Directory.Move(personKeyDirectory, newestPersonKeyDirectory);
    }

    private static int? GetLinksCount(string yearDirectory)
    {
        int? result;
        string[] yearDirectoryNameSegments = Path.GetFileName(yearDirectory).Split('-');
        if (yearDirectoryNameSegments.Length != 3)
            result = null;
        else
        {
            string lastSegment = yearDirectoryNameSegments[^1];
            if (lastSegment.Length != 3 || !lastSegment.All(l => l == lastSegment[0]))
                result = null;
            else
                result = lastSegment[0] - 65;
        }
        return result;
    }

    internal static List<Record> DeleteEmptyDirectoriesAndGetCollection(Shared.Models.Properties.IPropertyConfiguration propertyConfiguration, Configuration configuration, long ticks, string eDistanceContentDirectory, ReadOnlyDictionary<string, string> personKeyFormattedToNewestPersonKeyFormatted, ReadOnlyCollection<string> personKeyFormattedCollection)
    {
        List<Record> results = [];
        bool check;
        string message;
        string[] files;
        bool? isDefault;
        int? linksCount;
        int totalSeconds;
        DateTime dateTime;
        TimeSpan timeSpan;
        int directoryNumber;
        string? checkDirectory;
        ProgressBar progressBar;
        string[] yearDirectories;
        string personKeyFormatted;
        List<string> distinct = [];
        string? personFirstInitial;
        bool isReservedDirectoryName;
        string[] personNameDirectories;
        string? newestPersonKeyFormatted;
        string? personDisplayDirectoryName;
        string[] personNameLinkDirectories;
        string? personFirstInitialDirectory;
        List<TicksDirectory> ticksDirectories;
        string[] personKeyFormattedDirectories;
        string manualCopyHumanized = nameof(Shared.Models.Stateless.IMapLogic.ManualCopy).Humanize(LetterCasing.Title);
        string forceSingleImageHumanized = nameof(Shared.Models.Stateless.IMapLogic.ForceSingleImage).Humanize(LetterCasing.Title);
        ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true };
        for (int i = 1; i < 6; i++)
        {
            check = false;
            results.Clear();
            distinct.Clear();
            directoryNumber = 0;
            ticksDirectories = UpdateDateVerifyAndGetTicksDirectories(configuration, eDistanceContentDirectory);
            totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
            message = $"{i}) {ticksDirectories.Count:000} compile from and clean ticks Director(ies) - B - {totalSeconds} total second(s)";
            progressBar = new(ticksDirectories.Count, message, options);
            foreach (TicksDirectory ticksDirectory in ticksDirectories)
            {
                if (i == 1)
                    progressBar.Tick();
                personKeyFormattedDirectories = Directory.GetDirectories(ticksDirectory.Directory, "*", SearchOption.TopDirectoryOnly);
                foreach (string personKeyFormattedDirectory in personKeyFormattedDirectories)
                {
                    personKeyFormatted = Path.GetFileName(personKeyFormattedDirectory);
                    isReservedDirectoryName = personKeyFormatted.StartsWith(nameof(Shared.Models.Stateless.IMapLogic.Sorting)) || personKeyFormatted.StartsWith(nameof(Shared.Models.Stateless.IMapLogic.Mapping)) || personKeyFormatted.StartsWith(nameof(Shared.Models.Stateless.IMapLogic.ManualCopy));
                    if (!isReservedDirectoryName && personKeyFormatted.StartsWith(nameof(Shared.Models.Stateless.IMapLogic.Individually)))
                    {
                        Individually(configuration, ticksDirectory, personKeyFormattedDirectory);
                        throw new Exception($"B) Move personKey directories up one from {nameof(Shared.Models.Stateless.IMapLogic.Sorting)} and delete {nameof(Shared.Models.Stateless.IMapLogic.Sorting)} directory!");
                    }
                    _ = personKeyFormattedToNewestPersonKeyFormatted.TryGetValue(personKeyFormatted, out newestPersonKeyFormatted);
                    if (personKeyFormattedToNewestPersonKeyFormatted.Count > 0 && newestPersonKeyFormatted is null)
                    {
                        timeSpan = new TimeSpan(DateTime.Now.Ticks - ticksDirectory.DirectoryDateTime.Ticks);
                        if (timeSpan.TotalDays > 6)
                            throw new Exception($"{configuration.MappingDefaultName} <{ticksDirectory.DirectoryDateTime}> are only allowed within x days!");
                    }
                    yearDirectories = Directory.GetDirectories(personKeyFormattedDirectory, "*", SearchOption.TopDirectoryOnly);
                    foreach (string yearDirectory in yearDirectories)
                    {
                        if (check && !Directory.Exists(yearDirectory))
                            continue;
                        if (ticksDirectory.IsLocationContainerDebugDirectory is null || !ticksDirectory.IsLocationContainerDebugDirectory.Value)
                            linksCount = null;
                        else
                            linksCount = GetLinksCount(yearDirectory);
                        if (ticksDirectory.DirectoryName != configuration.LocationContainerDebugDirectory)
                        {
                            files = Directory.GetFiles(yearDirectory, "*", SearchOption.TopDirectoryOnly);
                            foreach (string file in files)
                                File.Delete(file);
                        }
                        if (ticksDirectory.DirectoryName == configuration.LocationContainerDebugDirectory)
                        {
                            isDefault = null;
                            personDisplayDirectoryName = null;
                            files = Directory.GetFiles(yearDirectory, "*", SearchOption.TopDirectoryOnly);
                            results.AddRange(GetRecords(propertyConfiguration, configuration, isDefault, files, directoryNumber, personKeyFormatted, linksCount, distinct, personDisplayDirectoryName));
                            files = Directory.GetFiles(yearDirectory, "*.lnk", SearchOption.AllDirectories);
                            foreach (string file in files)
                                File.Delete(file);
                            continue;
                        }
                        personNameDirectories = Directory.GetDirectories(yearDirectory, "*", SearchOption.TopDirectoryOnly);
                        if (personNameDirectories.Length > 1)
                            throw new NotSupportedException("Try deleting *.lnk files!");
                        foreach (string personNameDirectory in personNameDirectories)
                        {
                            directoryNumber++;
                            personDisplayDirectoryName = Path.GetFileName(personNameDirectory);
                            isDefault = IPerson.IsDefaultName(personDisplayDirectoryName) && IPersonBirthday.IsCounterPersonYear(personKeyFormatted[..4]);
                            if (isDefault.Value && personDisplayDirectoryName.Length == 1)
                            {
                                if (personKeyFormatted.Length != configuration.PersonBirthdayFormat.Length || !DateTime.TryParseExact(personKeyFormatted, configuration.PersonBirthdayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime))
                                    continue;
                                checkDirectory = Path.Combine(yearDirectory, $"X+{dateTime.Ticks}");
                                if (Directory.Exists(checkDirectory))
                                {
                                    Directory.Delete(yearDirectory, recursive: true);
                                    continue;
                                }
                                Directory.Move(personNameDirectory, checkDirectory);
                                if (!check)
                                    check = true;
                                continue;
                            }
                            if (isDefault.Value && (ticksDirectory.DirectoryDateTime.Hour != 0 || ticksDirectory.DirectoryDateTime.Minute != 0 || ticksDirectory.DirectoryDateTime.Second != 0))
                            {
                                checkDirectory = Path.GetDirectoryName(ticksDirectory.Directory);
                                if (checkDirectory is null)
                                    continue;
                                checkDirectory = Path.Combine(checkDirectory, ticksDirectory.AlternateDirectoryDateTime.Ticks.ToString());
                                if (!Directory.Exists(checkDirectory))
                                    _ = Directory.CreateDirectory(checkDirectory);
                                checkDirectory = Path.Combine(checkDirectory, personKeyFormatted);
                                if (!Directory.Exists(checkDirectory))
                                {
                                    Directory.Move(personKeyFormattedDirectory, checkDirectory);
                                    if (!check)
                                        check = true;
                                    break;
                                }
                            }
                            files = Directory.GetFiles(personNameDirectory, "*", SearchOption.TopDirectoryOnly);
                            if (isReservedDirectoryName && files.Length > 0)
                                throw new Exception($"Move personKey directories up one from {nameof(Shared.Models.Stateless.IMapLogic.Sorting)} and delete {nameof(Shared.Models.Stateless.IMapLogic.Sorting)} directory!");
                            if (personKeyFormatted == manualCopyHumanized && files.Length > 0)
                                throw new Exception($"Move personKey directories up one from {manualCopyHumanized} and delete {manualCopyHumanized} directory!");
                            if (personKeyFormatted == forceSingleImageHumanized && files.Length > 0)
                                throw new Exception($"Move personKey directories up one from {forceSingleImageHumanized} and delete {forceSingleImageHumanized} directory!");
                            if (!isDefault.Value)
                            {
                                if (personKeyFormattedToNewestPersonKeyFormatted.Count > 0 && newestPersonKeyFormatted is null)
                                    files = RenameBirth(files);
                                else if (newestPersonKeyFormatted is not null && personKeyFormatted != newestPersonKeyFormatted)
                                {
                                    if (!check)
                                        check = true;
                                    MovedToNewestPersonKeyFormatted(personKeyFormatted, newestPersonKeyFormatted, ticksDirectory, personKeyFormattedDirectory);
                                    continue;
                                }
                            }
                            if (personKeyFormatted.Length != configuration.PersonBirthdayFormat.Length)
                                continue;
                            if (personDisplayDirectoryName.Length == 1 || isDefault.Value || !personKeyFormattedCollection.Contains(personKeyFormatted))
                                personFirstInitialDirectory = personNameDirectory;
                            else
                            {
                                personFirstInitial = personDisplayDirectoryName[..1];
                                if (personFirstInitial.All(char.IsDigit))
                                {
                                    foreach (string file in files)
                                        File.Delete(file);
                                    files = Directory.GetFiles(personNameDirectory, "*", SearchOption.AllDirectories);
                                    foreach (string file in files)
                                        File.Delete(file);
                                    _ = IPath.DeleteEmptyDirectories(personNameDirectory);
                                    continue;
                                }
                                personFirstInitialDirectory = Path.Combine(yearDirectory, personFirstInitial.ToString());
                                if (Directory.Exists(personFirstInitialDirectory))
                                    throw new Exception("Forgot to ...");
                                Directory.Move(personNameDirectory, personFirstInitialDirectory);
                                files = Directory.GetFiles(personFirstInitialDirectory, "*", SearchOption.TopDirectoryOnly);
                            }
                            results.AddRange(GetRecords(propertyConfiguration, configuration, isDefault, files, directoryNumber, personKeyFormatted, linksCount, distinct, personDisplayDirectoryName));
                            personNameLinkDirectories = Directory.GetDirectories(personFirstInitialDirectory, "*", SearchOption.TopDirectoryOnly);
                            foreach (string personNameLinkDirectory in personNameLinkDirectories)
                            {
                                files = Directory.GetFiles(personNameLinkDirectory, "*", SearchOption.TopDirectoryOnly);
                                foreach (string file in files)
                                {
                                    if (!file.EndsWith(".lnk"))
                                        continue;
                                    File.Delete(file);
                                }
                                _ = IPath.DeleteEmptyDirectories(personNameLinkDirectory);
                            }
                            _ = IPath.DeleteEmptyDirectories(personFirstInitialDirectory);
                        }
                        _ = IPath.DeleteEmptyDirectories(yearDirectory);
                    }
                    _ = IPath.DeleteEmptyDirectories(personKeyFormattedDirectory);
                }
                _ = IPath.DeleteEmptyDirectories(ticksDirectory.Directory);
                _ = IPath.DeleteEmptyDirectories(ticksDirectory.Directory);
            }
            progressBar.Dispose();
            if (check)
                continue;
            break;
        }
        return results;
    }

}