using System.Reflection;
using HtmlAgilityPack;

namespace OF_DL.Services;

public class FileNameService(IAuthService authService) : IFileNameService
{
    /// <summary>
    /// Builds a map of filename token values from post, media, and author data.
    /// </summary>
    /// <param name="info">The post or message object.</param>
    /// <param name="media">The media object.</param>
    /// <param name="author">The author object.</param>
    /// <param name="selectedProperties">The tokens requested by the filename format.</param>
    /// <param name="username">The resolved username when available.</param>
    /// <param name="users">Optional lookup of user IDs to usernames.</param>
    /// <returns>A dictionary of token values keyed by token name.</returns>
    public async Task<Dictionary<string, string>> GetFilename(object info, object media, object author,
        List<string> selectedProperties, string username, Dictionary<string, long>? users = null)
    {
        Dictionary<string, string> values = new();
        Type type1 = info.GetType();
        Type type2 = media.GetType();
        PropertyInfo[] properties1 = type1.GetProperties();
        PropertyInfo[] properties2 = type2.GetProperties();

        foreach (string propertyName in selectedProperties)
        {
            if (propertyName.Contains("media"))
            {
                object? drmProperty = null;
                object? fileProperty = GetNestedPropertyValue(media, "Files");
                if (fileProperty != null)
                {
                    drmProperty = GetNestedPropertyValue(media, "Files.Drm");
                }

                if (fileProperty != null && drmProperty != null && propertyName == "mediaCreatedAt")
                {
                    string? mpdUrl = GetNestedPropertyValue(media, "Files.Drm.Manifest.Dash") as string;
                    string? policy =
                        GetNestedPropertyValue(media, "Files.Drm.Signature.Dash.CloudFrontPolicy") as string;
                    string? signature =
                        GetNestedPropertyValue(media, "Files.Drm.Signature.Dash.CloudFrontSignature") as string;
                    string? kvp =
                        GetNestedPropertyValue(media, "Files.Drm.Signature.Dash.CloudFrontKeyPairId") as string;

                    if (string.IsNullOrEmpty(mpdUrl) || string.IsNullOrEmpty(policy) ||
                        string.IsNullOrEmpty(signature) || string.IsNullOrEmpty(kvp) ||
                        authService.CurrentAuth == null)
                    {
                        continue;
                    }

                    DateTime lastModified =
                        await DownloadService.GetDrmVideoLastModified(string.Join(",", mpdUrl, policy, signature, kvp),
                            authService.CurrentAuth);
                    values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
                    continue;
                }

                if ((fileProperty == null || drmProperty == null) && propertyName == "mediaCreatedAt")
                {
                    object? source = GetNestedPropertyValue(media, "Files.Full.Url");
                    if (source != null)
                    {
                        DateTime lastModified = await DownloadService.GetMediaLastModified(source.ToString() ?? "");
                        values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
                        continue;
                    }

                    object? preview = GetNestedPropertyValue(media, "Preview");
                    if (preview != null)
                    {
                        DateTime lastModified = await DownloadService.GetMediaLastModified(preview.ToString() ?? "");
                        values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
                        continue;
                    }
                }

                PropertyInfo? property = Array.Find(properties2,
                    p => p.Name.Equals(propertyName.Replace("media", ""), StringComparison.OrdinalIgnoreCase));
                if (property != null)
                {
                    object? propertyValue = property.GetValue(media);
                    if (propertyValue != null)
                    {
                        if (propertyValue is DateTime dateTimeValue)
                        {
                            values.Add(propertyName, dateTimeValue.ToString("yyyy-MM-dd"));
                        }
                        else
                        {
                            values.Add(propertyName, propertyValue.ToString() ?? "");
                        }
                    }
                }
            }
            else if (propertyName.Contains("filename"))
            {
                object? sourcePropertyValue = GetNestedPropertyValue(media, "Files.Full.Url");
                if (sourcePropertyValue != null)
                {
                    Uri uri = new(sourcePropertyValue.ToString() ?? "");
                    string filename = Path.GetFileName(uri.LocalPath);
                    values.Add(propertyName, filename.Split(".")[0]);
                }
                else
                {
                    object? nestedPropertyValue = GetNestedPropertyValue(media, "Files.Drm.Manifest.Dash");
                    if (nestedPropertyValue != null)
                    {
                        Uri uri = new(nestedPropertyValue.ToString() ?? "");
                        string filename = Path.GetFileName(uri.LocalPath);
                        values.Add(propertyName, filename.Split(".")[0] + "_source");
                    }
                }
            }
            else if (propertyName.Contains("username"))
            {
                if (!string.IsNullOrEmpty(username))
                {
                    values.Add(propertyName, username);
                }
                else
                {
                    object? nestedPropertyValue = GetNestedPropertyValue(author, "Id");
                    if (nestedPropertyValue != null && users != null)
                    {
                        values.Add(propertyName,
                            users.FirstOrDefault(u => u.Value == Convert.ToInt32(nestedPropertyValue.ToString())).Key);
                    }
                }
            }
            else if (propertyName.Contains("text", StringComparison.OrdinalIgnoreCase))
            {
                PropertyInfo? property = Array.Find(properties1,
                    p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
                if (property != null)
                {
                    object? propertyValue = property.GetValue(info);
                    if (propertyValue != null)
                    {
                        HtmlDocument pageDoc = new();
                        pageDoc.LoadHtml(propertyValue.ToString() ?? "");
                        string str = pageDoc.DocumentNode.InnerText;
                        if (str.Length > 100) // TODO: add length limit to config
                        {
                            str = str[..100];
                        }

                        values.Add(propertyName, str);
                    }
                }
            }
            else
            {
                PropertyInfo? property = Array.Find(properties1,
                    p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
                if (property != null)
                {
                    object? propertyValue = property.GetValue(info);
                    if (propertyValue != null)
                    {
                        if (propertyValue is DateTime dateTimeValue)
                        {
                            values.Add(propertyName, dateTimeValue.ToString("yyyy-MM-dd"));
                        }
                        else
                        {
                            values.Add(propertyName, propertyValue.ToString() ?? "");
                        }
                    }
                }
            }
        }

        return values;
    }

    /// <summary>
    /// Applies token values to a filename format and removes invalid file name characters.
    /// </summary>
    /// <param name="fileFormat">The filename format string.</param>
    /// <param name="values">Token values to substitute.</param>
    /// <returns>The resolved filename.</returns>
    public Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values)
    {
        foreach (KeyValuePair<string, string> kvp in values)
        {
            string placeholder = "{" + kvp.Key + "}";
            fileFormat = fileFormat.Replace(placeholder, kvp.Value);
        }

        return Task.FromResult(RemoveInvalidFileNameChars($"{fileFormat}"));
    }

    private static object? GetNestedPropertyValue(object source, string propertyPath)
    {
        object? value = source;
        foreach (string propertyName in propertyPath.Split('.'))
        {
            if (value == null)
            {
                return null;
            }

            PropertyInfo? property = value.GetType().GetProperty(propertyName,
                BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            if (property == null)
            {
                return null;
            }

            value = property.GetValue(value);
        }

        return value;
    }

    private static string RemoveInvalidFileNameChars(string fileName) => string.IsNullOrEmpty(fileName)
        ? fileName
        : string.Concat(fileName.Split(Path.GetInvalidFileNameChars()));
}
