using MetadataExtractor;
using MetadataExtractor.Formats.Exif;
using MetadataExtractor.Formats.QuickTime;
using Microsoft.Extensions.Configuration;
using Serilog;
using ShellProgressBar;
using Directory = System.IO.Directory;

namespace PhotoRenamer
{
    internal class Renamer
    {
        private readonly ProgressBarOptions _childOptions;
        private readonly string _sourcePath;
        private readonly string _targetPath;

        public Renamer(IConfiguration configuration)
        {
            _sourcePath = Path.GetFullPath(configuration["Source"]);
            _targetPath = Path.GetFullPath(configuration["Target"]);
            Log.Information($"Source path: {_sourcePath}");
            Log.Information($"Target path: {_targetPath}");
            _childOptions = new ProgressBarOptions
            {
                ForegroundColor = ConsoleColor.Yellow,
                BackgroundColor = ConsoleColor.DarkGreen,
                ProgressCharacter = '─',
                CollapseWhenFinished = true
            };
        }

        public int Run()
        {
            var files = Directory.GetFiles(_sourcePath, "*", SearchOption.AllDirectories);
            var options = new ProgressBarOptions
            {
                ForegroundColor = ConsoleColor.Yellow,
                ForegroundColorDone = ConsoleColor.DarkGreen,
                BackgroundColor = ConsoleColor.DarkGray,
                BackgroundCharacter = '\u2593'
            };
            var i = 0;
            using var progressBar = new ProgressBar(files.Length, "Copying files", options);
            var po = new ParallelOptions { MaxDegreeOfParallelism = 4 };
            Parallel.ForEach(
                files,
                po,
                file =>
                {
                    try
                    {
                        var directories = ImageMetadataReader.ReadMetadata(file);
                        var dateTime =
                            GetDateTimeFromExif(directories)
                            ?? GetDateTimeFromMp4(directories)
                            ?? GetDateTimeFromLastWrite(file);
                        var folder = CreateFolder(dateTime);
                        CopyFile(folder, file, progressBar);
                    }
                    catch (ImageProcessingException)
                    {
                        //silently ignore
                    }
                    finally
                    {
                        Interlocked.Increment(ref i);
                        progressBar.Tick(i);
                    }
                }
            );

            return 0;
        }

        private static DateTime GetDateTimeFromLastWrite(string file)
        {
            var creationTime = File.GetLastWriteTime(file);
            return creationTime;
        }

        private static DateTime? GetDateTimeFromExif(
            IEnumerable<MetadataExtractor.Directory> directories
        )
        {
            DateTime dateTime = default;
            return
                directories
                    .OfType<ExifIfd0Directory>()
                    .FirstOrDefault()
                    ?.TryGetDateTime(ExifDirectoryBase.TagDateTime, out dateTime) == true
                ? (DateTime?)dateTime
                : null;
        }

        private static DateTime? GetDateTimeFromMp4(
            IEnumerable<MetadataExtractor.Directory> directories
        )
        {
            DateTime dateTime = default;
            return
                directories
                    .OfType<QuickTimeMovieHeaderDirectory>()
                    .FirstOrDefault()
                    ?.TryGetDateTime(QuickTimeMovieHeaderDirectory.TagCreated, out dateTime) == true
                ? (DateTime?)dateTime
                : null;
        }

        private static void ResetTimes(FileSystemInfo destination, FileSystemInfo source)
        {
            destination.LastWriteTime = source.LastWriteTime;
            destination.LastWriteTimeUtc = source.LastWriteTimeUtc;
            destination.CreationTime = source.CreationTime;
            destination.CreationTimeUtc = source.CreationTimeUtc;
        }

        private async void CopyFile(string folder, string file, ProgressBarBase progressBar)
        {
            var destination = new FileInfo(Path.Combine(folder, Path.GetFileName(file)));
            var source = new FileInfo(file);
            if (destination.Exists && destination.Length == source.Length)
                return;

            using var child = progressBar.Spawn(100, destination.FullName, _childOptions);
            using var client = new HttpClient();
            using var fileStream = File.OpenWrite(destination.FullName);

            var progress = new Progress<float>(o => progressBar.Tick((int)o * 100));
            await client.DownloadAsync(source.FullName, fileStream, progress);
            ResetTimes(destination, source);

            child.Tick(100);
        }

        private string CreateFolder(DateTime dateTime)
        {
            var folder = Path.Combine(
                _targetPath,
                dateTime.Year.ToString(),
                dateTime.Month.ToString("D2")
            );
            if (!Directory.Exists(folder))
                Directory.CreateDirectory(folder);
            return folder;
        }
    }
}
