using ShellProgressBar;
using System.Collections.ObjectModel;
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Stateless.Methods;

namespace View_by_Distance.Map.Models.Stateless;

internal abstract class RelationLogic
{

    internal record Group(string Key, long PersonKey, ReadOnlyCollection<LocationContainer<MetadataExtractor.Directory>> RelationContainersCollection);

    private static Dictionary<long, Dictionary<int, List<LocationContainer<MetadataExtractor.Directory>>>> GetPersonKeyTo(Configuration configuration, List<LocationContainer<MetadataExtractor.Directory>> locationContainers)
    {
        List<LocationContainer<MetadataExtractor.Directory>>? collection;
        Dictionary<int, List<LocationContainer<MetadataExtractor.Directory>>>? yearTo;
        Dictionary<long, Dictionary<int, List<LocationContainer<MetadataExtractor.Directory>>>> personKeyTo = [];
        foreach (LocationContainer<MetadataExtractor.Directory> locationContainer in locationContainers)
        {
            if (!locationContainer.FromDistanceContent)
                continue;
            if (!locationContainer.File.Contains(configuration.LocationContainerDirectoryPattern))
                continue;
            if (!personKeyTo.TryGetValue(locationContainer.PersonKey, out yearTo))
            {
                personKeyTo.Add(locationContainer.PersonKey, []);
                if (!personKeyTo.TryGetValue(locationContainer.PersonKey, out yearTo))
                    throw new Exception();
            }
            if (!yearTo.TryGetValue(locationContainer.CreationDateOnly.Year, out collection))
            {
                yearTo.Add(locationContainer.CreationDateOnly.Year, []);
                if (!yearTo.TryGetValue(locationContainer.CreationDateOnly.Year, out collection))
                    throw new Exception();
            }
            collection.Add(locationContainer);
        }
        return personKeyTo;
    }

    private static ReadOnlyCollection<Group> GetGroups(Configuration configuration, List<LocationContainer<MetadataExtractor.Directory>> locationContainers)
    {
        List<Group> results = [];
        string key;
        int lastIndex;
        List<int> years = [];
        List<int> indices = [];
        List<(int Index, int Year)> sort = [];
        List<LocationContainer<MetadataExtractor.Directory>> collection = [];
        KeyValuePair<int, List<LocationContainer<MetadataExtractor.Directory>>> keyValue;
        Dictionary<long, Dictionary<int, List<LocationContainer<MetadataExtractor.Directory>>>> personKeyTo = GetPersonKeyTo(configuration, locationContainers);
        foreach (KeyValuePair<long, Dictionary<int, List<LocationContainer<MetadataExtractor.Directory>>>> keyValuePair in personKeyTo)
        {
            sort.Clear();
            years.Clear();
            indices.Clear();
            for (int i = 0; i < keyValuePair.Value.Count; i++)
                sort.Add(new(i, keyValuePair.Value.ElementAt(i).Key));
            if (sort.Count == 0)
                continue;
            foreach ((int index, int _) in sort.OrderBy(l => l.Year))
                indices.Add(index);
            lastIndex = indices[^1];
            foreach (int index in indices)
            {
                keyValue = keyValuePair.Value.ElementAt(index);
                if (keyValue.Value.Count == 0)
                    continue;
                years.Add(keyValue.Key);
                collection.AddRange(keyValue.Value);
                if (index != lastIndex && years.Count < 6 && years.Sum() > configuration.LocationContainerDistanceGroupMinimum)
                    continue;
                if (years.Count == 1)
                    key = keyValue.Key.ToString();
                else
                    key = $"{years.Min()}-{keyValue.Key}";
                if (collection.Count == 0)
                    continue;
                results.Add(new(key, collection[0].PersonKey, new(collection)));
                collection = [];
                years.Clear();
            }
        }
        return new(results);
    }

    private static ReadOnlyDictionary<string, string> MoveFiles(Configuration configuration, string key, bool isCounterPersonYear, string? displayDirectoryName, ReadOnlyCollection<RelationContainer> relationContainers, List<string> linked1, List<string> linked2, List<string> linked3, List<string> linked4, List<string> linked5, List<string> linked6, List<string> linked7, List<string> linked8, List<string> linked9)
    {
        Dictionary<string, string> results = [];
        string value;
        string checkFile;
        string debugFile;
        string checkDirectory;
        string? yearDirectory;
        string? personNameDirectory;
        string? maybeTicksDirectoryName;
        string? personNameDirectoryName;
        string? personKeyFormattedDirectory;
        foreach ((FileHolder fileHolder, _) in relationContainers)
        {
            personNameDirectory = fileHolder.DirectoryName;
            yearDirectory = Path.GetDirectoryName(personNameDirectory);
            personNameDirectoryName = Path.GetFileName(personNameDirectory);
            personKeyFormattedDirectory = Path.GetDirectoryName(yearDirectory);
            maybeTicksDirectoryName = Path.GetFileName(personKeyFormattedDirectory);
            if (string.IsNullOrEmpty(personNameDirectory) || string.IsNullOrEmpty(yearDirectory) || string.IsNullOrEmpty(personKeyFormattedDirectory) || string.IsNullOrEmpty(personNameDirectoryName))
                continue;
            if (linked9.Contains(fileHolder.FullName))
                value = "JJJ";
            else if (linked8.Contains(fileHolder.FullName))
                value = "III";
            else if (linked7.Contains(fileHolder.FullName))
                value = "HHH";
            else if (linked6.Contains(fileHolder.FullName))
                value = "GGG";
            else if (linked5.Contains(fileHolder.FullName))
                value = "FFF";
            else if (linked4.Contains(fileHolder.FullName))
                value = "EEE";
            else if (linked3.Contains(fileHolder.FullName))
                value = "DDD";
            else if (linked2.Contains(fileHolder.FullName))
                value = "CCC";
            else if (linked1.Contains(fileHolder.FullName))
                value = "BBB";
            else
                value = "AAA";
            if (maybeTicksDirectoryName == configuration.LocationContainerDebugDirectory)
                checkDirectory = Path.Combine(yearDirectory, $"{key}-{value}");
            else
            {
                if (!string.IsNullOrEmpty(configuration.LocationContainerDebugDirectory))
                    continue;
                checkDirectory = Path.Combine(personKeyFormattedDirectory, $"{key}-{value}");
            }
            if (maybeTicksDirectoryName != configuration.LocationContainerDebugDirectory)
            {
                if (isCounterPersonYear || string.IsNullOrEmpty(displayDirectoryName))
                    checkDirectory = Path.Combine(checkDirectory, personNameDirectoryName);
                else
                    checkDirectory = Path.Combine(checkDirectory, displayDirectoryName[0].ToString());
            }
            if (checkDirectory == personNameDirectory)
                continue;
            if (!Directory.Exists(checkDirectory))
                _ = Directory.CreateDirectory(checkDirectory);
            checkFile = Path.Combine(checkDirectory, fileHolder.Name);
            if (File.Exists(checkFile))
                continue;
            results.Add(fileHolder.FullName, checkFile);
            File.Move(fileHolder.FullName, checkFile);
            debugFile = $"{fileHolder.FullName[..^4]}.gif";
            if (File.Exists(debugFile))
            {
                checkFile = Path.Combine(checkDirectory, $"{Path.GetFileName(fileHolder.FullName)[..^4]}.gif");
                if (File.Exists(checkFile))
                    continue;
                File.Move(debugFile, checkFile);
            }
            if (maybeTicksDirectoryName == configuration.LocationContainerDebugDirectory && !string.IsNullOrEmpty(displayDirectoryName))
            {
                checkDirectory = Path.Combine(checkDirectory, displayDirectoryName);
                if (!Directory.Exists(checkDirectory))
                    _ = Directory.CreateDirectory(checkDirectory);
            }
        }
        return new(results);
    }

    private static string? GetDisplayDirectoryName(ReadOnlyDictionary<long, List<PersonContainer>> readOnlyPersonKeyToPersonContainerCollection, ReadOnlyDictionary<string, PersonContainer> readOnlyPersonKeyFormattedToPersonContainer, long personKey, string personKeyFormatted)
    {
        string? result;
        PersonContainer? personContainer;
        List<PersonContainer>? collection;
        _ = readOnlyPersonKeyToPersonContainerCollection.TryGetValue(personKey, out collection);
        if (collection is not null)
            result = collection[0].DisplayDirectoryName;
        else
        {
            if (!readOnlyPersonKeyFormattedToPersonContainer.TryGetValue(personKeyFormatted, out personContainer))
                result = null;
            else
                result = personContainer.DisplayDirectoryName;
        }
        return result;
    }

    private static int GetTake(int locationContainerDistanceTake, int count)
    {
        int result = locationContainerDistanceTake;
        int subtract = (int)(locationContainerDistanceTake * .05);
        if (subtract < 1)
            subtract = 1;
        if (count > 9000)
            result -= subtract;
        if (count > 8000)
            result -= subtract;
        if (count > 7000)
            result -= subtract;
        if (count > 6000)
            result -= subtract;
        if (count > 5000)
            result -= subtract;
        if (count > 4000)
            result -= subtract;
        if (count > 3000)
            result -= subtract;
        if (count > 2000)
            result -= subtract;
        if (count > 1000)
            result -= subtract;
        if (result < 3)
            result = 3;
        return result;
    }

    private static void WriteVsCodeFiles(string eDistanceContentDirectory, string? displayDirectoryName, string directory)
    {
        string json;
        string vsCodeDirectory = Path.Combine(directory, ".vscode");
        if (!Directory.Exists(vsCodeDirectory))
            _ = Directory.CreateDirectory(vsCodeDirectory);
        if (displayDirectoryName is not null)
            File.WriteAllText(Path.Combine(directory, $"_ {displayDirectoryName}.txt"), string.Empty);
        json = "{ \"[markdown]\": { \"editor.wordWrap\": \"off\" }, \"foam.links.hover.enable\": false, \"foam.graph.style\": { \"background\": \"#202020\", \"node\": { \"note\": \"#f2cb1d\", \"distance\": \"green\", \"image\": \"orange\", \"placeholder\": \"white\", } } }";
        _ = IPath.WriteAllText(Path.Combine(vsCodeDirectory, "settings.json"), json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null);
        json = string.Concat("{ \"version\": \"2.0.0\", \"tasks\": [ { \"label\": \"MKLink\", \"type\": \"shell\", \"command\": \"New-Item\", \"args\": [ \"-ItemType\", \"Junction\", \"-Path\", \"'", directory.Replace('\\', '/'), "/()'\", \"-Target\", \"'", eDistanceContentDirectory.Replace('\\', '/'), "'\" ], \"problemMatcher\": [] } ] }");
        _ = IPath.WriteAllText(Path.Combine(vsCodeDirectory, "tasks.json"), json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null);
    }

    private static ReadOnlyDictionary<string, string> GetMoveFiles(Configuration configuration, string key, int take, bool isCounterPersonYear, string? displayDirectoryName, ReadOnlyCollection<RelationContainer> relationContainers)
    {
        ReadOnlyDictionary<string, string> results;
        List<string> linked1 = [];
        List<string> linked2 = [];
        List<string> linked3 = [];
        List<string> linked4 = [];
        List<string> linked5 = [];
        List<string> linked6 = [];
        List<string> linked7 = [];
        List<string> linked8 = [];
        List<string> linked9 = [];
        foreach ((FileHolder fileHolder, ReadOnlyCollection<Relation> relations) in relationContainers)
        {
            foreach (Relation relation in relations.Take(take))
            {
                if (!linked1.Contains(relation.File))
                {
                    linked1.Add(relation.File);
                    continue;
                }
                if (!linked2.Contains(relation.File))
                {
                    linked2.Add(relation.File);
                    continue;
                }
                if (!linked3.Contains(relation.File))
                {
                    linked3.Add(relation.File);
                    continue;
                }
                if (!linked4.Contains(relation.File))
                {
                    linked4.Add(relation.File);
                    continue;
                }
                if (!linked5.Contains(relation.File))
                {
                    linked5.Add(relation.File);
                    continue;
                }
                if (!linked6.Contains(relation.File))
                {
                    linked6.Add(relation.File);
                    continue;
                }
                if (!linked7.Contains(relation.File))
                {
                    linked7.Add(relation.File);
                    continue;
                }
                if (!linked8.Contains(relation.File))
                {
                    linked8.Add(relation.File);
                    continue;
                }
                if (!linked9.Contains(relation.File))
                {
                    linked9.Add(relation.File);
                    continue;
                }
            }
        }
        results = MoveFiles(configuration, key, isCounterPersonYear, displayDirectoryName, relationContainers, linked1, linked2, linked3, linked4, linked5, linked6, linked7, linked8, linked9);
        return results;
    }

    private static void WriteFile(int take, long personKey, bool isCounterPersonYear, string personKeyFormatted, string? displayDirectoryName, string directory, long ticks, Uri uri, ReadOnlyCollection<RelationContainer> relationContainers, ReadOnlyDictionary<string, string> movedFiles)
    {
        string a;
        string b;
        int years;
        string text;
        string? file;
        Relation relation;
        string markDownFile;
        FileHolder fileHolder;
        string originalString;
        List<string> lines = [];
        string fileNameWithoutExtension;
        foreach ((FileHolder relationFileHolder, ReadOnlyCollection<Relation> relations) in relationContainers)
        {
            lines.Clear();
            if (movedFiles.TryGetValue(relationFileHolder.FullName, out file))
                fileHolder = new(file);
            else
                fileHolder = new(relationFileHolder.FullName);
            if (!relationFileHolder.Exists || relationFileHolder.CreationTime is null)
                continue;
            if (isCounterPersonYear)
                (years, _) = IAge.GetAge(ticks, relationFileHolder.CreationTime.Value.Ticks);
            else
                (years, _) = IAge.GetAge(relationFileHolder.CreationTime.Value.Ticks, personKey);
            fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileHolder.NameWithoutExtension);
            markDownFile = $"{fileNameWithoutExtension}.md";
            lines.Add("---");
            lines.Add("type: \"distance\"");
            lines.Add("---");
            lines.Add(string.Empty);
            if (displayDirectoryName is null)
                lines.Add($"## {personKeyFormatted}");
            else
                lines.Add($"## {displayDirectoryName}");
            lines.Add(string.Empty);
            lines.Add($"![0]({uri.MakeRelativeUri(new(fileHolder.FullName)).OriginalString})");
            lines.Add($"- __{fileNameWithoutExtension}__");
            if (isCounterPersonYear)
                lines.Add($"- #{years}yrs-ago");
            else
                lines.Add($"- #{years}yrs-old");
            lines.Add($"- ~~{relations.Count}~~");
            lines.Add(string.Empty);
            for (int i = 0; i < relations.Count; i++)
            {
                relation = relations[i];
                if (movedFiles.TryGetValue(relation.File, out file))
                    fileHolder = new(file);
                else
                    fileHolder = new(relation.File);
                if (!fileHolder.Exists || fileHolder.CreationTime is null)
                    continue;
                fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileHolder.NameWithoutExtension);
                originalString = uri.MakeRelativeUri(new(fileHolder.FullName)).OriginalString;
                if (i < take)
                    (a, b) = ("[[", "]]");
                else
                    (a, b) = ("~~", "~~");
                lines.Add($"![{relation.DistancePermyriad}]({originalString}){Environment.NewLine}{a}{fileNameWithoutExtension}{b}");
                lines.Add(string.Empty);
            }
            lines.Add("<style>");
            lines.Add("img {");
            lines.Add("min-width: 75px;");
            lines.Add("max-width: 75px;");
            lines.Add("display: block;");
            lines.Add("}");
            lines.Add("</style>");
            text = string.Join(Environment.NewLine, lines);
            File.WriteAllText(Path.Combine(directory, markDownFile), text);
        }
    }

    private static void AddDisplayDirectoryNames(Configuration configuration, string eDistanceContentDirectory, ReadOnlyDictionary<string, PersonContainer> readOnlyPersonKeyFormattedToPersonContainer, ReadOnlyDictionary<long, List<PersonContainer>> readOnlyPersonKeyToPersonContainerCollection, ReadOnlyCollection<Group> groups)
    {
        bool isCounterPersonYear;
        string personKeyFormatted;
        string? displayDirectoryName;
        string? checkDirectory = Path.Combine(eDistanceContentDirectory, configuration.LocationContainerDebugDirectory);
        _ = IPath.DeleteEmptyDirectories(checkDirectory);
        foreach (Group group in groups)
        {
            if (configuration.LocationContainerDistanceTolerance is null)
                break;
            isCounterPersonYear = IPersonBirthday.IsCounterPersonYear(new DateTime(group.PersonKey).Year);
            if (isCounterPersonYear)
                continue;
            personKeyFormatted = IPersonBirthday.GetFormatted(configuration.PersonBirthdayFormat, group.PersonKey);
            checkDirectory = Path.Combine(eDistanceContentDirectory, configuration.LocationContainerDebugDirectory, personKeyFormatted);
            if (!Directory.Exists(checkDirectory))
                continue;
            displayDirectoryName = GetDisplayDirectoryName(readOnlyPersonKeyToPersonContainerCollection, readOnlyPersonKeyFormattedToPersonContainer, group.PersonKey, personKeyFormatted);
            if (string.IsNullOrEmpty(displayDirectoryName))
                continue;
            foreach (string yearDirectory in Directory.GetDirectories(checkDirectory, "*", SearchOption.TopDirectoryOnly))
            {
                checkDirectory = Path.Combine(yearDirectory, displayDirectoryName);
                if (!Directory.Exists(checkDirectory))
                    _ = Directory.CreateDirectory(checkDirectory);
            }
            checkDirectory = Path.Combine(eDistanceContentDirectory, configuration.LocationContainerDebugDirectory, personKeyFormatted, displayDirectoryName);
            if (!Directory.Exists(checkDirectory))
                _ = Directory.CreateDirectory(checkDirectory);
        }
    }

    internal static void SaveMappedRelations(Configuration configuration, Shared.Models.Methods.IDistance<MetadataExtractor.Directory> distance, string a2PeopleContentDirectory, string eDistanceContentDirectory, long ticks, List<LocationContainer<MetadataExtractor.Directory>> locationContainers, ReadOnlyDictionary<string, PersonContainer> readOnlyPersonKeyFormattedToPersonContainer, ReadOnlyDictionary<long, List<PersonContainer>> readOnlyPersonKeyToPersonContainerCollection)
    {
        int take;
        string directory;
        bool isCounterPersonYear;
        string personKeyFormatted;
        string? displayDirectoryName;
        Uri uri = new(eDistanceContentDirectory);
        ReadOnlyDictionary<string, string> movedFiles;
        ReadOnlyCollection<RelationContainer> relationContainers;
        ReadOnlyCollection<Group> groups = GetGroups(configuration, locationContainers);
        int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
        string message = $") Save Mapped Relations - {totalSeconds} total second(s)";
        ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true };
        using ProgressBar progressBar = new(groups.Count, message, options);
        foreach (Group group in groups)
        {
            if (configuration.LocationContainerDistanceTolerance is null)
                break;
            progressBar.Tick();
            if (group.RelationContainersCollection.Count == 0)
                continue;
            take = GetTake(configuration.LocationContainerDistanceTake, group.RelationContainersCollection.Count);
            isCounterPersonYear = IPersonBirthday.IsCounterPersonYear(new DateTime(group.PersonKey).Year);
            personKeyFormatted = IPersonBirthday.GetFormatted(configuration.PersonBirthdayFormat, group.PersonKey);
            displayDirectoryName = GetDisplayDirectoryName(readOnlyPersonKeyToPersonContainerCollection, readOnlyPersonKeyFormattedToPersonContainer, group.PersonKey, personKeyFormatted);
            directory = Path.Combine(a2PeopleContentDirectory, $"{ticks}-{configuration.LocationContainerDistanceTolerance.Value}", personKeyFormatted, group.Key);
            if (!Directory.Exists(directory))
                _ = Directory.CreateDirectory(directory);
            WriteVsCodeFiles(eDistanceContentDirectory, displayDirectoryName, directory);
            relationContainers = distance.GetRelationContainers(configuration.FaceDistancePermyriad, configuration.LocationContainerDistanceTake, configuration.LocationContainerDistanceTolerance.Value, group.RelationContainersCollection);
            movedFiles = GetMoveFiles(configuration, group.Key, take, isCounterPersonYear, displayDirectoryName, relationContainers);
            WriteFile(take, group.PersonKey, isCounterPersonYear, personKeyFormatted, displayDirectoryName, directory, ticks, uri, relationContainers, movedFiles);
        }
        if (string.IsNullOrEmpty(configuration.LocationContainerDebugDirectory))
            _ = IPath.DeleteEmptyDirectories(eDistanceContentDirectory);
        else
            AddDisplayDirectoryNames(configuration, eDistanceContentDirectory, readOnlyPersonKeyFormattedToPersonContainer, readOnlyPersonKeyToPersonContainerCollection, groups);
    }

}