using System.Collections.ObjectModel;

namespace View_by_Distance.Shared.Models.Stateless.Methods;

internal abstract partial class XDirectory
{

    private static int GetCeilingAverage(List<string[]> fileCollection)
    {
        List<int> counts = [];
        foreach (string[] files in fileCollection)
            counts.Add(files.Length);
        int average = (int)Math.Ceiling(counts.Average());
        return average;
    }

    private static List<string[]> GetFilesCollection(List<string[]> fileCollection, int ceilingAverage)
    {
        List<string[]> results = [];
        foreach (string[] files in fileCollection)
        {
            if (files.Length < ceilingAverage)
                results.Add(files);
        }
        foreach (string[] files in fileCollection)
        {
            if (files.Length >= ceilingAverage)
                results.Add(files);
        }
        return results;
    }

    internal static ReadOnlyCollection<string[]> GetFilesCollection(string directory, string directorySearchFilter, string fileSearchFilter, bool useCeilingAverage)
    {
        List<string[]> results = [];
        if (!fileSearchFilter.Contains('*'))
            fileSearchFilter = string.Concat('*', fileSearchFilter);
        if (!directorySearchFilter.Contains('*'))
            directorySearchFilter = string.Concat('*', directorySearchFilter);
        if (!Directory.Exists(directory))
            _ = Directory.CreateDirectory(directory);
        results.Add(Directory.GetFiles(directory, fileSearchFilter, SearchOption.TopDirectoryOnly));
        string[] directories = Directory.GetDirectories(directory, directorySearchFilter, SearchOption.TopDirectoryOnly);
        foreach (string innerDirectory in directories)
        {
            try
            { results.Add(Directory.GetFiles(innerDirectory, fileSearchFilter, SearchOption.AllDirectories)); }
            catch (UnauthorizedAccessException)
            { continue; }
        }
        int ceilingAverage = directory[^1] == '_' || results.Count == 0 ? 0 : GetCeilingAverage(results);
        if (useCeilingAverage)
            results = GetFilesCollection(results, ceilingAverage);
        return new(results);
    }

    internal static IReadOnlyDictionary<string, List<string>> GetFilesKeyValuePairs(ReadOnlyCollection<string[]> filesCollection)
    {
        Dictionary<string, List<string>> results = [];
        string fileName;
        List<string>? collection;
        foreach (string[] files in filesCollection)
        {
            foreach (string file in files)
            {
                fileName = Path.GetFileName(file);
                if (!results.TryGetValue(fileName, out collection))
                {
                    results.Add(fileName, []);
                    if (!results.TryGetValue(fileName, out collection))
                        throw new Exception();
                }
                collection.Add(file);
            }
        }
        return results;
    }

    internal static int LookForAbandoned(ReadOnlyCollection<string[]> jsonFilesCollection, IReadOnlyDictionary<string, List<string>> fileNamesToFiles, string extension)
    {
        string fileName;
        string fileNameWith;
        List<string>? collection;
        string fileNameUpperExtension;
        int length = extension.Length;
        List<string> renameCollection = [];
        foreach (string[] files in jsonFilesCollection)
        {
            foreach (string file in files)
            {
                fileNameWith = Path.GetFileName(file);
                if (fileNameWith.Length < length || !fileNameWith.EndsWith(extension))
                    throw new Exception();
                fileName = fileNameWith[..^length];
                if (!fileNamesToFiles.TryGetValue(fileName, out collection))
                {
                    fileNameUpperExtension = string.Concat(Path.GetFileNameWithoutExtension(fileName), Path.GetExtension(fileName).ToUpper());
                    if (fileName == fileNameUpperExtension || !fileNamesToFiles.TryGetValue(fileNameUpperExtension, out collection))
                        renameCollection.Add(file);
                }
            }
        }
        if (renameCollection.Count > 0)
            IDirectory.MoveFiles(renameCollection, "{}", "{abd}");
        return renameCollection.Count;
    }

    private static bool GetIsNotUniqueAndNeedsReview(string file, List<string> collection)
    {
        bool result = false;
        FileInfo possibleFileInfo;
        FileInfo fileInfo = new(file);
        foreach (string possible in collection)
        {
            if (possible == file)
                continue;
            possibleFileInfo = new(possible);
            if (possibleFileInfo.LastWriteTime != fileInfo.LastWriteTime)
                File.SetLastWriteTime(file, new DateTime[] { possibleFileInfo.LastWriteTime, fileInfo.LastWriteTime }.Max());
            if (possibleFileInfo.LastWriteTime == fileInfo.LastWriteTime && possibleFileInfo.Length == fileInfo.Length)
                continue;
            if (!result)
                result = true;
        }
        return result;
    }

    private static string? GetMatch(string file, List<string> collection)
    {
        string? result = null;
        FileInfo possibleFileInfo;
        List<long> lengths = [];
        List<string> matches = [];
        FileInfo fileInfo = new(file);
        List<DateTime> creationTimes = [];
        foreach (string possible in collection)
        {
            possibleFileInfo = new(possible);
            lengths.Add(possibleFileInfo.Length);
            creationTimes.Add(possibleFileInfo.CreationTime);
            if (possibleFileInfo.CreationTime != fileInfo.LastWriteTime)
                continue;
            matches.Add(possible);
        }
        if (matches.Count == 1 || (matches.Count > 0 && lengths.Distinct().Count() == 1 && creationTimes.Distinct().Count() == 1))
            result = matches.First();
        return result;
    }

    internal static List<FilePair> GetFiles(ReadOnlyCollection<string[]> filesCollection, IReadOnlyDictionary<string, List<string>> fileNamesToFiles, string extension, IReadOnlyDictionary<string, List<string>> compareFileNamesToFiles)
    {
        List<FilePair> results = [];
        string? match;
        string fileName;
        bool uniqueFileName;
        List<string>? collection;
        bool? isNotUniqueAndNeedsReview;
        foreach (string[] files in filesCollection)
        {
            foreach (string file in files)
            {
                isNotUniqueAndNeedsReview = null;
                fileName = Path.GetFileName(file);
                if (!fileNamesToFiles.TryGetValue(fileName, out collection))
                    throw new Exception();
                uniqueFileName = collection.Count == 1;
                if (!uniqueFileName)
                    isNotUniqueAndNeedsReview = GetIsNotUniqueAndNeedsReview(file, collection);
                if (!compareFileNamesToFiles.TryGetValue(string.Concat(fileName, extension), out collection))
                    results.Add(new(file, uniqueFileName, isNotUniqueAndNeedsReview, [], null));
                else
                {
                    if (collection.Count == 0)
                        results.Add(new(file, uniqueFileName, isNotUniqueAndNeedsReview, collection, null));
                    else if (uniqueFileName && collection.Count == 1)
                        results.Add(new(file, uniqueFileName, isNotUniqueAndNeedsReview, collection, collection.First()));
                    else
                    {
                        match = GetMatch(file, collection);
                        results.Add(new(file, uniqueFileName, isNotUniqueAndNeedsReview, collection, match));
                    }
                }
            }
        }
        return results;
    }

    private static void IsNotUniqueLoop(Properties.IPropertyConfiguration propertyConfiguration, string jsonGroupDirectory, string extension, FilePair filePair, List<(string, string)> rename)
    {
        int length = propertyConfiguration.RootDirectory.Length;
        foreach (string path in filePair.Collection)
        {
            if (filePair.Match is null || path != filePair.Match)
                continue;
            rename.Add(new(path, string.Concat(jsonGroupDirectory, filePair.Path[length..], extension)));
        }
    }

    internal static int MaybeMove(Properties.IPropertyConfiguration propertyConfiguration, List<FilePair> filePairs, string jsonGroupDirectory, string extension)
    {
        FileInfo? toFileInfo;
        FileInfo fromFileInfo;
        string checkDirectory;
        List<(string, string)> rename = [];
        foreach (FilePair filePair in filePairs)
        {
            if (filePair.IsUnique)
                continue;
            IsNotUniqueLoop(propertyConfiguration, jsonGroupDirectory, extension, filePair, rename);
        }
        foreach ((string from, string to) in rename)
        {
            toFileInfo = null;
            checkDirectory = to;
            fromFileInfo = new(from);
            if (!fromFileInfo.Exists)
                continue;
            for (int i = 0; i < int.MaxValue; i++)
            {
                toFileInfo = new(checkDirectory);
                if (toFileInfo.Directory is null)
                    continue;
                if (!toFileInfo.Directory.Exists)
                    _ = Directory.CreateDirectory(toFileInfo.Directory.FullName);
                if (checkDirectory.Length > 199)
                    throw new Exception();
                if (!toFileInfo.Exists)
                    break;
                else if (fromFileInfo.Length == toFileInfo.Length && fromFileInfo.LastWriteTime == toFileInfo.LastWriteTime)
                    checkDirectory = string.Concat(checkDirectory, ".del");
                else
                    checkDirectory = string.Concat(checkDirectory, ".j");
            }
            File.Move(from, checkDirectory);
        }
        return rename.Count;
    }

    internal static void MoveFiles(List<string> files, string find, string replace)
    {
        string checkFile;
        string? checkDirectory;
        List<string> directories = [];
        foreach (string file in files)
        {
            checkDirectory = Path.GetDirectoryName(file.Replace(find, replace));
            if (string.IsNullOrEmpty(checkDirectory) || directories.Contains(checkDirectory))
                continue;
            directories.Add(checkDirectory);
        }
        foreach (string directory in directories)
        {
            if (Directory.Exists(directory))
                continue;
            _ = Directory.CreateDirectory(directory);
        }
        foreach (string file in files)
        {
            if (!File.Exists(file))
                continue;
            checkFile = file.Replace(find, replace);
            if (File.Exists(checkFile))
                continue;
            File.Move(file, checkFile);
        }
    }

    private static FilePath[] GetSortedRecords(Properties.IPropertyConfiguration propertyConfiguration, ReadOnlyCollection<string[]> filesCollection)
    {
        List<FilePath> results = [];
        FilePath filePath;
        Models.FileHolder fileHolder;
        foreach (string[] files in filesCollection)
        {
            foreach (string file in files)
            {
                fileHolder = IFileHolder.Get(file);
                filePath = FilePath.Get(propertyConfiguration, fileHolder, index: null);
                results.Add(filePath);
            }
        }
        return (from l in results orderby l.CreationTicks, l.FullName.Length descending select l).ToArray();
    }

    internal static (string[], List<(FilePath, string)>) GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, bool copyDuplicates, bool ifCanUseId, ReadOnlyCollection<string[]> filesCollection, string[] directories, Action? tick)
    {
        List<(FilePath, string)> results = [];
        string paddedId;
        string checkFile;
        string directory;
        FileInfo fileInfo;
        FilePath filePath;
        int directoryIndex;
        bool ignore = false;
        string paddedIdFile;
        bool wrapped = false;
        string intelligentId;
        bool paddedCheck = false;
        string fileDirectoryName;
        List<int> distinctIds = [];
        List<string> distinct = [];
        Models.FileHolder fileHolder;
        List<string> distinctDirectories = [];
        FilePath[] sortedRecords = GetSortedRecords(propertyConfiguration, filesCollection);
        for (int i = 0; i < sortedRecords.Length; i++)
        {
            tick?.Invoke();
            filePath = sortedRecords[i];
            if (filePath.Name.EndsWith("len") || filePath.ExtensionLowered == ".id" || filePath.ExtensionLowered == ".lsv" || filePath.DirectoryName is null)
                continue;
            (_, directoryIndex) = IPath.GetDirectoryNameAndIndex(propertyConfiguration, filePath);
            fileDirectoryName = Path.GetFileName(filePath.DirectoryName);
            if (fileDirectoryName.Length < propertyConfiguration.ResultAllInOneSubdirectoryLength + 3 || !filePath.Name.StartsWith(fileDirectoryName))
            {
                if (wrapped)
                    continue;
                directory = directories[directoryIndex];
            }
            else
            {
                if (!wrapped)
                    wrapped = true;
                directory = Path.Combine(directories[directoryIndex], fileDirectoryName);
            }
            if (ifCanUseId && filePath.IsIntelligentIdFormat && filePath.Id is not null && filePath.DirectoryName is not null)
            {
                paddedId = IId.GetPaddedId(propertyConfiguration, i, filePath.Id.Value);
                paddedIdFile = Path.Combine(filePath.DirectoryName, $"{paddedId}{filePath.ExtensionLowered}");
                if (!File.Exists(paddedIdFile))
                {
                    File.Move(filePath.FullName, paddedIdFile);
                    fileInfo = new(paddedIdFile);
                    fileHolder = Models.FileHolder.Get(fileInfo);
                    filePath = FilePath.Get(propertyConfiguration, fileHolder, index: null);
                    if (!paddedCheck)
                        paddedCheck = true;
                }
            }
            if (filePath.IsIntelligentIdFormat || !ifCanUseId)
                checkFile = Path.Combine(directory, $"{filePath.NameWithoutExtension}{filePath.ExtensionLowered}");
            else
            {
                if (filePath.Id is null)
                    throw new NullReferenceException(nameof(filePath.Id));
                intelligentId = IId.GetIntelligentId(propertyConfiguration, filePath.Id.Value, ignore);
                checkFile = Path.Combine(directory, $"{intelligentId}{filePath.ExtensionLowered}");
            }
            if ((filePath.Id is not null && distinctIds.Contains(filePath.Id.Value)) || distinct.Contains(checkFile))
            {
                if (string.IsNullOrEmpty(filePath.DirectoryName))
                    continue;
                if (!copyDuplicates)
                    continue;
                for (int j = 1; j < int.MaxValue; j++)
                {
                    fileInfo = new(checkFile);
                    if (!fileInfo.Exists || filePath.Length == fileInfo.Length && filePath.LastWriteTicks == fileInfo.LastWriteTime.Ticks)
                        checkFile = Path.Combine(directory, $"{filePath.NameWithoutExtension}.{j}dup{filePath.ExtensionLowered}");
                    else
                        checkFile = Path.Combine(directory, $"{filePath.NameWithoutExtension}.{j}why{filePath.ExtensionLowered}");
                    if (filePath.Id is not null)
                    {
                        if (distinctIds.Contains(filePath.Id.Value))
                            continue;
                        distinctIds.Add(filePath.Id.Value);
                    }
                    if (distinct.Contains(checkFile))
                        continue;
                    distinct.Add(checkFile);
                    results.Add(new(filePath, checkFile));
                    if (!distinctDirectories.Contains(directory))
                        distinctDirectories.Add(directory);
                    break;
                }
                continue;
            }
            distinct.Add(checkFile);
            if (filePath.Id is not null)
                distinctIds.Add(filePath.Id.Value);
            results.Add(new(filePath, checkFile));
            if (!distinctDirectories.Contains(directory))
                distinctDirectories.Add(directory);
        }
        if (paddedCheck)
            throw new Exception("Maybe need to restart application!");
        return (distinctDirectories.ToArray(), results);
    }

    internal static List<string> CopyOrMove(List<(FilePath, string)> toDoCollection, bool move, bool moveBack, Action? tick)
    {
        List<string> results = [];
        FileInfo fileInfo;
        foreach ((FilePath filePath, string to) in toDoCollection)
        {
            tick?.Invoke();
            fileInfo = new(to);
            if (fileInfo.Exists)
            {
                if (filePath.Length != fileInfo.Length || filePath.LastWriteTicks != fileInfo.LastWriteTime.Ticks)
                    fileInfo.Delete();
                else
                    continue;
            }
            results.Add(filePath.NameWithoutExtension);
            try
            {
                if (move || moveBack)
                    File.Move(filePath.FullName, to);
                else
                    File.Copy(filePath.FullName, to);
            }
            catch (Exception) { }
        }
        return results;
    }

}