using System.Reflection;
using System.Text;
using Akka.Configuration;
using Newtonsoft.Json;
using OF_DL.Enumerations;
using OF_DL.Models.Config;
using OF_DL.Utils;
using Serilog;
using Config = OF_DL.Models.Config.Config;

namespace OF_DL.Services;

public class ConfigService(ILoggingService loggingService) : IConfigService
{
    /// <summary>
    /// Gets the active configuration in memory.
    /// </summary>
    public Config CurrentConfig { get; private set; } = new();

    /// <summary>
    /// Gets whether the CLI requested non-interactive mode.
    /// </summary>
    public bool IsCliNonInteractive { get; private set; }

    /// <summary>
    /// Loads configuration from disk and applies runtime settings.
    /// </summary>
    /// <param name="args">CLI arguments used to influence configuration.</param>
    /// <returns>True when configuration is loaded successfully.</returns>
    public async Task<bool> LoadConfigurationAsync(string[] args)
    {
        try
        {
            IsCliNonInteractive = false;
            if (args.Length > 0)
            {
                const string nonInteractiveArg = "--non-interactive";
                if (args.Any(a => a.Equals(nonInteractiveArg, StringComparison.OrdinalIgnoreCase)))
                {
                    IsCliNonInteractive = true;
                    Log.Debug("NonInteractiveMode set via command line");
                }

                Log.Debug("Additional arguments:");
                foreach (string argument in args)
                {
                    Log.Debug(argument);
                }
            }

            // Migrate from config.json to config.conf if needed
            await MigrateFromJsonToConfAsync();

            // Load config.conf or create default
            if (File.Exists("config.conf"))
            {
                Log.Debug("config.conf located successfully");
                if (!await LoadConfigFromFileAsync("config.conf"))
                {
                    return false;
                }
            }
            else
            {
                Log.Debug("config.conf not found, creating default");
                await CreateDefaultConfigFileAsync();
                if (!await LoadConfigFromFileAsync("config.conf"))
                {
                    return false;
                }
            }

            if (IsCliNonInteractive && !CurrentConfig.NonInteractiveMode)
            {
                CurrentConfig.NonInteractiveMode = true;
                Log.Debug("NonInteractiveMode overridden to true via command line");
            }

            return true;
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Configuration loading failed");
            return false;
        }
    }

    /// <summary>
    /// Saves the current configuration to disk.
    /// </summary>
    /// <param name="filePath">The destination config file path.</param>
    public async Task SaveConfigurationAsync(string filePath = "config.conf")
    {
        try
        {
            string hoconConfig = BuildHoconFromConfig(CurrentConfig);
            await File.WriteAllTextAsync(filePath, hoconConfig);
            Log.Debug($"Config saved to file: {filePath}");
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Failed to save config to file");
        }
    }

    /// <summary>
    /// Replaces the current configuration and applies runtime settings.
    /// </summary>
    /// <param name="newConfig">The new configuration instance.</param>
    public void UpdateConfig(Config newConfig)
    {
        CurrentConfig = newConfig;

        // Update logging level
        loggingService.UpdateLoggingLevel(newConfig.LoggingLevel);

        // Apply text sanitization preference globally
        XmlUtils.Passthrough = newConfig.DisableTextSanitization;

        Log.Debug("Configuration updated");
        string configString = JsonConvert.SerializeObject(newConfig, Formatting.Indented);
        Log.Debug(configString);
    }

    private async Task MigrateFromJsonToConfAsync()
    {
        if (!File.Exists("config.json"))
        {
            return;
        }

        try
        {
            Log.Debug("config.json found, migrating to config.conf");
            string jsonText = await File.ReadAllTextAsync("config.json");
            Config? jsonConfig = JsonConvert.DeserializeObject<Config>(jsonText);

            if (jsonConfig != null)
            {
                string hoconConfig = BuildHoconFromConfig(jsonConfig);
                await File.WriteAllTextAsync("config.conf", hoconConfig);
                File.Delete("config.json");
                Log.Information("config.conf created successfully from config.json");
            }
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Failed to migrate config.json to config.conf");
            throw;
        }
    }

    private async Task<bool> LoadConfigFromFileAsync(string filePath)
    {
        try
        {
            string hoconText = await File.ReadAllTextAsync(filePath);
            Akka.Configuration.Config? hoconConfig = ConfigurationFactory.ParseString(hoconText);

            CurrentConfig = new Config
            {
                // Auth
                DisableBrowserAuth = hoconConfig.GetBoolean("Auth.DisableBrowserAuth"),

                // FFmpeg Settings
                FFmpegPath = hoconConfig.GetString("External.FFmpegPath"),

                // Download Settings
                DownloadAvatarHeaderPhoto = hoconConfig.GetBoolean("Download.Media.DownloadAvatarHeaderPhoto"),
                DownloadPaidPosts = hoconConfig.GetBoolean("Download.Media.DownloadPaidPosts"),
                DownloadPosts = hoconConfig.GetBoolean("Download.Media.DownloadPosts"),
                DownloadArchived = hoconConfig.GetBoolean("Download.Media.DownloadArchived"),
                DownloadStreams = hoconConfig.GetBoolean("Download.Media.DownloadStreams"),
                DownloadStories = hoconConfig.GetBoolean("Download.Media.DownloadStories"),
                DownloadHighlights = hoconConfig.GetBoolean("Download.Media.DownloadHighlights"),
                DownloadMessages = hoconConfig.GetBoolean("Download.Media.DownloadMessages"),
                DownloadPaidMessages = hoconConfig.GetBoolean("Download.Media.DownloadPaidMessages"),
                DownloadImages = hoconConfig.GetBoolean("Download.Media.DownloadImages"),
                DownloadVideos = hoconConfig.GetBoolean("Download.Media.DownloadVideos"),
                DownloadAudios = hoconConfig.GetBoolean("Download.Media.DownloadAudios"),
                IgnoreOwnMessages = hoconConfig.GetBoolean("Download.IgnoreOwnMessages"),
                DownloadPostsIncrementally = hoconConfig.GetBoolean("Download.DownloadPostsIncrementally"),
                BypassContentForCreatorsWhoNoLongerExist =
                    hoconConfig.GetBoolean("Download.BypassContentForCreatorsWhoNoLongerExist"),
                DownloadDuplicatedMedia = hoconConfig.GetBoolean("Download.DownloadDuplicatedMedia"),
                SkipAds = hoconConfig.GetBoolean("Download.SkipAds"),
                DownloadPath = hoconConfig.GetString("Download.DownloadPath"),
                DownloadOnlySpecificDates = hoconConfig.GetBoolean("Download.DownloadOnlySpecificDates"),
                DownloadDateSelection =
                    Enum.Parse<DownloadDateSelection>(hoconConfig.GetString("Download.DownloadDateSelection"), true),
                CustomDate =
                    !string.IsNullOrWhiteSpace(hoconConfig.GetString("Download.CustomDate"))
                        ? DateTime.Parse(hoconConfig.GetString("Download.CustomDate"))
                        : null,
                ShowScrapeSize = hoconConfig.GetBoolean("Download.ShowScrapeSize"),
                DisableTextSanitization =
                    bool.TryParse(hoconConfig.GetString("Download.DisableTextSanitization", "false"), out bool dts)
                        ? dts
                        : false,
                DownloadVideoResolution =
                    ParseVideoResolution(hoconConfig.GetString("Download.DownloadVideoResolution", "source")),

                // File Settings
                PaidPostFileNameFormat = hoconConfig.GetString("File.PaidPostFileNameFormat"),
                PostFileNameFormat = hoconConfig.GetString("File.PostFileNameFormat"),
                PaidMessageFileNameFormat = hoconConfig.GetString("File.PaidMessageFileNameFormat"),
                MessageFileNameFormat = hoconConfig.GetString("File.MessageFileNameFormat"),
                RenameExistingFilesWhenCustomFormatIsSelected =
                    hoconConfig.GetBoolean("File.RenameExistingFilesWhenCustomFormatIsSelected"),

                // Folder Settings
                FolderPerPaidPost = hoconConfig.GetBoolean("Folder.FolderPerPaidPost"),
                FolderPerPost = hoconConfig.GetBoolean("Folder.FolderPerPost"),
                FolderPerPaidMessage = hoconConfig.GetBoolean("Folder.FolderPerPaidMessage"),
                FolderPerMessage = hoconConfig.GetBoolean("Folder.FolderPerMessage"),

                // Subscription Settings
                IncludeExpiredSubscriptions = hoconConfig.GetBoolean("Subscriptions.IncludeExpiredSubscriptions"),
                IncludeRestrictedSubscriptions = hoconConfig.GetBoolean("Subscriptions.IncludeRestrictedSubscriptions"),
                IgnoredUsersListName = hoconConfig.GetString("Subscriptions.IgnoredUsersListName"),

                // Interaction Settings
                NonInteractiveMode = hoconConfig.GetBoolean("Interaction.NonInteractiveMode"),
                NonInteractiveModeListName = hoconConfig.GetString("Interaction.NonInteractiveModeListName"),
                NonInteractiveModePurchasedTab = hoconConfig.GetBoolean("Interaction.NonInteractiveModePurchasedTab"),

                // Performance Settings
                Timeout =
                    string.IsNullOrWhiteSpace(hoconConfig.GetString("Performance.Timeout"))
                        ? -1
                        : hoconConfig.GetInt("Performance.Timeout"),
                LimitDownloadRate = hoconConfig.GetBoolean("Performance.LimitDownloadRate"),
                DownloadLimitInMbPerSec = hoconConfig.GetInt("Performance.DownloadLimitInMbPerSec"),

                // Logging/Debug Settings
                LoggingLevel = Enum.Parse<LoggingLevel>(hoconConfig.GetString("Logging.LoggingLevel"), true)
            };

            // Validate file name formats
            ValidateFileNameFormat(CurrentConfig.PaidPostFileNameFormat, "PaidPostFileNameFormat");
            ValidateFileNameFormat(CurrentConfig.PostFileNameFormat, "PostFileNameFormat");
            ValidateFileNameFormat(CurrentConfig.PaidMessageFileNameFormat, "PaidMessageFileNameFormat");
            ValidateFileNameFormat(CurrentConfig.MessageFileNameFormat, "MessageFileNameFormat");

            // Load creator-specific configs
            Akka.Configuration.Config? creatorConfigsSection = hoconConfig.GetConfig("CreatorConfigs");
            if (creatorConfigsSection != null)
            {
                foreach ((string? creatorKey, _) in creatorConfigsSection.AsEnumerable())
                {
                    Akka.Configuration.Config? creatorHocon = creatorConfigsSection.GetConfig(creatorKey);
                    if (!CurrentConfig.CreatorConfigs.ContainsKey(creatorKey) && creatorHocon != null)
                    {
                        CurrentConfig.CreatorConfigs.Add(creatorKey,
                            new CreatorConfig
                            {
                                PaidPostFileNameFormat = creatorHocon.GetString("PaidPostFileNameFormat"),
                                PostFileNameFormat = creatorHocon.GetString("PostFileNameFormat"),
                                PaidMessageFileNameFormat = creatorHocon.GetString("PaidMessageFileNameFormat"),
                                MessageFileNameFormat = creatorHocon.GetString("MessageFileNameFormat")
                            });

                        ValidateFileNameFormat(CurrentConfig.CreatorConfigs[creatorKey].PaidPostFileNameFormat,
                            $"{creatorKey}.PaidPostFileNameFormat");
                        ValidateFileNameFormat(CurrentConfig.CreatorConfigs[creatorKey].PostFileNameFormat,
                            $"{creatorKey}.PostFileNameFormat");
                        ValidateFileNameFormat(CurrentConfig.CreatorConfigs[creatorKey].PaidMessageFileNameFormat,
                            $"{creatorKey}.PaidMessageFileNameFormat");
                        ValidateFileNameFormat(CurrentConfig.CreatorConfigs[creatorKey].MessageFileNameFormat,
                            $"{creatorKey}.MessageFileNameFormat");
                    }
                }
            }

            // Update logging level
            loggingService.UpdateLoggingLevel(CurrentConfig.LoggingLevel);

            // Apply text sanitization preference globally
            XmlUtils.Passthrough = CurrentConfig.DisableTextSanitization;

            Log.Debug("Configuration loaded successfully");
            string configString = JsonConvert.SerializeObject(CurrentConfig, Formatting.Indented);
            Log.Debug(configString);

            return true;
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Failed to parse config file");
            return false;
        }
    }

    private async Task CreateDefaultConfigFileAsync()
    {
        Config defaultConfig = new();
        string hoconConfig = BuildHoconFromConfig(defaultConfig);
        await File.WriteAllTextAsync("config.conf", hoconConfig);
        Log.Information("Created default config.conf file");
    }

    private string BuildHoconFromConfig(Config config)
    {
        StringBuilder hocon = new();

        hocon.AppendLine("# Auth");
        hocon.AppendLine("Auth {");
        hocon.AppendLine($"  DisableBrowserAuth = {config.DisableBrowserAuth.ToString().ToLower()}");
        hocon.AppendLine("}");

        hocon.AppendLine("# External Tools");
        hocon.AppendLine("External {");
        hocon.AppendLine($"  FFmpegPath = \"{config.FFmpegPath}\"");
        hocon.AppendLine("}");

        hocon.AppendLine("# Download Settings");
        hocon.AppendLine("Download {");
        hocon.AppendLine("  Media {");
        hocon.AppendLine($"    DownloadAvatarHeaderPhoto = {config.DownloadAvatarHeaderPhoto.ToString().ToLower()}");
        hocon.AppendLine($"    DownloadPaidPosts = {config.DownloadPaidPosts.ToString().ToLower()}");
        hocon.AppendLine($"    DownloadPosts = {config.DownloadPosts.ToString().ToLower()}");
        hocon.AppendLine($"    DownloadArchived = {config.DownloadArchived.ToString().ToLower()}");
        hocon.AppendLine($"    DownloadStreams = {config.DownloadStreams.ToString().ToLower()}");
        hocon.AppendLine($"    DownloadStories = {config.DownloadStories.ToString().ToLower()}");
        hocon.AppendLine($"    DownloadHighlights = {config.DownloadHighlights.ToString().ToLower()}");
        hocon.AppendLine($"    DownloadMessages = {config.DownloadMessages.ToString().ToLower()}");
        hocon.AppendLine($"    DownloadPaidMessages = {config.DownloadPaidMessages.ToString().ToLower()}");
        hocon.AppendLine($"    DownloadImages = {config.DownloadImages.ToString().ToLower()}");
        hocon.AppendLine($"    DownloadVideos = {config.DownloadVideos.ToString().ToLower()}");
        hocon.AppendLine($"    DownloadAudios = {config.DownloadAudios.ToString().ToLower()}");
        hocon.AppendLine("  }");
        hocon.AppendLine($"  IgnoreOwnMessages = {config.IgnoreOwnMessages.ToString().ToLower()}");
        hocon.AppendLine($"  DownloadPostsIncrementally = {config.DownloadPostsIncrementally.ToString().ToLower()}");
        hocon.AppendLine(
            $"  BypassContentForCreatorsWhoNoLongerExist = {config.BypassContentForCreatorsWhoNoLongerExist.ToString().ToLower()}");
        hocon.AppendLine($"  DownloadDuplicatedMedia = {config.DownloadDuplicatedMedia.ToString().ToLower()}");
        hocon.AppendLine($"  SkipAds = {config.SkipAds.ToString().ToLower()}");
        hocon.AppendLine($"  DownloadPath = \"{config.DownloadPath}\"");
        hocon.AppendLine($"  DownloadOnlySpecificDates = {config.DownloadOnlySpecificDates.ToString().ToLower()}");
        hocon.AppendLine($"  DownloadDateSelection = \"{config.DownloadDateSelection.ToString().ToLower()}\"");
        hocon.AppendLine($"  CustomDate = \"{config.CustomDate?.ToString("yyyy-MM-dd")}\"");
        hocon.AppendLine($"  ShowScrapeSize = {config.ShowScrapeSize.ToString().ToLower()}");
        hocon.AppendLine($"  DisableTextSanitization = {config.DisableTextSanitization.ToString().ToLower()}");
        hocon.AppendLine(
            $"  DownloadVideoResolution = \"{(config.DownloadVideoResolution == VideoResolution.source ? "source" : config.DownloadVideoResolution.ToString().TrimStart('_'))}\"");
        hocon.AppendLine("}");

        hocon.AppendLine("# File Settings");
        hocon.AppendLine("File {");
        hocon.AppendLine($"  PaidPostFileNameFormat = \"{config.PaidPostFileNameFormat}\"");
        hocon.AppendLine($"  PostFileNameFormat = \"{config.PostFileNameFormat}\"");
        hocon.AppendLine($"  PaidMessageFileNameFormat = \"{config.PaidMessageFileNameFormat}\"");
        hocon.AppendLine($"  MessageFileNameFormat = \"{config.MessageFileNameFormat}\"");
        hocon.AppendLine(
            $"  RenameExistingFilesWhenCustomFormatIsSelected = {config.RenameExistingFilesWhenCustomFormatIsSelected.ToString().ToLower()}");
        hocon.AppendLine("}");

        hocon.AppendLine("# Creator-Specific Configurations");
        hocon.AppendLine("CreatorConfigs {");
        foreach (KeyValuePair<string, CreatorConfig> creatorConfig in config.CreatorConfigs)
        {
            hocon.AppendLine($"  \"{creatorConfig.Key}\" {{");
            hocon.AppendLine($"    PaidPostFileNameFormat = \"{creatorConfig.Value.PaidPostFileNameFormat}\"");
            hocon.AppendLine($"    PostFileNameFormat = \"{creatorConfig.Value.PostFileNameFormat}\"");
            hocon.AppendLine($"    PaidMessageFileNameFormat = \"{creatorConfig.Value.PaidMessageFileNameFormat}\"");
            hocon.AppendLine($"    MessageFileNameFormat = \"{creatorConfig.Value.MessageFileNameFormat}\"");
            hocon.AppendLine("  }");
        }

        hocon.AppendLine("}");

        hocon.AppendLine("# Folder Settings");
        hocon.AppendLine("Folder {");
        hocon.AppendLine($"  FolderPerPaidPost = {config.FolderPerPaidPost.ToString().ToLower()}");
        hocon.AppendLine($"  FolderPerPost = {config.FolderPerPost.ToString().ToLower()}");
        hocon.AppendLine($"  FolderPerPaidMessage = {config.FolderPerPaidMessage.ToString().ToLower()}");
        hocon.AppendLine($"  FolderPerMessage = {config.FolderPerMessage.ToString().ToLower()}");
        hocon.AppendLine("}");

        hocon.AppendLine("# Subscription Settings");
        hocon.AppendLine("Subscriptions {");
        hocon.AppendLine($"  IncludeExpiredSubscriptions = {config.IncludeExpiredSubscriptions.ToString().ToLower()}");
        hocon.AppendLine(
            $"  IncludeRestrictedSubscriptions = {config.IncludeRestrictedSubscriptions.ToString().ToLower()}");
        hocon.AppendLine($"  IgnoredUsersListName = \"{config.IgnoredUsersListName}\"");
        hocon.AppendLine("}");

        hocon.AppendLine("# Interaction Settings");
        hocon.AppendLine("Interaction {");
        hocon.AppendLine($"  NonInteractiveMode = {config.NonInteractiveMode.ToString().ToLower()}");
        hocon.AppendLine($"  NonInteractiveModeListName = \"{config.NonInteractiveModeListName}\"");
        hocon.AppendLine(
            $"  NonInteractiveModePurchasedTab = {config.NonInteractiveModePurchasedTab.ToString().ToLower()}");
        hocon.AppendLine("}");

        hocon.AppendLine("# Performance Settings");
        hocon.AppendLine("Performance {");
        hocon.AppendLine($"  Timeout = {(config.Timeout.HasValue ? config.Timeout.Value : -1)}");
        hocon.AppendLine($"  LimitDownloadRate = {config.LimitDownloadRate.ToString().ToLower()}");
        hocon.AppendLine($"  DownloadLimitInMbPerSec = {config.DownloadLimitInMbPerSec}");
        hocon.AppendLine("}");

        hocon.AppendLine("# Logging/Debug Settings");
        hocon.AppendLine("Logging {");
        hocon.AppendLine($"  LoggingLevel = \"{config.LoggingLevel.ToString().ToLower()}\"");
        hocon.AppendLine("}");

        return hocon.ToString();
    }

    private void ValidateFileNameFormat(string? format, string settingName)
    {
        if (!string.IsNullOrEmpty(format) &&
            !format.Contains("{mediaId}", StringComparison.OrdinalIgnoreCase) &&
            !format.Contains("{filename}", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException(
                $"{settingName} is not unique enough. Please include either '{{mediaId}}' or '{{filename}}' to ensure files are not overwritten.");
        }
    }

    /// <summary>
    /// Returns toggleable config properties and their current values.
    /// </summary>
    public List<(string Name, bool Value)> GetToggleableProperties()
    {
        List<(string Name, bool Value)> result = [];
        foreach (PropertyInfo propInfo in typeof(Config).GetProperties())
        {
            ToggleableConfigAttribute? attr = propInfo.GetCustomAttribute<ToggleableConfigAttribute>();
            if (attr != null)
            {
                bool? value = (bool?)propInfo.GetValue(CurrentConfig);
                if (value == null)
                {
                    continue;
                }

                result.Add((propInfo.Name, value.Value));
            }
        }

        return result;
    }

    /// <summary>
    /// Applies a set of toggle selections to the configuration.
    /// </summary>
    /// <param name="selectedNames">The names of toggles that should be enabled.</param>
    /// <returns>True when any values were changed.</returns>
    public bool ApplyToggleableSelections(List<string> selectedNames)
    {
        bool configChanged = false;
        Config newConfig = new();

        foreach (PropertyInfo propInfo in typeof(Config).GetProperties())
        {
            ToggleableConfigAttribute? attr = propInfo.GetCustomAttribute<ToggleableConfigAttribute>();
            if (attr != null)
            {
                bool newValue = selectedNames.Contains(propInfo.Name);
                bool? oldValue = (bool?)propInfo.GetValue(CurrentConfig);

                if (oldValue == null)
                {
                    continue;
                }

                propInfo.SetValue(newConfig, newValue);

                if (newValue != oldValue.Value)
                {
                    configChanged = true;
                }
            }
            else
            {
                propInfo.SetValue(newConfig, propInfo.GetValue(CurrentConfig));
            }
        }

        UpdateConfig(newConfig);
        return configChanged;
    }

    private VideoResolution ParseVideoResolution(string value)
    {
        if (value.Equals("source", StringComparison.OrdinalIgnoreCase))
        {
            return VideoResolution.source;
        }

        return Enum.Parse<VideoResolution>("_" + value, true);
    }
}
