using System.Text.Json;

namespace View_by_Distance.Shared.Models.Stateless.Methods;

internal abstract class Container
{

    private record FilePair(string Path, string? Directory, bool IsUnique, List<string> Collection, Models.Item Item) { }

    internal static DateTime[] GetContainerDateTimes(IEnumerable<Models.Item> items)
    {
        DateTime[] results;
        DateTime? containerMinimumDateTime;
        DateTime? containerMaximumDateTime;
        containerMinimumDateTime = (from l in items select l.ImageFileHolder.LastWriteTime).Min();
        if (containerMinimumDateTime is null)
            containerMaximumDateTime = null;
        else
            containerMaximumDateTime = (from l in items select l.ImageFileHolder.LastWriteTime).Max();
        if (containerMinimumDateTime is null || containerMaximumDateTime is null)
            results = Array.Empty<DateTime>();
        else
            results = new DateTime[] { containerMinimumDateTime.Value, containerMaximumDateTime.Value };
        return results;
    }

    internal static Models.Item[] GetFilterItems(Properties.IPropertyConfiguration propertyConfiguration, Models.Container container)
    {
        List<Models.Item> results = new();
        foreach (Models.Item item in container.Items)
        {
            if (item.ImageFileHolder is not null
                && item.IsValidImageFormatExtension
                && !propertyConfiguration.IgnoreExtensions.Contains(item.ImageFileHolder.ExtensionLowered))
                results.Add(item);
        }
        return results.ToArray();
    }

    internal static bool IsIgnoreRelativePath(Properties.IPropertyConfiguration propertyConfiguration, string[] ignoreRelativePaths, string directory)
    {
        bool result = false;
        string? checkDirectory = Path.GetFullPath(directory);
        for (int i = 0; i < int.MaxValue; i++)
        {
            if (ignoreRelativePaths.Contains(Path.GetFileName(checkDirectory)))
            {
                result = true;
                break;
            }
            checkDirectory = Path.GetDirectoryName(checkDirectory);
            if (string.IsNullOrEmpty(checkDirectory) || checkDirectory == propertyConfiguration.RootDirectory)
                break;
        }
        return result;
    }

    internal static Models.Container[] SortContainers(Properties.IPropertyConfiguration propertyConfiguration, string[] ignoreRelativePaths, bool argZeroIsConfigurationRootDirectory, string argZero, Models.Container[] containers)
    {
        List<Models.Container> results = new();
        bool isIgnoreRelativePath;
        for (int i = 1; i < 3; i++)
        {
            foreach (Models.Container container in containers)
            {
                if (!container.Items.Any())
                    continue;
                if (!argZeroIsConfigurationRootDirectory && !container.SourceDirectory.StartsWith(argZero))
                    continue;
                isIgnoreRelativePath = ignoreRelativePaths.Any(l => container.SourceDirectory.Contains(l)) && IsIgnoreRelativePath(propertyConfiguration, ignoreRelativePaths, container.SourceDirectory);
                if (i == 1 && isIgnoreRelativePath)
                    continue;
                if (i == 2 && !isIgnoreRelativePath)
                    continue;
                results.Add(container);
            }
        }
        return results.ToArray();
    }

    internal static List<Models.FilePair> GetFilePairs(Properties.IPropertyConfiguration propertyConfiguration, string directorySearchFilter, string extension, string aPropertySingletonDirectory, List<string[]> filesCollection)
    {
        int renamed;
        List<Models.FilePair>? filePairs = null;
        List<string[]>? jsonFilesCollection = null;
        IReadOnlyDictionary<string, List<string>>? compareFileNamesToFiles = null;
        IReadOnlyDictionary<string, List<string>> fileNamesToFiles = IDirectory.GetFilesKeyValuePairs(filesCollection);
        for (int i = 0; i < int.MaxValue; i++)
        {
            renamed = 0;
            jsonFilesCollection = IDirectory.GetFilesCollection(aPropertySingletonDirectory, directorySearchFilter, extension);
            compareFileNamesToFiles = IDirectory.GetFilesKeyValuePairs(jsonFilesCollection);
            renamed += IDirectory.LookForAbandoned(jsonFilesCollection, fileNamesToFiles, extension);
            filePairs = IDirectory.GetFiles(filesCollection, fileNamesToFiles, extension, compareFileNamesToFiles);
            renamed += IDirectory.MaybeMove(propertyConfiguration.RootDirectory, propertyConfiguration.ResultAllInOne, propertyConfiguration.ResultAllInOneSubdirectoryLength, filePairs, aPropertySingletonDirectory, extension);
            if (renamed == 0)
                break;
        }
        if (filePairs is null || jsonFilesCollection is null || compareFileNamesToFiles is null)
            throw new NullReferenceException(nameof(filePairs));
        return filePairs;
    }

    private static Models.Property? GetProperty(Models.FilePair filePair)
    {
        Models.Property? property;
        if (filePair.Match is null)
            property = null;
        else
        {
            string json = File.ReadAllText(filePair.Match);
            if (string.IsNullOrEmpty(json))
                property = null;
            else
                property = JsonSerializer.Deserialize<Models.Property>(json);
        }
        return property;
    }

    private static void ParallelFor(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string extension, int length, Models.FilePair filePair, List<FilePair> results)
    {
        string fileName;
        bool abandoned = false;
        Models.FileHolder sourceDirectoryFileHolder;
        Models.Property? property = GetProperty(filePair);
        Models.FileHolder imageFileInfo = new(filePair.Path);
        bool? fileSizeChanged = property is not null ? property.FileSize != imageFileInfo.Length : null;
        string relativePath = IPath.GetRelativePath(filePair.Path, length, forceExtensionToLower: true);
        bool isValidImageFormatExtension = propertyConfiguration.ValidImageFormatExtensions.Contains(imageFileInfo.ExtensionLowered);
        bool? lastWriteTimeChanged = property is not null ? propertyConfiguration.PropertiesChangedForProperty || property.LastWriteTime != imageFileInfo.LastWriteTime : null;
        if (filePair.Match is not null)
            sourceDirectoryFileHolder = new(filePair.Match);
        else if (!filePair.IsUnique)
            sourceDirectoryFileHolder = new(Path.GetFullPath(string.Concat(aPropertySingletonDirectory, relativePath, extension)));
        else
        {
            fileName = Path.GetFileName(filePair.Path);
            (string directoryName, _) = IPath.GetDirectoryNameAndIndex(propertyConfiguration.ResultAllInOneSubdirectoryLength, fileName);
            sourceDirectoryFileHolder = new(Path.Combine(aPropertySingletonDirectory, propertyConfiguration.ResultAllInOne, directoryName, $"{fileName}{extension}"));
        }
        if (imageFileInfo.LastWriteTime is not null && sourceDirectoryFileHolder.CreationTime is not null && sourceDirectoryFileHolder.LastWriteTime is not null && imageFileInfo.LastWriteTime.Value != sourceDirectoryFileHolder.CreationTime.Value)
        {
            File.SetCreationTime(sourceDirectoryFileHolder.FullName, imageFileInfo.LastWriteTime.Value);
            File.SetLastWriteTime(sourceDirectoryFileHolder.FullName, sourceDirectoryFileHolder.LastWriteTime.Value);
        }
        Models.Item item = new(sourceDirectoryFileHolder, relativePath, imageFileInfo, filePair.IsNotUniqueAndNeedsReview, filePair.IsUnique, isValidImageFormatExtension, property, abandoned, fileSizeChanged, lastWriteTimeChanged);
        lock (results)
            results.Add(new(filePair.Path, imageFileInfo.DirectoryName, filePair.IsUnique, filePair.Collection, item));
    }

    private static List<FilePair> GetFilePairs(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string extension, List<Models.FilePair> filePairs)
    {
        List<FilePair> results = new();
        int length = propertyConfiguration.RootDirectory.Length;
        int maxDegreeOfParallelism = Environment.ProcessorCount;
        ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
        _ = Parallel.For(0, filePairs.Count, parallelOptions, (i, state) => ParallelFor(propertyConfiguration, aPropertySingletonDirectory, extension, length, filePairs[i], results));
        return results;
    }

    private static (int, Models.Container[]) GetContainers(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string directorySearchFilter, List<string[]> filesCollection)
    {
        List<Models.Container> results = new();
        string? directory;
        List<Models.Item>? items;
        Models.Container container;
        const string extension = ".json";
        List<string> directories = new();
        Dictionary<string, List<Models.Item>> directoryToItems = new();
        foreach (string[] files in filesCollection)
        {
            if (!files.Any())
                continue;
            directory = Path.GetDirectoryName(files.First());
            if (directory is null)
                continue;
            if (!directories.Contains(directory))
                directories.Add(directory);
            if (!directoryToItems.TryGetValue(directory, out items))
            {
                directoryToItems.Add(directory, new());
                if (!directoryToItems.TryGetValue(directory, out items))
                    throw new Exception();
            }
        }
        List<Models.FilePair> filePairs = GetFilePairs(propertyConfiguration, directorySearchFilter, extension, aPropertySingletonDirectory, filesCollection);
        List<FilePair> collection = GetFilePairs(propertyConfiguration, aPropertySingletonDirectory, extension, filePairs);
        foreach (FilePair filePair in collection)
        {
            if (filePair.Directory is null)
                continue;
            if (!directoryToItems.TryGetValue(filePair.Directory, out items))
            {
                directoryToItems.Add(filePair.Directory, new());
                if (!directoryToItems.TryGetValue(filePair.Directory, out items))
                    throw new Exception();
            }
            items.Add(filePair.Item);
        }
        foreach (KeyValuePair<string, List<Models.Item>> keyValuePair in directoryToItems)
        {
            if (!keyValuePair.Value.Any())
                continue;
            container = new(keyValuePair.Key, keyValuePair.Value);
            results.Add(container);
        }
        return (collection.Count, results.ToArray());
    }

    internal static (int, Models.Container[]) GetContainers(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, List<string[]> filesCollection)
    {
        int count;
        Models.Container[] results;
        const string directorySearchFilter = "*";
        (count, results) = GetContainers(propertyConfiguration, aPropertySingletonDirectory, directorySearchFilter, filesCollection);
        return (count, results);
    }

    internal static (int, Models.Container[]) GetContainers(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory)
    {
        int count;
        Models.Container[] results;
        const string fileSearchFilter = "*";
        const string directorySearchFilter = "*";
        List<string[]> filesCollection = IDirectory.GetFilesCollection(propertyConfiguration.RootDirectory, directorySearchFilter, fileSearchFilter);
        (count, results) = GetContainers(propertyConfiguration, aPropertySingletonDirectory, directorySearchFilter, filesCollection);
        return (count, results);
    }

    internal static List<int> GetFilteredDistinctIds(Properties.IPropertyConfiguration propertyConfiguration, Models.Container[] containers)
    {
        List<int> results = new();
        Models.Item[] filteredItems;
        foreach (Models.Container container in containers)
        {
            if (!container.Items.Any())
                continue;
            filteredItems = GetFilterItems(propertyConfiguration, container);
            if (!filteredItems.Any())
                continue;
            foreach (Models.Item item in filteredItems)
            {
                if (item.Property?.Id is null || item.ResizedFileHolder is null)
                    continue;
                if (results.Contains(item.Property.Id.Value))
                    continue;
                results.Add(item.Property.Id.Value);
            }
        }
        return results;
    }

    internal static List<Models.Item> GetItems(Properties.IPropertyConfiguration propertyConfiguration, Models.Container[] containers, bool distinctItems, bool filterItems)
    {
        List<Models.Item> results = new();
        List<int> distinct = new();
        IEnumerable<Models.Item> filteredItems;
        foreach (Models.Container container in containers)
        {
            if (!container.Items.Any())
                continue;
            if (!filterItems)
                filteredItems = container.Items;
            else
            {
                filteredItems = GetFilterItems(propertyConfiguration, container);
                if (!filteredItems.Any())
                    continue;
            }
            foreach (Models.Item item in filteredItems)
            {
                if (item.Property?.Id is null || item.ResizedFileHolder is null)
                    continue;
                if (distinctItems)
                {
                    if (distinct.Contains(item.Property.Id.Value))
                        continue;
                    distinct.Add(item.Property.Id.Value);
                }
                results.Add(item);
            }
        }
        return results;
    }

}