using System.Text.Json;

namespace YMhut.Box.Core.Settings;

public sealed class AppSettingsStore
{
    private static readonly string LegacyPreferencePrefix = string.Concat("flut", "ter.");
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
    {
        WriteIndented = true
    };

    private readonly string _settingsPath;

    public AppSettingsStore(string? root = null)
    {
        var appData = root ?? Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "YMhut Box",
            "WinUI");
        Directory.CreateDirectory(appData);
        _settingsPath = Path.Combine(appData, "settings.json");
    }

    public string SettingsPath => _settingsPath;

    public async Task<AppSettings> LoadAsync(CancellationToken cancellationToken = default)
    {
        if (!File.Exists(_settingsPath))
        {
            var legacy = await TryImportLegacySettingsAsync(cancellationToken);
            if (legacy is not null)
            {
                await TryImportLegacyDatabaseSnapshotAsync(legacy, cancellationToken);
                return legacy;
            }

            var fresh = new AppSettings { LegacyImportCompleted = true };
            await TryImportLegacyDatabaseSnapshotAsync(fresh, cancellationToken);
            await SaveAsync(fresh, cancellationToken);
            return fresh;
        }

        AppSettings settings;
        await using (var stream = File.OpenRead(_settingsPath))
        {
            settings = await JsonSerializer.DeserializeAsync<AppSettings>(stream, JsonOptions, cancellationToken)
                ?? new AppSettings();
        }

        if (!settings.LegacyImportCompleted)
        {
            settings.LegacyImportCompleted = true;
        }

        settings.HomePageMode = NormalizeHomePageMode(settings.HomePageMode);
        settings.Language = LanguagePreference.Normalize(settings.Language);
        settings.ToolDisplayMode = NormalizeToolDisplayMode(settings.ToolDisplayMode);
        settings.ToolboxDefaultScope = NormalizeToolboxScope(settings.ToolboxDefaultScope);
        settings.WindowBackdrop = NormalizeWindowBackdrop(settings.WindowBackdrop);
        settings.SettingsPanelMaterial = NormalizeSettingsPanelMaterial(settings.SettingsPanelMaterial);
        settings.TopBarMaterial = NormalizeTopBarMaterial(settings.TopBarMaterial);
        settings.FeedbackDefaultType = NormalizeFeedbackType(settings.FeedbackDefaultType);
        settings.FeedbackDefaultSeverity = NormalizeFeedbackSeverity(settings.FeedbackDefaultSeverity);

        if (!settings.LegacyDatabaseImportCompleted)
        {
            await TryImportLegacyDatabaseSnapshotAsync(settings, cancellationToken);
        }

        await SaveAsync(settings, cancellationToken);

        return settings;
    }

    public async Task SaveAsync(AppSettings settings, CancellationToken cancellationToken = default)
    {
        settings.Language = LanguagePreference.Normalize(settings.Language);
        Directory.CreateDirectory(Path.GetDirectoryName(_settingsPath)!);
        await using var stream = File.Create(_settingsPath);
        await JsonSerializer.SerializeAsync(stream, settings, JsonOptions, cancellationToken);
    }

    public async Task RecordRecentToolAsync(string toolId, CancellationToken cancellationToken = default)
    {
        var settings = await LoadAsync(cancellationToken);
        settings.RecentToolIds.RemoveAll(id => string.Equals(id, toolId, StringComparison.OrdinalIgnoreCase));
        settings.RecentToolIds.Insert(0, toolId);
        if (settings.RecentToolIds.Count > 12)
        {
            settings.RecentToolIds.RemoveRange(12, settings.RecentToolIds.Count - 12);
        }

        await SaveAsync(settings, cancellationToken);
    }

    private async Task<AppSettings?> TryImportLegacySettingsAsync(CancellationToken cancellationToken)
    {
        var settingsRoot = Path.GetDirectoryName(_settingsPath);
        if (string.IsNullOrWhiteSpace(settingsRoot))
        {
            return null;
        }

        foreach (var legacyPath in ResolveLegacySettingsCandidates(settingsRoot))
        {
            if (!File.Exists(legacyPath))
            {
                continue;
            }

            try
            {
                await using var stream = File.OpenRead(legacyPath);
                using var document = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken);
                var legacy = ParseLegacySettings(document.RootElement);
                if (legacy is null)
                {
                    continue;
                }

                legacy.LegacyImportCompleted = true;
                await SaveAsync(legacy, cancellationToken);
                return legacy;
            }
            catch (JsonException)
            {
                continue;
            }
        }

        return null;
    }

    private static IEnumerable<string> ResolveLegacySettingsCandidates(string settingsRoot)
    {
        var legacyBase = Path.GetFullPath(Path.Combine(settingsRoot, ".."));
        foreach (var basePath in new[]
        {
            legacyBase,
            Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)
        }.Where(path => !string.IsNullOrWhiteSpace(path)).Distinct(StringComparer.OrdinalIgnoreCase))
        {
            foreach (var appFolder in new[] { "YMhut Box", "ymhut_box" })
            {
                yield return Path.Combine(basePath, appFolder, "settings.json");
                yield return Path.Combine(basePath, appFolder, "shared_preferences.json");
            }
        }
    }

    private static AppSettings? ParseLegacySettings(JsonElement root)
    {
        if (root.ValueKind != JsonValueKind.Object)
        {
            return null;
        }

        var found = false;
        var settings = new AppSettings();

        if (TryGetString(root, out var theme, "theme"))
        {
            settings.Theme = NormalizeTheme(theme);
            found = true;
        }

        AssignString(root, value => settings.Language = LanguagePreference.Normalize(value), ref found, "language", "locale", "ui_language", "uiLanguage", "app_language", "appLanguage");
        AssignBool(root, value => settings.PitchBlack = value, ref found, "pitch_black", "pitchBlack");
        AssignInt(root, value => settings.SeedColor = value, ref found, "seed_color", "seedColor");
        AssignDouble(root, value => settings.CardOpacity = value, ref found, "card_opacity", "cardOpacity");
        AssignString(root, value => settings.WindowBackdrop = NormalizeWindowBackdrop(value), ref found, "window_backdrop", "windowBackdrop");
        AssignString(root, value => settings.SettingsPanelMaterial = NormalizeSettingsPanelMaterial(value), ref found, "settings_panel_material", "settingsPanelMaterial");
        AssignString(root, value => settings.TopBarMaterial = NormalizeTopBarMaterial(value), ref found, "top_bar_material", "topBarMaterial", "title_bar_material", "titleBarMaterial");
        AssignString(root, value => settings.BackgroundImage = value, ref found, "background_image", "backgroundImage");
        AssignDouble(root, value => settings.BackgroundOpacity = value, ref found, "background_opacity", "backgroundOpacity");
        AssignBool(root, value => settings.UserAgreementAccepted = value, ref found, "user_agreement_accepted", "userAgreementAccepted");
        AssignString(root, value => settings.UserAgreementVersion = value, ref found, "user_agreement_version", "userAgreementVersion");
        AssignDateTimeOffset(root, value => settings.UserAgreementAcceptedAt = value, ref found, "user_agreement_accepted_at", "userAgreementAcceptedAt");
        AssignBool(root, value => settings.SidebarCollapsed = value, ref found, "sidebar_collapsed", "sidebarCollapsed");
        AssignBool(root, value => settings.DesktopTitlebar = value, ref found, "desktop_titlebar", "desktopTitlebar");
        AssignStringList(root, value => settings.RecentToolIds = value, ref found, "recent_tool_ids", "recentToolIds");
        AssignStringList(root, value => settings.PinnedToolIds = value, ref found, "pinned_tool_ids", "pinnedToolIds");
        AssignNullableDouble(root, value => settings.WindowX = value, ref found, "window_x", "windowX");
        AssignNullableDouble(root, value => settings.WindowY = value, ref found, "window_y", "windowY");
        AssignNullableDouble(root, value => settings.WindowWidth = value, ref found, "window_width", "windowWidth");
        AssignNullableDouble(root, value => settings.WindowHeight = value, ref found, "window_height", "windowHeight");
        AssignBool(root, value => settings.WindowMaximized = value, ref found, "window_maximized", "windowMaximized");
        AssignBool(root, value => settings.ProxyEnabled = value, ref found, "proxy_enabled", "proxyEnabled");
        AssignString(root, value => settings.ProxyMode = value, ref found, "proxy_mode", "proxyMode");
        AssignString(root, value => settings.ProxyHost = value, ref found, "proxy_host", "proxyHost");
        AssignInt(root, value => settings.ProxyPort = value, ref found, "proxy_port", "proxyPort");
        AssignBool(root, value => settings.AnimationsEnabled = value, ref found, "animations_enabled", "animationsEnabled");
        AssignDouble(root, value => settings.FontSize = value, ref found, "font_size", "fontSize");
        AssignString(root, value => settings.ToolDisplayMode = NormalizeToolDisplayMode(value), ref found, "tool_display_mode", "toolDisplayMode");
        AssignString(root, value => settings.ToolboxDefaultScope = NormalizeToolboxScope(value), ref found, "toolbox_default_scope", "toolboxDefaultScope");
        AssignBool(root, value => settings.ToolboxCompactCards = value, ref found, "toolbox_compact_cards", "toolboxCompactCards");
        AssignBool(root, value => settings.ToolboxShowRecentFirst = value, ref found, "toolbox_show_recent_first", "toolboxShowRecentFirst");
        AssignBool(root, value => settings.ShowHardwareBrandLogo = value, ref found, "show_hardware_brand_logo", "showHardwareBrandLogo", "brand_logo", "brandLogo");
        AssignBool(root, value => settings.RequireRiskConfirmation = value, ref found, "require_risk_confirmation", "requireRiskConfirmation");
        AssignString(root, value => settings.HomePageMode = NormalizeHomePageMode(value), ref found, "home_page_mode", "homePageMode");
        AssignString(root, value => settings.CloseBehavior = value, ref found, "close_behavior", "closeBehavior");
        AssignBool(root, value => settings.RestoreWindowPosition = value, ref found, "restore_window_position", "restoreWindowPosition");
        AssignBool(root, value => settings.AutoStart = value, ref found, "auto_start", "autoStart");
        AssignInt(root, value => settings.LogRetentionCount = value, ref found, "log_retention_count", "logRetentionCount");
        AssignInt(root, value => settings.DataRefreshInterval = value, ref found, "data_refresh_interval", "dataRefreshInterval");
        AssignBool(root, value => settings.UpdateNotification = value, ref found, "update_notification", "updateNotification");
        AssignBool(root, value => settings.HardwareAccelerationEnabled = value, ref found, "hardware_acceleration_enabled", "hardwareAccelerationEnabled");
        AssignInt(root, value => settings.ProxyTestTimeoutSeconds = value, ref found, "proxy_test_timeout_seconds", "proxyTestTimeoutSeconds");
        AssignBool(root, value => settings.PluginsEnabled = value, ref found, "plugins_enabled", "pluginsEnabled");
        AssignString(root, value => settings.PluginRootPath = value, ref found, "plugin_root_path", "pluginRootPath");
        AssignString(root, value => settings.FeedbackDefaultContact = value, ref found, "feedback_default_contact", "feedbackDefaultContact");
        AssignString(root, value => settings.FeedbackDefaultType = NormalizeFeedbackType(value), ref found, "feedback_default_type", "feedbackDefaultType");
        AssignString(root, value => settings.FeedbackDefaultSeverity = NormalizeFeedbackSeverity(value), ref found, "feedback_default_severity", "feedbackDefaultSeverity");
        AssignBool(root, value => settings.FeedbackIncludeTodayLogsByDefault = value, ref found, "feedback_include_today_logs_by_default", "feedbackIncludeTodayLogsByDefault");
        AssignBool(root, value => settings.FeedbackIncludeToolStatusByDefault = value, ref found, "feedback_include_tool_status_by_default", "feedbackIncludeToolStatusByDefault");
        AssignBool(root, value => settings.FeedbackIncludeSystemSummaryByDefault = value, ref found, "feedback_include_system_summary_by_default", "feedbackIncludeSystemSummaryByDefault");
        AssignBool(root, value => settings.FeedbackRememberDefaults = value, ref found, "feedback_remember_defaults", "feedbackRememberDefaults");

        if (!TryGetString(root, out _, "close_behavior", "closeBehavior") &&
            TryGetBool(root, out var minimizeToTray, "minimize_to_tray", "minimizeToTray"))
        {
            settings.CloseBehavior = minimizeToTray ? "minimize_then_exit" : "exit_directly";
            found = true;
        }

        settings.LegacyImportCompleted = true;
        return found ? settings : null;
    }

    private static string NormalizeTheme(string theme)
    {
        return theme.Trim().ToLowerInvariant() switch
        {
            "dark" => "Dark",
            "system" => "System",
            _ => "Light"
        };
    }

    private static string NormalizeHomePageMode(string mode)
    {
        return (mode ?? string.Empty).Trim().ToLowerInvariant() switch
        {
            "solar" or "planets" or "planet" => "solar",
            "dashboard" or "overview" or "tools" => "dashboard",
            _ => "dashboard"
        };
    }

    private static string NormalizeToolDisplayMode(string mode)
    {
        return (mode ?? string.Empty).Trim().ToLowerInvariant() switch
        {
            "list" or "compact" => "list",
            _ => "grid"
        };
    }

    private static string NormalizeToolboxScope(string scope)
    {
        return (scope ?? string.Empty).Trim().ToLowerInvariant() switch
        {
            "favorite" or "favorites" or "pinned" => "favorites",
            "recent" or "history" => "recent",
            "external" or "tools" => "external",
            "builtin" or "built-in" or "reference" => "builtin",
            "plugin" or "plugins" => "plugin",
            "risk" or "risky" => "risk",
            _ => "all"
        };
    }

    private static string NormalizeWindowBackdrop(string value)
    {
        return (value ?? string.Empty).Trim().ToLowerInvariant() switch
        {
            "none" or "solid" => "solid",
            "micaalt" or "mica_alt" or "mica-alt" => "micaAlt",
            "acrylic" or "desktopacrylic" or "desktop_acrylic" => "acrylic",
            _ => "mica"
        };
    }

    private static string NormalizeSettingsPanelMaterial(string value)
    {
        return (value ?? string.Empty).Trim().ToLowerInvariant() switch
        {
            "acrylic" => "acrylic",
            "glass" or "frosted" or "frosted_glass" => "glass",
            _ => "solid"
        };
    }

    private static string NormalizeTopBarMaterial(string value)
    {
        return (value ?? string.Empty).Trim().ToLowerInvariant() switch
        {
            "solid" or "none" => "solid",
            "mica" => "mica",
            "acrylic" => "acrylic",
            "glass" or "frosted" or "frosted_glass" => "glass",
            _ => "inherit"
        };
    }

    private static string NormalizeFeedbackType(string value)
    {
        return (value ?? string.Empty).Trim().ToLowerInvariant() switch
        {
            "suggestion" => "suggestion",
            "ui" => "ui",
            "other" => "other",
            _ => "issue"
        };
    }

    private static string NormalizeFeedbackSeverity(string value)
    {
        return (value ?? string.Empty).Trim().ToLowerInvariant() switch
        {
            "major" => "major",
            "blocking" => "blocking",
            _ => "normal"
        };
    }

    private static void AssignString(JsonElement root, Action<string> assign, ref bool found, params string[] aliases)
    {
        if (TryGetString(root, out var value, aliases))
        {
            assign(value);
            found = true;
        }
    }

    private static void AssignBool(JsonElement root, Action<bool> assign, ref bool found, params string[] aliases)
    {
        if (TryGetBool(root, out var value, aliases))
        {
            assign(value);
            found = true;
        }
    }

    private static void AssignInt(JsonElement root, Action<int> assign, ref bool found, params string[] aliases)
    {
        if (TryGetInt(root, out var value, aliases))
        {
            assign(value);
            found = true;
        }
    }

    private static void AssignDouble(JsonElement root, Action<double> assign, ref bool found, params string[] aliases)
    {
        if (TryGetDouble(root, out var value, aliases))
        {
            assign(value);
            found = true;
        }
    }

    private static void AssignNullableDouble(JsonElement root, Action<double?> assign, ref bool found, params string[] aliases)
    {
        if (TryGetDouble(root, out var value, aliases))
        {
            assign(value);
            found = true;
        }
    }

    private static void AssignStringList(JsonElement root, Action<List<string>> assign, ref bool found, params string[] aliases)
    {
        if (TryGetStringList(root, out var value, aliases))
        {
            assign(value);
            found = true;
        }
    }

    private static void AssignDateTimeOffset(JsonElement root, Action<DateTimeOffset?> assign, ref bool found, params string[] aliases)
    {
        if (TryGetString(root, out var value, aliases) &&
            DateTimeOffset.TryParse(value, out var parsed))
        {
            assign(parsed);
            found = true;
        }
    }

    private static bool TryGetString(JsonElement root, out string value, params string[] aliases)
    {
        if (TryGet(root, out var element, aliases))
        {
            if (element.ValueKind == JsonValueKind.String)
            {
                value = element.GetString() ?? string.Empty;
                return true;
            }

            if (element.ValueKind is JsonValueKind.Number or JsonValueKind.True or JsonValueKind.False)
            {
                value = element.ToString();
                return true;
            }
        }

        value = string.Empty;
        return false;
    }

    private static bool TryGetBool(JsonElement root, out bool value, params string[] aliases)
    {
        if (TryGet(root, out var element, aliases))
        {
            if (element.ValueKind is JsonValueKind.True or JsonValueKind.False)
            {
                value = element.GetBoolean();
                return true;
            }

            if (element.ValueKind == JsonValueKind.String && bool.TryParse(element.GetString(), out var parsed))
            {
                value = parsed;
                return true;
            }
        }

        value = false;
        return false;
    }

    private static bool TryGetInt(JsonElement root, out int value, params string[] aliases)
    {
        if (TryGet(root, out var element, aliases))
        {
            if (element.ValueKind == JsonValueKind.Number && element.TryGetInt32(out value))
            {
                return true;
            }

            if (element.ValueKind == JsonValueKind.String && int.TryParse(element.GetString(), out value))
            {
                return true;
            }
        }

        value = 0;
        return false;
    }

    private static bool TryGetDouble(JsonElement root, out double value, params string[] aliases)
    {
        if (TryGet(root, out var element, aliases))
        {
            if (element.ValueKind == JsonValueKind.Number && element.TryGetDouble(out value))
            {
                return true;
            }

            if (element.ValueKind == JsonValueKind.String && double.TryParse(element.GetString(), out value))
            {
                return true;
            }
        }

        value = 0;
        return false;
    }

    private static bool TryGetStringList(JsonElement root, out List<string> value, params string[] aliases)
    {
        if (TryGet(root, out var element, aliases))
        {
            if (element.ValueKind == JsonValueKind.Array)
            {
                value = element.EnumerateArray()
                    .Where(item => item.ValueKind == JsonValueKind.String)
                    .Select(item => item.GetString() ?? string.Empty)
                    .Where(item => !string.IsNullOrWhiteSpace(item))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToList();
                return true;
            }

            if (element.ValueKind == JsonValueKind.String)
            {
                value = element.GetString()?
                    .Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToList() ?? [];
                return true;
            }
        }

        value = [];
        return false;
    }

    private static bool TryGet(JsonElement root, out JsonElement value, params string[] aliases)
    {
        foreach (var alias in aliases)
        {
            foreach (var key in new[] { alias, LegacyPreferencePrefix + alias })
            {
                if (root.TryGetProperty(key, out value))
                {
                    return true;
                }
            }
        }

        value = default;
        return false;
    }

    private async Task TryImportLegacyDatabaseSnapshotAsync(AppSettings settings, CancellationToken cancellationToken)
    {
        if (settings.LegacyDatabaseImportCompleted)
        {
            return;
        }

        var settingsRoot = Path.GetDirectoryName(_settingsPath);
        if (string.IsNullOrWhiteSpace(settingsRoot))
        {
            return;
        }

        var snapshotPath = Path.Combine(settingsRoot, "legacy_app_state.db");
        foreach (var legacyPath in ResolveLegacyDatabaseCandidates(settingsRoot))
        {
            if (!File.Exists(legacyPath))
            {
                continue;
            }

            Directory.CreateDirectory(settingsRoot);
            await using var source = File.Open(legacyPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            await using var target = File.Create(snapshotPath);
            await source.CopyToAsync(target, cancellationToken);
            settings.LegacyDatabaseSnapshotPath = snapshotPath;
            settings.LegacyDatabaseImportCompleted = true;
            return;
        }

        settings.LegacyDatabaseImportCompleted = true;
    }

    private static IEnumerable<string> ResolveLegacyDatabaseCandidates(string settingsRoot)
    {
        var legacyBase = Path.GetFullPath(Path.Combine(settingsRoot, ".."));
        yield return Path.Combine(legacyBase, "YMhut Box", "app_state.db");
        yield return Path.Combine(legacyBase, "ymhut_box", "app_state.db");
        yield return Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
            "ymhut_box",
            "app_state.db");
        yield return Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "YMhut Box",
            "app_state.db");
    }
}
