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;
    private int _currentCount;

    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'
        };
        _currentCount = 0;
        using var progressBar = new ProgressBar(files.Length, "Copying files", options);
        var po = new ParallelOptions { MaxDegreeOfParallelism = 1 };

        Parallel.ForEach(files, po, file => Body(file, progressBar));

        return 0;
    }

    private void Body(string file, ProgressBar progressBar)
    {
        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 _currentCount);
            progressBar.Tick(_currentCount);
        }
    }

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

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

    private static DateTime? GetDateTimeFromMp4(IEnumerable<MetadataExtractor.Directory> directories)
    {
        return directories.OfType<QuickTimeMovieHeaderDirectory>().FirstOrDefault()?.TryGetDateTime(QuickTimeMovieHeaderDirectory.TagCreated, out var dateTime) == true ? 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);

        await using var sourceStream = File.Open(source.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
        await using var targetStream = File.Open(destination.FullName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);

        var progress = new Progress<long>(o => progressBar.Tick((int)o * 100));
        await sourceStream.CopyToAsync(targetStream, 81920, 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;
    }
}
