using System.Collections.ObjectModel;
using System.Text.Json;

namespace View_by_Distance.Shared.Models.Stateless.Methods;

internal abstract class Container
{

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

    internal static DateTime[] GetContainerDateTimes(ReadOnlyCollection<Models.Item> items)
    {
        DateTime[] results;
        long containerMinimumTicks = (from l in items select l.FilePath.LastWriteTicks).Min();
        long containerMaximumTicks = (from l in items select l.FilePath.LastWriteTicks).Max();
        results = [new(containerMinimumTicks), new(containerMaximumTicks)];
        return results;
    }

    internal static ReadOnlyCollection<Models.Item> GetValidImageItems(Properties.IPropertyConfiguration propertyConfiguration, Models.Container container)
    {
        List<Models.Item> results = [];
        foreach (Models.Item item in container.Items)
        {
            if (!item.IsValidImageFormatExtension || propertyConfiguration.IgnoreExtensions.Contains(item.FilePath.ExtensionLowered))
                continue;
            results.Add(item);
        }
        return container.Items.Count == results.Count ? container.Items : new(results);
    }

    private static List<Models.FilePair> GetFilePairs(Properties.IPropertyConfiguration propertyConfiguration, string directorySearchFilter, string extension, string aPropertySingletonDirectory, ReadOnlyCollection<ReadOnlyCollection<FilePath>> filePathsCollection)
    {
        int renamed;
        const bool useCeilingAverage = true;
        List<Models.FilePair>? filePairs = null;
        ReadOnlyCollection<string[]>? jsonFilesCollection = null;
        IReadOnlyDictionary<string, List<string>>? compareFileNamesToFiles = null;
        IReadOnlyDictionary<string, List<string>> fileNamesToFiles = XDirectory.GetFilesKeyValuePairs(filePathsCollection);
        for (int i = 0; i < short.MaxValue; i++)
        {
            renamed = 0;
            jsonFilesCollection = IDirectory.GetFilesCollection(aPropertySingletonDirectory, directorySearchFilter, extension, useCeilingAverage);
            compareFileNamesToFiles = XDirectory.GetFilesKeyValuePairs(jsonFilesCollection);
            renamed += XDirectory.LookForAbandoned(jsonFilesCollection, fileNamesToFiles, extension);
            filePairs = XDirectory.GetFiles(propertyConfiguration, filePathsCollection, fileNamesToFiles, extension, compareFileNamesToFiles);
            renamed += XDirectory.MaybeMove(propertyConfiguration, filePairs, aPropertySingletonDirectory, extension);
            if (renamed == 0)
                break;
            if (i > 10)
                throw new NotImplementedException();
        }
        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? result;
        if (filePair.Match is null)
            result = null;
        else
        {
            string json = File.ReadAllText(filePair.Match);
            if (string.IsNullOrEmpty(json))
                result = null;
            else
                result = JsonSerializer.Deserialize(json, PropertyGenerationContext.Default.Property);
        }
        return result;
    }

    private static void ParallelFor(IDlibDotNet? dlibDotNet, Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string extension, int rootDirectoryLength, ReadOnlyDictionary<int, Identifier>? splatNineIdentifiers, Models.FilePair filePair, List<FilePair> results)
    {
        dlibDotNet?.Tick();
        bool abandoned = false;
        Models.FileHolder sourceDirectoryFileHolder;
        Models.Property? property = GetProperty(filePair);
        Models.FileHolder imageFileHolder = IFileHolder.Get(filePair.Path);
        FilePath filePath = FilePath.Get(propertyConfiguration, imageFileHolder, index: null);
        bool? fileSizeChanged = property is not null ? property.FileSize != filePath.Length : null;
        bool isValidImageFormatExtension = propertyConfiguration.ValidImageFormatExtensions.Contains(filePath.ExtensionLowered);
        bool? shouldIgnore = property is null || property.Keywords is null ? null : propertyConfiguration.IgnoreRulesKeyWords.Any(l => property.Keywords.Contains(l));
        bool? isArchive = filePath.Id is null || splatNineIdentifiers is null ? null : splatNineIdentifiers.TryGetValue(filePath.Id.Value, out Identifier? identifier);
        if (shouldIgnore is not null)
        {
            if (shouldIgnore.Value)
            {
                FileInfo fileInfo = new(filePath.FullName);
                if (!fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
                    File.SetAttributes(imageFileHolder.FullName, FileAttributes.Hidden);
            }
            if (filePath.HasIgnoreKeyword is not null && filePath.HasIgnoreKeyword.Value != shouldIgnore.Value)
            {
                if (filePath.DirectoryFullPath.Contains("Results") && filePath.DirectoryFullPath.Contains("Resize"))
                    File.Delete(filePath.FullName);
                else
                    throw new NotSupportedException($"Rename File! <{filePath.FileNameFirstSegment}>");
            }
        }
        string relativePath = IPath.GetRelativePath(filePair.Path, rootDirectoryLength, forceExtensionToLower: true);
        bool? lastWriteTimeChanged = property is not null ? propertyConfiguration.PropertiesChangedForProperty || property.LastWriteTime.Ticks != filePath.LastWriteTicks : null;
        if (filePair.Match is not null)
            sourceDirectoryFileHolder = IFileHolder.Get(filePair.Match);
        else if (!filePair.IsUnique)
            sourceDirectoryFileHolder = IFileHolder.Get(Path.GetFullPath(string.Concat(aPropertySingletonDirectory, relativePath, extension)));
        else
        {
            string fileName = Path.GetFileName(filePair.Path);
            (string directoryName, _) = IPath.GetDirectoryNameAndIndex(propertyConfiguration, filePath);
            sourceDirectoryFileHolder = IFileHolder.Get(Path.Combine(aPropertySingletonDirectory, directoryName, $"{fileName}{extension}"));
        }
        if (sourceDirectoryFileHolder.CreationTime is not null && sourceDirectoryFileHolder.LastWriteTime is not null && filePath.LastWriteTicks != sourceDirectoryFileHolder.CreationTime.Value.Ticks)
        {
            File.SetCreationTime(sourceDirectoryFileHolder.FullName, new(filePath.LastWriteTicks));
            File.SetLastWriteTime(sourceDirectoryFileHolder.FullName, sourceDirectoryFileHolder.LastWriteTime.Value);
        }
        Models.Item item = Models.Item.Get(filePath, sourceDirectoryFileHolder, relativePath, isArchive, filePair.IsNotUniqueAndNeedsReview, filePair.IsUnique, isValidImageFormatExtension, property, abandoned, fileSizeChanged, lastWriteTimeChanged);
        lock (results)
            results.Add(new(filePair.IsUnique, filePair.Collection, filePath, item));
    }

    private static List<FilePair> GetFilePairs(IDlibDotNet? dlibDotNet, Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string filesCollectionDirectory, ReadOnlyDictionary<int, Identifier>? splatNineIdentifiers, ReadOnlyCollection<ReadOnlyCollection<FilePath>> filePathsCollection, string directorySearchFilter)
    {
        List<FilePair> results = [];
        const string extension = ".json";
        int maxDegreeOfParallelism = Environment.ProcessorCount;
        int filesCollectionDirectoryLength = filesCollectionDirectory.Length;
        ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
        List<Models.FilePair> filePairs = GetFilePairs(propertyConfiguration, directorySearchFilter, extension, aPropertySingletonDirectory, filePathsCollection);
        _ = Parallel.For(0, filePairs.Count, parallelOptions, (i, state) => ParallelFor(dlibDotNet, propertyConfiguration, aPropertySingletonDirectory, extension, filesCollectionDirectoryLength, splatNineIdentifiers, filePairs[i], results));
        return results;
    }

    private static (int, Models.Container[]) GetContainers(IDlibDotNet? dlibDotNet, Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string filesCollectionDirectory, ReadOnlyDictionary<int, Identifier>? splatNineIdentifiers, ReadOnlyCollection<ReadOnlyCollection<FilePath>> filePathsCollection, string directorySearchFilter)
    {
        List<Models.Container> results = [];
        string directory;
        List<Models.Item>? items;
        Models.Container container;
        List<string> directories = [];
        Dictionary<string, List<Models.Item>> directoryToItems = [];
        foreach (ReadOnlyCollection<FilePath> filePaths in filePathsCollection)
        {
            if (filePaths.Count == 0)
                continue;
            directory = filePaths[0].DirectoryFullPath;
            if (directory is null)
                continue;
            if (!directories.Contains(directory))
                directories.Add(directory);
            if (!directoryToItems.TryGetValue(directory, out items))
            {
                directoryToItems.Add(directory, []);
                if (!directoryToItems.TryGetValue(directory, out items))
                    throw new Exception();
            }
        }
        List<FilePair> filePairs = GetFilePairs(dlibDotNet, propertyConfiguration, aPropertySingletonDirectory, filesCollectionDirectory, splatNineIdentifiers, filePathsCollection, directorySearchFilter);
        foreach (FilePair filePair in filePairs)
        {
            if (!directoryToItems.TryGetValue(filePair.FilePath.DirectoryFullPath, out items))
            {
                directoryToItems.Add(filePair.FilePath.DirectoryFullPath, []);
                if (!directoryToItems.TryGetValue(filePair.FilePath.DirectoryFullPath, out items))
                    throw new Exception();
            }
            items.Add(filePair.Item);
        }
        foreach (KeyValuePair<string, List<Models.Item>> keyValuePair in directoryToItems)
        {
            if (keyValuePair.Value.Count == 0)
                continue;
            container = new(keyValuePair.Key, new(keyValuePair.Value));
            results.Add(container);
        }
        return (filePairs.Count, results.ToArray());
    }

    internal static ReadOnlyCollection<Models.Container> GetContainers(IDlibDotNet dlibDotNet, Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory, string filesCollectionDirectory, ReadOnlyDictionary<int, Identifier>? splatNineIdentifiers, ReadOnlyCollection<ReadOnlyCollection<FilePath>> filePathsCollection)
    {
        Models.Container[] results;
        const string directorySearchFilter = "*";
        (_, results) = GetContainers(dlibDotNet, propertyConfiguration, aPropertySingletonDirectory, filesCollectionDirectory, splatNineIdentifiers, filePathsCollection, directorySearchFilter);
        return new(results);
    }

    internal static (int, Models.Container[]) GetContainers(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyDictionary<int, Identifier>? splatNineIdentifiers, string aPropertySingletonDirectory)
    {
        int count;
        Models.Container[] results;
        IDlibDotNet? dlibDotNet = null;
        const bool useCeilingAverage = true;
        const string fileSearchFilter = "*";
        const string directorySearchFilter = "*";
        ReadOnlyCollection<string[]> filesCollection = IDirectory.GetFilesCollection(propertyConfiguration.RootDirectory, directorySearchFilter, fileSearchFilter, useCeilingAverage);
        ReadOnlyCollection<ReadOnlyCollection<FilePath>> filePathsCollection = IDirectory.GetFilePathCollections(propertyConfiguration, filesCollection);
        (count, results) = GetContainers(dlibDotNet, propertyConfiguration, aPropertySingletonDirectory, propertyConfiguration.RootDirectory, splatNineIdentifiers, filePathsCollection, directorySearchFilter);
        return (count, results);
    }

    internal static List<int> GetFilteredDistinctIds(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection<Models.Container> readOnlyContainers)
    {
        List<int> results = [];
        ReadOnlyCollection<Models.Item> filteredItems;
        foreach (Models.Container container in readOnlyContainers)
        {
            if (container.Items.Count == 0)
                continue;
            filteredItems = GetValidImageItems(propertyConfiguration, container);
            if (filteredItems.Count == 0)
                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<string> GetFilteredDistinctFileNameFirstSegments(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection<Models.Container> readOnlyContainers)
    {
        List<string> results = [];
        ReadOnlyCollection<Models.Item> filteredItems;
        foreach (Models.Container container in readOnlyContainers)
        {
            if (container.Items.Count == 0)
                continue;
            filteredItems = GetValidImageItems(propertyConfiguration, container);
            if (filteredItems.Count == 0)
                continue;
            foreach (Models.Item item in filteredItems)
            {
                if (item.Property?.Id is null || item.ResizedFileHolder is null)
                    continue;
                if (results.Contains(item.FilePath.FileNameFirstSegment))
                    continue;
                results.Add(item.FilePath.FileNameFirstSegment);
            }
        }
        return results;
    }

    internal static ReadOnlyCollection<Models.Item> GetValidImageItems(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection<Models.Container> containers, bool distinctItems, bool filterItems)
    {
        List<Models.Item> results = [];
        List<int> distinct = [];
        ReadOnlyCollection<Models.Item> filteredItems;
        foreach (Models.Container container in containers)
        {
            if (container.Items.Count == 0)
                continue;
            if (!filterItems)
                filteredItems = container.Items;
            else
            {
                filteredItems = GetValidImageItems(propertyConfiguration, container);
                if (filteredItems.Count == 0)
                    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 new(results);
    }

}