using MetadataExtractor;
using System.Collections.ObjectModel;
using System.Text.Json;
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Methods;
using View_by_Distance.Shared.Models.Properties;

namespace View_by_Distance.Metadata.Models;

/// <summary>
// Dictionary<string, List<KeyValuePair<string, string>>>
/// </summary>
public class B_Metadata : IMetadata<MetadataExtractor.Directory>
{

    private readonly bool _PropertiesChangedForMetadata;
    private readonly IPropertyConfiguration _PropertyConfiguration;
    private readonly bool _ForceMetadataLastWriteTimeToCreationTime;
    private readonly JsonSerializerOptions _WriteIndentedJsonSerializerOptions;
    private readonly ReadOnlyDictionary<string, ReadOnlyCollection<string>> _FileGroups;

    public B_Metadata(IPropertyConfiguration propertyConfiguration)
    {
        _PropertiesChangedForMetadata = false;
        _PropertyConfiguration = propertyConfiguration;
        _ForceMetadataLastWriteTimeToCreationTime = false;
        _WriteIndentedJsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true };
        _FileGroups = Shared.Models.Stateless.Methods.IPath.GetKeyValuePairs(propertyConfiguration, null, [propertyConfiguration.ResultSingleton]);
    }

    public B_Metadata(IPropertyConfiguration propertyConfiguration, bool forceMetadataLastWriteTimeToCreationTime, bool propertiesChangedForMetadata, string bResultsFullGroupDirectory)
    {
        _PropertyConfiguration = propertyConfiguration;
        _PropertiesChangedForMetadata = propertiesChangedForMetadata;
        _ForceMetadataLastWriteTimeToCreationTime = forceMetadataLastWriteTimeToCreationTime;
        _WriteIndentedJsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true };
        _FileGroups = Shared.Models.Stateless.Methods.IPath.GetKeyValuePairs(propertyConfiguration, bResultsFullGroupDirectory, [propertyConfiguration.ResultSingleton]);
    }

    public override string ToString()
    {
        string result = JsonSerializer.Serialize(this, _WriteIndentedJsonSerializerOptions);
        return result;
    }

    public ExifDirectory GetMetadataCollection(FilePath filePath, List<Tuple<string, DateTime>> subFileTuples, List<string> parseExceptions, string[] changesFrom, MappingFromItem mappingFromItem)
    {
        ExifDirectory? result = null;
        List<DateTime> dateTimes = (from l in subFileTuples where changesFrom.Contains(l.Item1) select l.Item2).ToList();
        (_, int directoryIndex) = Shared.Models.Stateless.Methods.IPath.GetDirectoryNameAndIndex(_PropertyConfiguration, filePath);
        FileInfo fileInfo = new(Path.Combine(_FileGroups[_PropertyConfiguration.ResultSingleton][directoryIndex], $"{mappingFromItem.FilePath.NameWithoutExtension}{mappingFromItem.FilePath.ExtensionLowered}.json"));
        if (_ForceMetadataLastWriteTimeToCreationTime && !fileInfo.Exists && File.Exists(Path.ChangeExtension(fileInfo.FullName, ".delete")))
        {
            File.Move(Path.ChangeExtension(fileInfo.FullName, ".delete"), fileInfo.FullName);
            fileInfo.Refresh();
        }
        if (_ForceMetadataLastWriteTimeToCreationTime && fileInfo.Exists && fileInfo.LastWriteTime != fileInfo.CreationTime)
        {
            File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime);
            fileInfo.Refresh();
        }
        if (_PropertiesChangedForMetadata)
            result = null;
        else if (!fileInfo.Exists)
            result = null;
        else if (!fileInfo.FullName.EndsWith(".json") && !fileInfo.FullName.EndsWith(".old"))
            throw new ArgumentException("must be a *.json file");
        else if (dateTimes.Count != 0 && dateTimes.Max() > fileInfo.LastWriteTime)
            result = null;
        else
        {
            string json = File.ReadAllText(fileInfo.FullName);
            try
            {
                result = JsonSerializer.Deserialize(json, ExifDirectorySourceGenerationContext.Default.ExifDirectory);
                if (result is null)
                    throw new Exception();
                subFileTuples.Add(new Tuple<string, DateTime>(nameof(B_Metadata), fileInfo.LastWriteTime));
            }
            catch (Exception)
            {
                result = null;
                parseExceptions.Add(nameof(B_Metadata));
            }
        }
        if (result is null)
        {
            result = Stateless.Methods.IMetadata.GetExifDirectory(filePath);
            string json = JsonSerializer.Serialize(result, ExifDirectorySourceGenerationContext.Default.ExifDirectory);
            bool updateDateWhenMatches = dateTimes.Count != 0 && fileInfo.Exists && dateTimes.Max() > fileInfo.LastWriteTime;
            DateTime? dateTime = !updateDateWhenMatches ? null : dateTimes.Max();
            if (Shared.Models.Stateless.Methods.IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches, compareBeforeWrite: true, updateToWhenMatches: dateTime))
            {
                if (!_ForceMetadataLastWriteTimeToCreationTime)
                    subFileTuples.Add(new Tuple<string, DateTime>(nameof(B_Metadata), DateTime.Now));
                else
                {
                    File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime);
                    fileInfo.Refresh();
                    subFileTuples.Add(new Tuple<string, DateTime>(nameof(B_Metadata), fileInfo.CreationTime));
                }
            }
        }
        return result;
    }

    (DateTime?, DateTime?[]) IMetadata<MetadataExtractor.Directory>.GetDateTimes(FilePath filePath, IReadOnlyList<MetadataExtractor.Directory> directories)
    {
        List<DateTime?> results = [];
        DateTime? result = null;
        DateTime? dateTime;
        DateTime checkDateTime;
        string dateTimeFormat = Property.Models.Stateless.IProperty.DateTimeFormat();
        MetadataExtractor.Formats.Exif.ExifDirectoryBase? exifDirectoryBase = directories.OfType<MetadataExtractor.Formats.Exif.ExifDirectoryBase>().FirstOrDefault();
        results.Add(new DateTime(filePath.CreationTicks));
        results.Add(new DateTime(filePath.LastWriteTicks));
        if (exifDirectoryBase is not null)
        {
            if (exifDirectoryBase.TryGetDateTime(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTime, out checkDateTime))
                results.Add(checkDateTime);
            else
            {
                dateTime = Property.Models.Stateless.IProperty.GetDateTime(dateTimeFormat, exifDirectoryBase.GetString(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTime));
                if (dateTime is not null)
                    results.Add(dateTime.Value);
            }
            if (exifDirectoryBase.TryGetDateTime(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeDigitized, out checkDateTime))
                results.Add(checkDateTime);
            else
            {
                dateTime = Property.Models.Stateless.IProperty.GetDateTime(dateTimeFormat, exifDirectoryBase.GetString(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeDigitized));
                if (dateTime is not null)
                    results.Add(dateTime.Value);
            }
            if (exifDirectoryBase.TryGetDateTime(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeOriginal, out checkDateTime))
            {
                result ??= checkDateTime;
                results.Add(checkDateTime);
            }
            else
            {
                dateTime = Property.Models.Stateless.IProperty.GetDateTime(dateTimeFormat, exifDirectoryBase.GetString(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeOriginal));
                if (dateTime is not null)
                {
                    result ??= dateTime.Value;
                    results.Add(dateTime.Value);
                }
            }
        }
        MetadataExtractor.Formats.Avi.AviDirectory? aviDirectory = directories.OfType<MetadataExtractor.Formats.Avi.AviDirectory>().FirstOrDefault();
        if (aviDirectory is not null)
        {
            if (aviDirectory.TryGetDateTime(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal, out checkDateTime))
            {
                result ??= checkDateTime;
                results.Add(checkDateTime);
            }
            else
            {
                dateTime = Property.Models.Stateless.IProperty.GetDateTime(dateTimeFormat, aviDirectory.GetString(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal));
                if (dateTime is not null)
                {
                    result ??= dateTime.Value;
                    results.Add(dateTime.Value);
                }
            }
        }
        MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory? quickTimeMovieHeaderDirectory = directories.OfType<MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory>().FirstOrDefault();
        if (quickTimeMovieHeaderDirectory is not null)
        {
            if (quickTimeMovieHeaderDirectory.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated, out checkDateTime))
            {
                result ??= checkDateTime;
                results.Add(checkDateTime);
            }
            else
            {
                dateTime = Property.Models.Stateless.IProperty.GetDateTime(dateTimeFormat, quickTimeMovieHeaderDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated));
                if (dateTime is not null)
                {
                    result ??= dateTime.Value;
                    results.Add(dateTime.Value);
                }
            }
        }
        MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory? quickTimeTrackHeaderDirectory = directories.OfType<MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory>().FirstOrDefault();
        if (quickTimeTrackHeaderDirectory is not null)
        {
            if (quickTimeTrackHeaderDirectory.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated, out checkDateTime))
            {
                result ??= checkDateTime;
                results.Add(checkDateTime);
            }
            else
            {
                dateTime = Property.Models.Stateless.IProperty.GetDateTime(dateTimeFormat, quickTimeTrackHeaderDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated));
                if (dateTime is not null)
                {
                    result ??= dateTime.Value;
                    results.Add(dateTime.Value);
                }
            }
        }
        return new(result, [.. results]);
    }

}