using Microsoft.Extensions.Configuration;
using Phares.Shared;
using View_by_Distance.Not.Copy.Copy.Models;
using View_by_Distance.Property.Models;
using View_by_Distance.Shared.Models.Methods;
using View_by_Distance.Shared.Models.Stateless;

namespace View_by_Distance.Not.Copy.Copy;

public class NotCopyCopy
{

    private readonly Serilog.ILogger? _Log;
    private readonly AppSettings _AppSettings;
    private readonly List<string> _Exceptions;
    private readonly IsEnvironment _IsEnvironment;
    private readonly Models.Configuration _Configuration;
    private readonly List<KeyValuePair<string, string>> _FileKeyValuePairs;
    private readonly Dictionary<string, List<Tuple<string, A_Property>>> _FilePropertiesKeyValuePairs;

    public NotCopyCopy(List<string> args, IsEnvironment isEnvironment, IConfigurationRoot configurationRoot, AppSettings appSettings, string workingDirectory, bool isSilent, IConsole console)
    {
        if (isSilent)
        { }
        if (args is null)
        { }
        if (console is null)
        { }
        _AppSettings = appSettings;
        if (appSettings.MaxDegreeOfParallelism is null)
            throw new Exception($"{nameof(appSettings.MaxDegreeOfParallelism)} is null!");
        _IsEnvironment = isEnvironment;
        _Exceptions = new List<string>();
        _Log = Serilog.Log.ForContext<NotCopyCopy>();
        _FileKeyValuePairs = new List<KeyValuePair<string, string>>();
        _FilePropertiesKeyValuePairs = new Dictionary<string, List<Tuple<string, A_Property>>>();
        Property.Models.Configuration propertyConfiguration = Property.Models.Stateless.Configuration.Get(isEnvironment, configurationRoot, workingDirectory);
        Property.Models.Configuration.Verify(propertyConfiguration);
        Models.Configuration configuration = Models.Stateless.Configuration.Get(isEnvironment, configurationRoot, workingDirectory, propertyConfiguration);
        Verify(configuration);
        bool reverse = false;
        Model? model = null;
        PredictorModel? predictorModel = null;
        _Configuration = configuration;
        if (propertyConfiguration.PopulatePropertyId is null)
            throw new Exception($"{nameof(propertyConfiguration.PopulatePropertyId)} is null!");
        if (!_IsEnvironment.Development)
            throw new Exception("This program only allows development environments!");
        PropertyLogic propertyLogic = GetPropertyLogic();
        propertyConfiguration.ChangeRootDirectory(configuration.CompareSource);
        List<PropertyHolder[]> comparePropertyHolderCollections = Property.Models.Stateless.A_Property.Get(propertyConfiguration, reverse, model, predictorModel, propertyLogic);
        propertyConfiguration.ChangeRootDirectory(configuration.SelectedSource);
        List<PropertyHolder[]> selectedPropertyHolderCollections = Property.Models.Stateless.A_Property.Get(propertyConfiguration, reverse, model, predictorModel, propertyLogic);
        if (comparePropertyHolderCollections.Count == selectedPropertyHolderCollections.Count)
            throw new Exception();
        string directoryName;
        List<string> distinct = new();
        List<Property.Models.DirectoryInfo> compareSourceGroupCollection = new();
        List<Property.Models.DirectoryInfo> selectedSourceGroupCollection = new();
        List<(string Source, string[] Destination)> copyCollection = GetCopyCollection(compareSourceGroupCollection, selectedSourceGroupCollection);
        foreach ((string source, string[] destination) in copyCollection)
        {
            directoryName = Path.Combine(destination.Take(destination.Length - 1).ToArray());
            if (distinct.Contains(directoryName))
                continue;
            distinct.Add(directoryName);
            if (!Directory.Exists(directoryName))
                _ = Directory.CreateDirectory(directoryName);
        }
        _Log.Information($"Ready to copy {copyCollection.Count} file(s)?");
        for (int y = 0; y < int.MaxValue; y++)
        {
            _Log.Information("Press \"Y\" key to to copy file(s)");
            if (Console.ReadKey().Key == ConsoleKey.Y)
                break;
        }
        _Log.Information(". . .");
        int copied = 0;
        string fullFileName;
        foreach ((string source, string[] destination) in copyCollection)
        {
            if (copied % 500 == 0)
                _Log.Information($"{copied})");
            fullFileName = Path.Combine(destination);
            if (File.Exists(fullFileName))
                continue;
            File.Copy(source, fullFileName);
            copied += 1;
        }
        _Log.Information($"{copied} file(s) copied");
        for (int y = 0; y < int.MaxValue; y++)
        {
            _Log.Information("Press \"Y\" key to continue or close console");
            if (Console.ReadKey().Key == ConsoleKey.Y)
                break;
        }
        _Log.Information(". . .");
    }

    private static void Verify(Models.Configuration configuration)
    {
        if (Path.GetPathRoot(configuration.SelectedSource) == configuration.SelectedSource)
            throw new Exception($"{nameof(configuration.SelectedSource)} should have at least one level!");
        if (string.IsNullOrEmpty(configuration.CompareSource) || !Directory.Exists(configuration.CompareSource))
            throw new Exception($"{nameof(configuration.CompareSource)} must have a value and exits!");
        if (string.IsNullOrEmpty(configuration.EmptyDestination) || Directory.Exists(configuration.EmptyDestination))
            throw new Exception($"{nameof(configuration.EmptyDestination)} can't exit!");
        if (string.IsNullOrEmpty(configuration.SelectedSource) || !Directory.Exists(configuration.SelectedSource))
            throw new Exception($"{nameof(configuration.SelectedSource)} must have a value and exits!");
        if (configuration.SelectedSource.Length != configuration.CompareSource.Length)
            throw new Exception($"{nameof(configuration.SelectedSource)} and {nameof(configuration.CompareSource)} must be the same length!");
    }

    private long LogDelta(long ticks, string methodName)
    {
        long result;
        if (_Log is null)
            throw new Exception($"{nameof(_Log)} is null!");
        double delta = new TimeSpan(DateTime.Now.Ticks - ticks).TotalMilliseconds;
        _Log.Debug($"{methodName} took {Math.Floor(delta)} millisecond(s)");
        result = DateTime.Now.Ticks;
        return result;
    }

    private PropertyLogic GetPropertyLogic()
    {
        PropertyLogic result;
        if (_AppSettings.MaxDegreeOfParallelism is null)
            throw new Exception($"{nameof(_AppSettings.MaxDegreeOfParallelism)} is null!");
        if (_Configuration?.PropertyConfiguration is null)
            throw new Exception($"{nameof(_Configuration.PropertyConfiguration)} must be set!");
        result = new(_AppSettings.MaxDegreeOfParallelism.Value, _Configuration.PropertyConfiguration);
        return result;
    }

    private List<(string Source, string[] Destination)> GetCopyCollection(List<Property.Models.DirectoryInfo> compareSourceGroupCollection, List<Property.Models.DirectoryInfo> selectedSourceGroupCollection)
    {
        List<(string Source, string[] Destination)> results = new();
        if (_Configuration?.PropertyConfiguration is null)
            throw new Exception($"{nameof(_Configuration.PropertyConfiguration)} must be set!");
        string key;
        string fileName;
        A_Property? property;
        string destinationDirectory;
        List<string> directoryNames;
        List<string> destinationCollection;
        string filteredSourceDirectoryFile;
        Dictionary<string, A_Property> keyValuePairs = new();
        foreach (Property.Models.DirectoryInfo group in compareSourceGroupCollection)
        {
            for (int i = 0; i < group.SourceDirectoryFileInfoCollection.Length; i++)
            {
                property = group.PropertyCollection[i];
                if (property is null)
                    continue;
                key = string.Concat(group.SourceDirectory[_Configuration.CompareSource.Length..], Path.GetFileName(group.FilteredSourceDirectoryFiles[i]));
                keyValuePairs.Add(key, property);
            }
        }
        foreach (Property.Models.DirectoryInfo group in selectedSourceGroupCollection)
        {
            for (int i = 0; i < group.SourceDirectoryFileInfoCollection.Length; i++)
            {
                destinationCollection = new();
                property = group.PropertyCollection[i];
                if (property is null)
                    continue;
                filteredSourceDirectoryFile = group.FilteredSourceDirectoryFiles[i];
                fileName = Path.GetFileName(filteredSourceDirectoryFile);
                key = string.Concat(group.SourceDirectory[_Configuration.SelectedSource.Length..], fileName);
                if (keyValuePairs.ContainsKey(key) && keyValuePairs[key].LastWriteTime == property.LastWriteTime)
                    continue;
                destinationDirectory = string.Concat(_Configuration.EmptyDestination, group.SourceDirectory[_Configuration.SelectedSource.Length..]);
                directoryNames = Property.Models.Stateless.IPath.GetDirectoryNames(destinationDirectory);
                destinationCollection.AddRange(directoryNames);
                destinationCollection.Add(fileName);
                results.Add(new(filteredSourceDirectoryFile, destinationCollection.ToArray()));
            }
        }
        return results;
    }

}