/*
 * Performance Monitor Dashboard
 * Copyright (c) 2026 Darling Data, LLC
 * Licensed under the MIT License - see LICENSE file for details
 */

using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using PerformanceMonitor.Notifications;
using PerformanceMonitorDashboard.Helpers;
using PerformanceMonitorDashboard.Interfaces;
using PerformanceMonitorDashboard.Models;
using PerformanceMonitorDashboard.Services;
using PerformanceMonitor.Ui;
using PerformanceMonitor.Common;

namespace PerformanceMonitorDashboard
{
    public partial class SettingsWindow : Window
    {
        private readonly IUserPreferencesService _preferencesService;
        private readonly MuteRuleService? _muteRuleService;
        private bool _isLoading = true;
        private readonly string _originalTheme = ThemeManager.CurrentTheme;
        private bool _saved;

        public SettingsWindow(IUserPreferencesService preferencesService, MuteRuleService? muteRuleService = null)
        {
            InitializeComponent();

            _preferencesService = preferencesService;
            _muteRuleService = muteRuleService;
            LoadSettings();
            _isLoading = false;
        }

        private void LoadSettings()
        {
            var prefs = _preferencesService.GetPreferences();

            // NOC refresh interval
            foreach (ComboBoxItem item in NocRefreshIntervalComboBox.Items)
            {
                if (item.Tag != null && int.Parse(item.Tag.ToString()!, CultureInfo.InvariantCulture) == prefs.NocRefreshIntervalSeconds)
                {
                    NocRefreshIntervalComboBox.SelectedItem = item;
                    break;
                }
            }

            // Default to 30 seconds if no match
            if (NocRefreshIntervalComboBox.SelectedItem == null)
            {
                NocRefreshIntervalComboBox.SelectedIndex = 1; // 30 seconds
            }

            // Auto-refresh settings
            AutoRefreshCheckBox.IsChecked = prefs.AutoRefreshEnabled;
            RefreshIntervalComboBox.IsEnabled = prefs.AutoRefreshEnabled;

            // Select the matching interval
            foreach (ComboBoxItem item in RefreshIntervalComboBox.Items)
            {
                if (item.Tag != null && int.Parse(item.Tag.ToString()!, CultureInfo.InvariantCulture) == prefs.AutoRefreshIntervalSeconds)
                {
                    RefreshIntervalComboBox.SelectedItem = item;
                    break;
                }
            }

            // Default to 1 minute if no match
            if (RefreshIntervalComboBox.SelectedItem == null)
            {
                RefreshIntervalComboBox.SelectedIndex = 2; // 1 minute
            }

            // Default time range
            foreach (ComboBoxItem item in DefaultTimeRangeComboBox.Items)
            {
                if (item.Tag != null && int.Parse(item.Tag.ToString()!, CultureInfo.InvariantCulture) == prefs.DefaultHoursBack)
                {
                    DefaultTimeRangeComboBox.SelectedItem = item;
                    break;
                }
            }

            // Default to 24 hours if no match
            if (DefaultTimeRangeComboBox.SelectedItem == null)
            {
                DefaultTimeRangeComboBox.SelectedIndex = 4; // 24 hours
            }

            // CSV separator
            foreach (ComboBoxItem item in CsvSeparatorComboBox.Items)
            {
                if (item.Tag != null && item.Tag.ToString() == prefs.CsvSeparator)
                {
                    CsvSeparatorComboBox.SelectedItem = item;
                    break;
                }
            }

            if (CsvSeparatorComboBox.SelectedItem == null)
            {
                CsvSeparatorComboBox.SelectedIndex = 0; // Comma
            }

            // Navigation settings
            FocusServerTabCheckBox.IsChecked = prefs.FocusServerTabOnClick;

            // Time display mode
            foreach (ComboBoxItem item in TimeDisplayModeComboBox.Items)
            {
                if (item.Tag?.ToString() == prefs.TimeDisplayMode)
                {
                    TimeDisplayModeComboBox.SelectedItem = item;
                    break;
                }
            }
            if (TimeDisplayModeComboBox.SelectedItem == null)
                TimeDisplayModeComboBox.SelectedIndex = 0;

            // Color theme
            foreach (System.Windows.Controls.ComboBoxItem item in ColorThemeComboBox.Items)
            {
                if (item.Tag?.ToString() == prefs.ColorTheme)
                {
                    ColorThemeComboBox.SelectedItem = item;
                    break;
                }
            }
            if (ColorThemeComboBox.SelectedItem == null)
                ColorThemeComboBox.SelectedIndex = 0;

            // Query logging settings
            LogSlowQueriesCheckBox.IsChecked = prefs.LogSlowQueries;
            QueryLogger.SetEnabled(prefs.LogSlowQueries);
            QueryLogger.SetThreshold(prefs.SlowQueryThresholdSeconds);

            // Method profiler settings
            LogSlowMethodsCheckBox.IsChecked = prefs.LogSlowMethods;
            MethodProfiler.SetEnabled(prefs.LogSlowMethods);

            // MCP server settings
            McpEnabledCheckBox.IsChecked = prefs.McpEnabled;
            McpPortTextBox.Text = prefs.McpPort.ToString(CultureInfo.InvariantCulture);
            McpPortTextBox.IsEnabled = prefs.McpEnabled;
            UpdateMcpStatus(prefs);

            // Automated analysis. Production (AnalysisEnabled, default on) is decoupled
            // from notification delivery (AnalysisNotificationsEnabled). Cooldown and
            // timeout are intentionally not exposed in the UI — preferences.json only.
            AnalysisEnabledCheckBox.IsChecked = prefs.AnalysisEnabled;
            AnalysisNotificationsEnabledCheckBox.IsChecked = prefs.AnalysisNotificationsEnabled;
            AnalysisIntervalMinutesTextBox.Text = prefs.AnalysisIntervalMinutes.ToString(CultureInfo.InvariantCulture);
            AnalysisNotifySeverityTextBox.Text = prefs.AnalysisNotifySeverity.ToString("F1", CultureInfo.InvariantCulture);

            // System tray settings
            MinimizeToTrayCheckBox.IsChecked = prefs.MinimizeToTray;
            NotificationsEnabledCheckBox.IsChecked = prefs.NotificationsEnabled;
            NotifyConnectionLostCheckBox.IsChecked = prefs.NotifyOnConnectionLost;
            NotifyConnectionRestoredCheckBox.IsChecked = prefs.NotifyOnConnectionRestored;

            // Alert notification settings
            NotifyOnBlockingCheckBox.IsChecked = prefs.NotifyOnBlocking;
            BlockingThresholdTextBox.Text = prefs.BlockingThresholdSeconds.ToString(CultureInfo.InvariantCulture);
            NotifyOnDeadlockCheckBox.IsChecked = prefs.NotifyOnDeadlock;
            DeadlockThresholdTextBox.Text = prefs.DeadlockThreshold.ToString(CultureInfo.InvariantCulture);
            NotifyOnHighCpuCheckBox.IsChecked = prefs.NotifyOnHighCpu;
            CpuThresholdTextBox.Text = prefs.CpuThresholdPercent.ToString(CultureInfo.InvariantCulture);
            CpuAlertModeBox.SelectedIndex = prefs.CpuAlertMode == CpuAlertMode.SqlOnly ? 1 : 0;
            NotifyOnPoisonWaitsCheckBox.IsChecked = prefs.NotifyOnPoisonWaits;
            PoisonWaitThresholdTextBox.Text = prefs.PoisonWaitThresholdMs.ToString(CultureInfo.InvariantCulture);
            NotifyOnLongRunningQueriesCheckBox.IsChecked = prefs.NotifyOnLongRunningQueries;
            LongRunningQueryThresholdTextBox.Text = prefs.LongRunningQueryThresholdMinutes.ToString(CultureInfo.InvariantCulture);
            LongRunningQueryMaxResultsTextBox.Text = prefs.LongRunningQueryMaxResults.ToString(CultureInfo.InvariantCulture);
            LrqExcludeSpServerDiagnosticsCheckBox.IsChecked = prefs.LongRunningQueryExcludeSpServerDiagnostics;
            LrqExcludeWaitForCheckBox.IsChecked = prefs.LongRunningQueryExcludeWaitFor;
            LrqExcludeBackupsCheckBox.IsChecked = prefs.LongRunningQueryExcludeBackups;
            LrqExcludeMiscWaitsCheckBox.IsChecked = prefs.LongRunningQueryExcludeMiscWaits;
            LrqExcludeCdcCheckBox.IsChecked = prefs.LongRunningQueryExcludeCdc;
            AlertExcludedDatabasesTextBox.Text = string.Join(", ", prefs.AlertExcludedDatabases);
            NotifyOnTempDbSpaceCheckBox.IsChecked = prefs.NotifyOnTempDbSpace;
            TempDbSpaceThresholdTextBox.Text = prefs.TempDbSpaceThresholdPercent.ToString(CultureInfo.InvariantCulture);
            NotifyOnLowDiskCheckBox.IsChecked = prefs.NotifyOnLowDisk;
            LowDiskThresholdPercentTextBox.Text = prefs.LowDiskThresholdPercent.ToString(CultureInfo.InvariantCulture);
            LowDiskThresholdGbTextBox.Text = prefs.LowDiskThresholdGb.ToString(CultureInfo.InvariantCulture);
            NotifyOnLongRunningJobsCheckBox.IsChecked = prefs.NotifyOnLongRunningJobs;
            LongRunningJobMultiplierTextBox.Text = prefs.LongRunningJobMultiplier.ToString(CultureInfo.InvariantCulture);
            NotifyOnFailedJobsCheckBox.IsChecked = prefs.NotifyOnFailedJobs;
            FailedJobLookbackTextBox.Text = prefs.FailedJobLookbackMinutes.ToString(CultureInfo.InvariantCulture);
            NotifyOnCollectionStoppedCheckBox.IsChecked = prefs.NotifyOnCollectionStopped;
            AlertCooldownTextBox.Text = prefs.AlertCooldownMinutes.ToString(CultureInfo.InvariantCulture);
            EmailCooldownTextBox.Text = prefs.EmailCooldownMinutes.ToString(CultureInfo.InvariantCulture);
            AlertDeliveryModeCombo.SelectedIndex = prefs.AlertDeliveryMode == AlertNotificationMode.PerEvent ? 1 : 0;
            AlertPerEventMaxTextBox.Text = prefs.AlertPerEventMaxPerCycle.ToString(CultureInfo.InvariantCulture);
            MuteRuleDefaultExpirationCombo.SelectedIndex = prefs.MuteRuleDefaultExpiration switch
            {
                "1 hour" => 0,
                "24 hours" => 1,
                "7 days" => 2,
                _ => 3
            };
            LogAlertDismissalsCheckBox.IsChecked = prefs.LogAlertDismissals;

            UpdateNotificationCheckboxStates();

            // SMTP email settings
            SmtpEnabledCheckBox.IsChecked = prefs.SmtpEnabled;
            SmtpServerTextBox.Text = prefs.SmtpServer;
            SmtpPortTextBox.Text = prefs.SmtpPort.ToString(CultureInfo.InvariantCulture);
            SmtpSslCheckBox.IsChecked = prefs.SmtpUseSsl;
            SmtpUsernameTextBox.Text = prefs.SmtpUsername;
            SmtpFromTextBox.Text = prefs.SmtpFromAddress;
            SmtpRecipientsTextBox.Text = prefs.SmtpRecipients;

            var password = DashboardAlertCredentials.GetSmtpPassword();
            if (!string.IsNullOrEmpty(password))
            {
                SmtpPasswordBox.Password = password;
            }

            UpdateSmtpControlStates();

            // Webhook settings (Teams / Slack)
            TeamsWebhookEnabledCheckBox.IsChecked = prefs.TeamsWebhookEnabled;
            TeamsProxyAddressTextBox.Text = prefs.TeamsProxyAddress;
            SlackWebhookEnabledCheckBox.IsChecked = prefs.SlackWebhookEnabled;
            SlackProxyAddressTextBox.Text = prefs.SlackProxyAddress;

            /* Migrate legacy plaintext webhook URLs to Credential Manager */
            if (!string.IsNullOrWhiteSpace(prefs.TeamsWebhookUrl))
            {
                DashboardAlertCredentials.SaveTeamsWebhookUrl(prefs.TeamsWebhookUrl);
                prefs.TeamsWebhookUrl = "";
                _preferencesService.SavePreferences(prefs);
            }
            if (!string.IsNullOrWhiteSpace(prefs.SlackWebhookUrl))
            {
                DashboardAlertCredentials.SaveSlackWebhookUrl(prefs.SlackWebhookUrl);
                prefs.SlackWebhookUrl = "";
                _preferencesService.SavePreferences(prefs);
            }

            /* Load webhook URLs from Credential Manager */
            TeamsWebhookUrlTextBox.Text = DashboardAlertCredentials.GetTeamsWebhookUrl();
            SlackWebhookUrlTextBox.Text = DashboardAlertCredentials.GetSlackWebhookUrl();
            UpdateTeamsControlStates();
            UpdateSlackControlStates();

        }

        private void NocRefreshIntervalComboBox_Changed(object sender, SelectionChangedEventArgs e)
        {
            // Just UI update, actual save happens on OK
        }

        private void AutoRefreshCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;

            RefreshIntervalComboBox.IsEnabled = AutoRefreshCheckBox.IsChecked == true;
        }

        private void RefreshIntervalComboBox_Changed(object sender, SelectionChangedEventArgs e)
        {
            // Just UI update, actual save happens on OK
        }

        private void DefaultTimeRangeComboBox_Changed(object sender, SelectionChangedEventArgs e)
        {
            // Just UI update, actual save happens on OK
        }

        private void ColorThemeComboBox_Changed(object sender, SelectionChangedEventArgs e)
        {
            if (_isLoading) return;
            if (ColorThemeComboBox.SelectedItem is ComboBoxItem item && item.Tag is string theme)
            {
                ThemeManager.Apply(theme);
            }
        }

        private void LogSlowQueriesCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;

            // Update the QueryLogger immediately for this session
            QueryLogger.SetEnabled(LogSlowQueriesCheckBox.IsChecked == true);
        }

        private void LogSlowMethodsCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;

            // Update the MethodProfiler immediately for this session
            MethodProfiler.SetEnabled(LogSlowMethodsCheckBox.IsChecked == true);
        }

        private void MinimizeToTrayCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            // Just UI update, actual save happens on OK
        }

        private void NotificationsEnabledCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            UpdateNotificationCheckboxStates();
        }

        private void NotifyConnectionLostCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            // Just UI update, actual save happens on OK
        }

        private void NotifyConnectionRestoredCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            // Just UI update, actual save happens on OK
        }

        private void NotifyOnBlockingCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            UpdateAlertNotificationStates();
        }

        private void NotifyOnDeadlockCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            UpdateAlertNotificationStates();
        }

        private void NotifyOnHighCpuCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            UpdateAlertNotificationStates();
        }

        private void NotifyOnPoisonWaitsCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            UpdateAlertNotificationStates();
        }

        private void NotifyOnLongRunningQueriesCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            UpdateAlertNotificationStates();
        }

        private void NotifyOnTempDbSpaceCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            UpdateAlertNotificationStates();
        }

        private void NotifyOnLowDiskCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            UpdateAlertNotificationStates();
        }

        private void NotifyOnLongRunningJobsCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            UpdateAlertNotificationStates();
        }

        private void NotifyOnFailedJobsCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            UpdateAlertNotificationStates();
        }

        private void NotifyOnCollectionStoppedCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            UpdateAlertNotificationStates();
        }

        private void RestoreAlertDefaultsButton_Click(object sender, RoutedEventArgs e)
        {
            BlockingThresholdTextBox.Text = "30";
            DeadlockThresholdTextBox.Text = "1";
            CpuThresholdTextBox.Text = "90";
            PoisonWaitThresholdTextBox.Text = "500";
            LongRunningQueryThresholdTextBox.Text = "30";
            TempDbSpaceThresholdTextBox.Text = "80";
            LowDiskThresholdPercentTextBox.Text = "10";
            LowDiskThresholdGbTextBox.Text = "5";
            LongRunningJobMultiplierTextBox.Text = "3";
            FailedJobLookbackTextBox.Text = "60";
            AlertCooldownTextBox.Text = "5";
            EmailCooldownTextBox.Text = "15";
            AlertDeliveryModeCombo.SelectedIndex = 0;
            AlertPerEventMaxTextBox.Text = "10";
            AlertExcludedDatabasesTextBox.Text = "";
            MuteRuleDefaultExpirationCombo.SelectedIndex = 1; // 24 hours
            UpdateAlertPreviewText();
        }

        private void ManageMuteRulesButton_Click(object sender, RoutedEventArgs e)
        {
            if (_muteRuleService == null) return;
            var window = new ManageMuteRulesWindow(_muteRuleService) { Owner = this };
            window.ShowDialog();
        }

        private void UpdateAlertPreviewText()
        {
            var parts = new System.Collections.Generic.List<string>();

            if (NotifyOnBlockingCheckBox.IsChecked == true)
                parts.Add($"blocking > {BlockingThresholdTextBox.Text}s");
            if (NotifyOnDeadlockCheckBox.IsChecked == true)
                parts.Add($"deadlocks >= {DeadlockThresholdTextBox.Text}");
            if (NotifyOnHighCpuCheckBox.IsChecked == true)
                parts.Add($"CPU > {CpuThresholdTextBox.Text}%");
            if (NotifyOnPoisonWaitsCheckBox.IsChecked == true)
                parts.Add($"poison waits >= {PoisonWaitThresholdTextBox.Text}ms avg");
            if (NotifyOnLongRunningQueriesCheckBox.IsChecked == true)
                parts.Add($"queries > {LongRunningQueryThresholdTextBox.Text}min");
            if (NotifyOnTempDbSpaceCheckBox.IsChecked == true)
                parts.Add($"TempDB > {TempDbSpaceThresholdTextBox.Text}%");
            if (NotifyOnLowDiskCheckBox.IsChecked == true)
                parts.Add($"disk free < {LowDiskThresholdPercentTextBox.Text}% or {LowDiskThresholdGbTextBox.Text}GB");
            if (NotifyOnLongRunningJobsCheckBox.IsChecked == true)
                parts.Add($"jobs > {LongRunningJobMultiplierTextBox.Text}x avg");
            if (NotifyOnFailedJobsCheckBox.IsChecked == true)
                parts.Add($"failed jobs (last {FailedJobLookbackTextBox.Text}m)");
            if (NotifyOnCollectionStoppedCheckBox.IsChecked == true)
                parts.Add("collection stopped");

            AlertPreviewText.Text = parts.Count > 0
                ? $"Will alert when: {string.Join(", ", parts)}"
                : "No alerts enabled";
        }

        private void UpdateAlertNotificationStates()
        {
            bool notificationsEnabled = NotificationsEnabledCheckBox.IsChecked == true;
            NotifyOnBlockingCheckBox.IsEnabled = notificationsEnabled;
            BlockingThresholdTextBox.IsEnabled = notificationsEnabled && NotifyOnBlockingCheckBox.IsChecked == true;
            NotifyOnDeadlockCheckBox.IsEnabled = notificationsEnabled;
            DeadlockThresholdTextBox.IsEnabled = notificationsEnabled && NotifyOnDeadlockCheckBox.IsChecked == true;
            NotifyOnHighCpuCheckBox.IsEnabled = notificationsEnabled;
            CpuThresholdTextBox.IsEnabled = notificationsEnabled && NotifyOnHighCpuCheckBox.IsChecked == true;
            CpuAlertModeBox.IsEnabled = notificationsEnabled && NotifyOnHighCpuCheckBox.IsChecked == true;
            NotifyOnPoisonWaitsCheckBox.IsEnabled = notificationsEnabled;
            PoisonWaitThresholdTextBox.IsEnabled = notificationsEnabled && NotifyOnPoisonWaitsCheckBox.IsChecked == true;
            NotifyOnLongRunningQueriesCheckBox.IsEnabled = notificationsEnabled;
            LongRunningQueryThresholdTextBox.IsEnabled = notificationsEnabled && NotifyOnLongRunningQueriesCheckBox.IsChecked == true;
            NotifyOnTempDbSpaceCheckBox.IsEnabled = notificationsEnabled;
            TempDbSpaceThresholdTextBox.IsEnabled = notificationsEnabled && NotifyOnTempDbSpaceCheckBox.IsChecked == true;
            NotifyOnLowDiskCheckBox.IsEnabled = notificationsEnabled;
            LowDiskThresholdPercentTextBox.IsEnabled = notificationsEnabled && NotifyOnLowDiskCheckBox.IsChecked == true;
            LowDiskThresholdGbTextBox.IsEnabled = notificationsEnabled && NotifyOnLowDiskCheckBox.IsChecked == true;
            NotifyOnLongRunningJobsCheckBox.IsEnabled = notificationsEnabled;
            LongRunningJobMultiplierTextBox.IsEnabled = notificationsEnabled && NotifyOnLongRunningJobsCheckBox.IsChecked == true;
            NotifyOnFailedJobsCheckBox.IsEnabled = notificationsEnabled;
            FailedJobLookbackTextBox.IsEnabled = notificationsEnabled && NotifyOnFailedJobsCheckBox.IsChecked == true;
            NotifyOnCollectionStoppedCheckBox.IsEnabled = notificationsEnabled;
            UpdateAlertPreviewText();
        }

        private void UpdateNotificationCheckboxStates()
        {
            bool notificationsEnabled = NotificationsEnabledCheckBox.IsChecked == true;
            NotifyConnectionLostCheckBox.IsEnabled = notificationsEnabled;
            NotifyConnectionRestoredCheckBox.IsEnabled = notificationsEnabled;
            UpdateAlertNotificationStates();
        }

        private void OpenQueryLogButton_Click(object sender, RoutedEventArgs e)
        {
            var logFile = QueryLogger.GetCurrentLogFile();

            if (File.Exists(logFile))
            {
                // Open the log file in the default text editor
                try
                {
                    Process.Start(new ProcessStartInfo
                    {
                        FileName = logFile,
                        UseShellExecute = true
                    });
                }
                catch
                {
                    MessageBox.Show($"Could not open log file:\n{logFile}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            else
            {
                // No log file yet - offer to open the log directory
                var logDir = QueryLogger.GetLogDirectory();
                var result = MessageBox.Show(
                    $"No slow query log file exists yet for today.\n\nLog files are created when queries exceed the threshold.\n\nWould you like to open the log directory?\n\n{logDir}",
                    "No Log File",
                    MessageBoxButton.YesNo,
                    MessageBoxImage.Information);

                if (result == MessageBoxResult.Yes && Directory.Exists(logDir))
                {
                    try
                    {
                        Process.Start(new ProcessStartInfo
                        {
                            FileName = logDir,
                            UseShellExecute = true
                        });
                    }
                    catch
                    {
                        MessageBox.Show($"Could not open log directory:\n{logDir}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                }
            }
        }

        private void OpenMethodProfileLogButton_Click(object sender, RoutedEventArgs e)
        {
            var logFile = MethodProfiler.GetCurrentLogFile();

            if (File.Exists(logFile))
            {
                // Open the log file in the default text editor
                try
                {
                    Process.Start(new ProcessStartInfo
                    {
                        FileName = logFile,
                        UseShellExecute = true
                    });
                }
                catch
                {
                    MessageBox.Show($"Could not open log file:\n{logFile}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            else
            {
                // No log file yet - offer to open the log directory
                var logDir = MethodProfiler.GetLogDirectory();
                var result = MessageBox.Show(
                    $"No method profile log file exists yet for today.\n\nLog files are created when methods exceed the threshold (500ms).\n\nWould you like to open the log directory?\n\n{logDir}",
                    "No Log File",
                    MessageBoxButton.YesNo,
                    MessageBoxImage.Information);

                if (result == MessageBoxResult.Yes && Directory.Exists(logDir))
                {
                    try
                    {
                        Process.Start(new ProcessStartInfo
                        {
                            FileName = logDir,
                            UseShellExecute = true
                        });
                    }
                    catch
                    {
                        MessageBox.Show($"Could not open log directory:\n{logDir}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                }
            }
        }

        private void McpEnabledCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            McpPortTextBox.IsEnabled = McpEnabledCheckBox.IsChecked == true;
        }

        private async void AutoPortButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                int port = await PortUtilityService.GetFreeTcpPortAsync();
                McpPortTextBox.Text = port.ToString();
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Could not find an available port: {ex.Message}",
                    "Error", MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private void UpdateMcpStatus(Models.UserPreferences prefs)
        {
            if (prefs.McpEnabled)
            {
                McpStatusText.Text = $"Status: Running on http://localhost:{prefs.McpPort}";
                CopyMcpCommandButton.IsEnabled = true;
            }
            else
            {
                McpStatusText.Text = "Status: Disabled";
                CopyMcpCommandButton.IsEnabled = false;
            }
        }

        private void CopyMcpCommandButton_Click(object sender, RoutedEventArgs e)
        {
            var port = McpPortTextBox.Text;
            var command = $"claude mcp add --transport http --scope user sql-monitor http://localhost:{port}/";
            /* Use SetDataObject with copy=false to avoid WPF's problematic Clipboard.Flush() */
            Clipboard.SetDataObject(command, false);
            McpStatusText.Text = "Copied to clipboard!";
        }

        private async void OkButton_Click(object sender, RoutedEventArgs e)
        {
            var prefs = _preferencesService.GetPreferences();

            // Save NOC refresh interval
            if (NocRefreshIntervalComboBox.SelectedItem is ComboBoxItem nocIntervalItem && nocIntervalItem.Tag != null)
            {
                prefs.NocRefreshIntervalSeconds = int.Parse(nocIntervalItem.Tag.ToString()!, CultureInfo.InvariantCulture);
            }

            // Save auto-refresh settings
            prefs.AutoRefreshEnabled = AutoRefreshCheckBox.IsChecked == true;

            if (RefreshIntervalComboBox.SelectedItem is ComboBoxItem intervalItem && intervalItem.Tag != null)
            {
                prefs.AutoRefreshIntervalSeconds = int.Parse(intervalItem.Tag.ToString()!, CultureInfo.InvariantCulture);
            }

            // Save default time range
            if (DefaultTimeRangeComboBox.SelectedItem is ComboBoxItem rangeItem && rangeItem.Tag != null)
            {
                prefs.DefaultHoursBack = int.Parse(rangeItem.Tag.ToString()!, CultureInfo.InvariantCulture);
            }

            // Save CSV separator
            if (CsvSeparatorComboBox.SelectedItem is ComboBoxItem csvItem && csvItem.Tag != null)
            {
                prefs.CsvSeparator = csvItem.Tag.ToString()!;
                TabHelpers.CsvSeparator = prefs.CsvSeparator;
            }

            // Save navigation settings
            prefs.FocusServerTabOnClick = FocusServerTabCheckBox.IsChecked == true;

            // Save time display mode
            if (TimeDisplayModeComboBox.SelectedItem is ComboBoxItem tdmItem && tdmItem.Tag != null)
            {
                prefs.TimeDisplayMode = tdmItem.Tag.ToString()!;
                if (Enum.TryParse<TimeDisplayMode>(prefs.TimeDisplayMode, out var tdm))
                    ServerTimeHelper.CurrentDisplayMode = tdm;
            }

            // Save color theme
            if (ColorThemeComboBox.SelectedItem is ComboBoxItem themeItem && themeItem.Tag != null)
            {
                prefs.ColorTheme = themeItem.Tag.ToString()!;
            }

            // Save query logging settings
            prefs.LogSlowQueries = LogSlowQueriesCheckBox.IsChecked == true;
            QueryLogger.SetEnabled(prefs.LogSlowQueries);

            // Save method profiler settings
            prefs.LogSlowMethods = LogSlowMethodsCheckBox.IsChecked == true;
            MethodProfiler.SetEnabled(prefs.LogSlowMethods);

            // Save system tray settings
            prefs.MinimizeToTray = MinimizeToTrayCheckBox.IsChecked == true;
            prefs.NotificationsEnabled = NotificationsEnabledCheckBox.IsChecked == true;
            prefs.NotifyOnConnectionLost = NotifyConnectionLostCheckBox.IsChecked == true;
            prefs.NotifyOnConnectionRestored = NotifyConnectionRestoredCheckBox.IsChecked == true;

            // Save alert notification settings with validation
            var validationErrors = new System.Collections.Generic.List<string>();

            prefs.NotifyOnBlocking = NotifyOnBlockingCheckBox.IsChecked == true;
            if (int.TryParse(BlockingThresholdTextBox.Text, out int blockingThreshold) && blockingThreshold > 0)
                prefs.BlockingThresholdSeconds = blockingThreshold;
            else if (prefs.NotifyOnBlocking)
                validationErrors.Add("Blocking threshold must be a positive number");

            prefs.NotifyOnDeadlock = NotifyOnDeadlockCheckBox.IsChecked == true;
            if (int.TryParse(DeadlockThresholdTextBox.Text, out int deadlockThreshold) && deadlockThreshold > 0)
                prefs.DeadlockThreshold = deadlockThreshold;
            else if (prefs.NotifyOnDeadlock)
                validationErrors.Add("Deadlock threshold must be a positive number");

            prefs.NotifyOnHighCpu = NotifyOnHighCpuCheckBox.IsChecked == true;
            if (int.TryParse(CpuThresholdTextBox.Text, out int cpuThreshold) && cpuThreshold > 0 && cpuThreshold <= 100)
                prefs.CpuThresholdPercent = cpuThreshold;
            else if (prefs.NotifyOnHighCpu)
                validationErrors.Add("CPU threshold must be between 1 and 100");
            prefs.CpuAlertMode = CpuAlertModeBox.SelectedIndex == 1 ? CpuAlertMode.SqlOnly : CpuAlertMode.Total;

            prefs.NotifyOnPoisonWaits = NotifyOnPoisonWaitsCheckBox.IsChecked == true;
            if (int.TryParse(PoisonWaitThresholdTextBox.Text, out int poisonWaitThreshold) && poisonWaitThreshold > 0)
                prefs.PoisonWaitThresholdMs = poisonWaitThreshold;
            else if (prefs.NotifyOnPoisonWaits)
                validationErrors.Add("Poison wait threshold must be a positive number");

            prefs.NotifyOnLongRunningQueries = NotifyOnLongRunningQueriesCheckBox.IsChecked == true;
            if (int.TryParse(LongRunningQueryThresholdTextBox.Text, out int lrqThreshold) && lrqThreshold > 0)
                prefs.LongRunningQueryThresholdMinutes = lrqThreshold;
            else if (prefs.NotifyOnLongRunningQueries)
                validationErrors.Add("Long-running query threshold must be a positive number");

            if (int.TryParse(LongRunningQueryMaxResultsTextBox.Text, out int lrqMaxResults) && lrqMaxResults >= 1 && lrqMaxResults <= int.MaxValue)
            {
                prefs.LongRunningQueryMaxResults = lrqMaxResults;
            }
            else
            {
                validationErrors.Add($"Long-running query max results must be between 1 and {int.MaxValue}");
            }

            prefs.LongRunningQueryExcludeSpServerDiagnostics = LrqExcludeSpServerDiagnosticsCheckBox.IsChecked == true;
            prefs.LongRunningQueryExcludeWaitFor = LrqExcludeWaitForCheckBox.IsChecked == true;
            prefs.LongRunningQueryExcludeBackups = LrqExcludeBackupsCheckBox.IsChecked == true;
            prefs.LongRunningQueryExcludeMiscWaits = LrqExcludeMiscWaitsCheckBox.IsChecked == true;
            prefs.LongRunningQueryExcludeCdc = LrqExcludeCdcCheckBox.IsChecked == true;
            prefs.AlertExcludedDatabases = AlertExcludedDatabasesTextBox.Text
                .Split(',')
                .Select(s => s.Trim())
                .Where(s => s.Length > 0)
                .ToList();

            prefs.NotifyOnTempDbSpace = NotifyOnTempDbSpaceCheckBox.IsChecked == true;
            if (int.TryParse(TempDbSpaceThresholdTextBox.Text, out int tempDbThreshold) && tempDbThreshold > 0 && tempDbThreshold <= 100)
                prefs.TempDbSpaceThresholdPercent = tempDbThreshold;
            else if (prefs.NotifyOnTempDbSpace)
                validationErrors.Add("TempDB space threshold must be between 1 and 100");

            prefs.NotifyOnLowDisk = NotifyOnLowDiskCheckBox.IsChecked == true;
            if (int.TryParse(LowDiskThresholdPercentTextBox.Text, out int lowDiskPct) && lowDiskPct >= 0 && lowDiskPct <= 100)
                prefs.LowDiskThresholdPercent = lowDiskPct;
            else if (prefs.NotifyOnLowDisk)
                validationErrors.Add("Volume free space percent threshold must be between 0 and 100");
            if (int.TryParse(LowDiskThresholdGbTextBox.Text, out int lowDiskGb) && lowDiskGb >= 0)
                prefs.LowDiskThresholdGb = lowDiskGb;
            else if (prefs.NotifyOnLowDisk)
                validationErrors.Add("Volume free space GB threshold must be 0 or greater");

            prefs.NotifyOnLongRunningJobs = NotifyOnLongRunningJobsCheckBox.IsChecked == true;
            if (int.TryParse(LongRunningJobMultiplierTextBox.Text, out int jobMultiplier) && jobMultiplier > 0)
                prefs.LongRunningJobMultiplier = jobMultiplier;
            else if (prefs.NotifyOnLongRunningJobs)
                validationErrors.Add("Job multiplier must be a positive number");

            prefs.NotifyOnFailedJobs = NotifyOnFailedJobsCheckBox.IsChecked == true;
            if (int.TryParse(FailedJobLookbackTextBox.Text, out int failedJobLookback) && failedJobLookback >= 1 && failedJobLookback <= 1440)
                prefs.FailedJobLookbackMinutes = failedJobLookback;
            else if (prefs.NotifyOnFailedJobs)
                validationErrors.Add("Failed-job lookback must be between 1 and 1440 minutes");

            prefs.NotifyOnCollectionStopped = NotifyOnCollectionStoppedCheckBox.IsChecked == true;

            if (int.TryParse(AlertCooldownTextBox.Text, out int alertCooldown) && alertCooldown >= 1 && alertCooldown <= 120)
                prefs.AlertCooldownMinutes = alertCooldown;
            else
                validationErrors.Add("Tray notification cooldown must be between 1 and 120 minutes");

            if (int.TryParse(EmailCooldownTextBox.Text, out int emailCooldown) && emailCooldown >= 1 && emailCooldown <= 120)
                prefs.EmailCooldownMinutes = emailCooldown;
            else
                validationErrors.Add("Email alert cooldown must be between 1 and 120 minutes");

            prefs.AlertDeliveryMode = AlertDeliveryModeCombo.SelectedIndex == 1 ? AlertNotificationMode.PerEvent : AlertNotificationMode.Summary;
            if (int.TryParse(AlertPerEventMaxTextBox.Text, out int perEventMax) && perEventMax >= 1 && perEventMax <= 100)
                prefs.AlertPerEventMaxPerCycle = perEventMax;
            else
                validationErrors.Add("Per-event max-per-cycle must be between 1 and 100");

            prefs.MuteRuleDefaultExpiration = (MuteRuleDefaultExpirationCombo.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "24 hours";
            MuteRuleDialog.DefaultExpiration = prefs.MuteRuleDefaultExpiration;
            prefs.LogAlertDismissals = LogAlertDismissalsCheckBox.IsChecked == true;

            // Save SMTP email settings
            prefs.SmtpEnabled = SmtpEnabledCheckBox.IsChecked == true;
            prefs.SmtpServer = SmtpServerTextBox.Text?.Trim() ?? "";
            if (int.TryParse(SmtpPortTextBox.Text, out int smtpPort) && smtpPort > 0 && smtpPort <= 65535)
            {
                prefs.SmtpPort = smtpPort;
            }
            else 
                validationErrors.Add("Smtp Port failed validation - must be a valid TCP port number.");
            prefs.SmtpUseSsl = SmtpSslCheckBox.IsChecked == true;
            prefs.SmtpUsername = SmtpUsernameTextBox.Text?.Trim() ?? "";
            prefs.SmtpFromAddress = SmtpFromTextBox.Text?.Trim() ?? "";
            prefs.SmtpRecipients = SmtpRecipientsTextBox.Text?.Trim() ?? "";

            if (!string.IsNullOrEmpty(SmtpPasswordBox.Password))
            {
                DashboardAlertCredentials.SaveSmtpPassword(SmtpPasswordBox.Password, prefs.SmtpUsername);
            }

            // Save webhook settings (Teams / Slack)
            prefs.TeamsWebhookEnabled = TeamsWebhookEnabledCheckBox.IsChecked == true;
            prefs.TeamsWebhookUrl = "";  /* URLs stored in Credential Manager, not preferences */
            prefs.TeamsProxyAddress = TeamsProxyAddressTextBox.Text?.Trim() ?? "";
            prefs.SlackWebhookEnabled = SlackWebhookEnabledCheckBox.IsChecked == true;
            prefs.SlackWebhookUrl = "";  /* URLs stored in Credential Manager, not preferences */
            prefs.SlackProxyAddress = SlackProxyAddressTextBox.Text?.Trim() ?? "";

            /* Save webhook URLs to Credential Manager */
            DashboardAlertCredentials.SaveTeamsWebhookUrl(TeamsWebhookUrlTextBox.Text?.Trim() ?? "");
            DashboardAlertCredentials.SaveSlackWebhookUrl(SlackWebhookUrlTextBox.Text?.Trim() ?? "");

            // Save MCP server settings
            bool mcpWasEnabled = prefs.McpEnabled;
            prefs.McpEnabled = McpEnabledCheckBox.IsChecked == true;
            if (int.TryParse(McpPortTextBox.Text, out int mcpPort) && mcpPort >= 1024 && mcpPort <= IPEndPoint.MaxPort)
            {
                if (prefs.McpEnabled && (mcpPort != prefs.McpPort || !mcpWasEnabled))
                {
                    // CanBindTcpPortAsync attempts an actual bind, which is more reliable
                    // than checking listeners (TOCTOU is still possible but less likely)
                    bool canBind = await PortUtilityService.CanBindTcpPortAsync(mcpPort, IPAddress.Loopback);
                    if (!canBind)
                    {
                        validationErrors.Add($"Port {mcpPort} is already in use. Choose a different port for the MCP server.");
                    }
                }
                prefs.McpPort = mcpPort;
            }
            else
                validationErrors.Add($"MCP port must be between 1024 and {IPEndPoint.MaxPort}.\nPorts 0–1023 are well-known privileged ports reserved by the operating system.");

            // Automated analysis. Production gate (AnalysisEnabled) is independent of the
            // notification delivery gate (AnalysisNotificationsEnabled). Bounds are also
            // enforced at consumption (AnalysisScheduler.Configure,
            // AnalysisNotificationService.NotifyAsync), but validating here catches typos early.
            prefs.AnalysisEnabled = AnalysisEnabledCheckBox.IsChecked == true;
            prefs.AnalysisNotificationsEnabled = AnalysisNotificationsEnabledCheckBox.IsChecked == true;

            if (int.TryParse(AnalysisIntervalMinutesTextBox.Text?.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int analysisInterval)
                && analysisInterval >= 5 && analysisInterval <= 360)
            {
                prefs.AnalysisIntervalMinutes = analysisInterval;
            }
            else
            {
                validationErrors.Add("Analysis interval must be between 5 and 360 minutes.");
            }

            if (double.TryParse(AnalysisNotifySeverityTextBox.Text?.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out double analysisSeverity)
                && analysisSeverity >= 0.0 && analysisSeverity <= 2.0)
            {
                prefs.AnalysisNotifySeverity = analysisSeverity;
            }
            else
            {
                validationErrors.Add("Analysis notify severity must be between 0.0 and 2.0.");
            }

            if (validationErrors.Count > 0)
            {
                MessageBox.Show(
                    "Some settings have invalid values and were not changed:\n\n" +
                    string.Join("\n", validationErrors),
                    "Validation", MessageBoxButton.OK, MessageBoxImage.Warning);
            }
            else
            {
                _preferencesService.SavePreferences(prefs);
                _saved = true;
                DialogResult = true;
                Close();
            }
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            DialogResult = false;
            Close();
        }

        protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
        {
            if (!_saved)
                ThemeManager.Apply(_originalTheme);
            base.OnClosing(e);
        }

        // ============================================
        // Email Alerts Tab
        // ============================================

        private void SmtpEnabledCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            UpdateSmtpControlStates();
        }

        private void UpdateSmtpControlStates()
        {
            bool enabled = SmtpEnabledCheckBox.IsChecked == true;
            SmtpServerTextBox.IsEnabled = enabled;
            SmtpPortTextBox.IsEnabled = enabled;
            SmtpSslCheckBox.IsEnabled = enabled;
            SmtpUsernameTextBox.IsEnabled = enabled;
            SmtpPasswordBox.IsEnabled = enabled;
            SmtpFromTextBox.IsEnabled = enabled;
            SmtpRecipientsTextBox.IsEnabled = enabled;
            TestEmailButton.IsEnabled = enabled;
            ValidateSmtpButton.IsEnabled = enabled;
        }

        private void ValidateSmtpButton_Click(object sender, RoutedEventArgs e)
        {
            var errors = new System.Collections.Generic.List<string>();

            if (string.IsNullOrWhiteSpace(SmtpServerTextBox.Text))
                errors.Add("SMTP server is required");
            if (!int.TryParse(SmtpPortTextBox.Text, out var port) || port < 1 || port > 65535)
                errors.Add("Port must be between 1 and 65535");
            if (string.IsNullOrWhiteSpace(SmtpFromTextBox.Text))
                errors.Add("From address is required");
            else if (!SmtpFromTextBox.Text.Trim().Contains('@'))
                errors.Add("From address must be a valid email");
            if (string.IsNullOrWhiteSpace(SmtpRecipientsTextBox.Text))
                errors.Add("At least one recipient is required");

            if (errors.Count == 0)
            {
                TestEmailStatusText.Text = "Settings look good. Use 'Send Test Email' to verify delivery.";
            }
            else
            {
                TestEmailStatusText.Text = "";
                MessageBox.Show(
                    "SMTP configuration has issues:\n\n" + string.Join("\n", errors),
                    "SMTP Validation", MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        // ============================================
        // Webhooks Tab (Teams / Slack)
        // ============================================

        private void TeamsWebhookEnabledCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            UpdateTeamsControlStates();
        }

        private void SlackWebhookEnabledCheckBox_Changed(object sender, RoutedEventArgs e)
        {
            if (_isLoading) return;
            UpdateSlackControlStates();
        }

        private void UpdateTeamsControlStates()
        {
            bool enabled = TeamsWebhookEnabledCheckBox.IsChecked == true;
            TeamsWebhookUrlTextBox.IsEnabled = enabled;
            TeamsProxyAddressTextBox.IsEnabled = enabled;
            TestTeamsButton.IsEnabled = enabled;
        }

        private void UpdateSlackControlStates()
        {
            bool enabled = SlackWebhookEnabledCheckBox.IsChecked == true;
            SlackWebhookUrlTextBox.IsEnabled = enabled;
            SlackProxyAddressTextBox.IsEnabled = enabled;
            TestSlackButton.IsEnabled = enabled;
        }

        private async void TestTeamsButton_Click(object sender, RoutedEventArgs e)
        {
            TestTeamsButton.IsEnabled = false;
            TestTeamsButton.Content = "Sending...";
            TestTeamsStatusText.Text = "";

            try
            {
                var url = TeamsWebhookUrlTextBox.Text?.Trim() ?? "";
                var proxy = TeamsProxyAddressTextBox.Text?.Trim();
                var error = await WebhookAlertService.SendTestTeamsAsync(url, proxy, EmailAlertService.Branding);

                if (error == null)
                {
                    TestTeamsStatusText.Text = "Test notification sent!";
                    MessageBox.Show("Teams test notification sent successfully!", "Test Webhook", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                else
                {
                    TestTeamsStatusText.Text = $"Failed: {error}";
                    MessageBox.Show($"Failed to send Teams test notification:\n\n{error}", "Test Webhook Failed", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            catch (Exception ex)
            {
                TestTeamsStatusText.Text = $"Error: {ex.Message}";
                MessageBox.Show($"Failed to send Teams test notification:\n\n{ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            finally
            {
                TestTeamsButton.Content = "Send Test Notification";
                TestTeamsButton.IsEnabled = true;
            }
        }

        private async void TestSlackButton_Click(object sender, RoutedEventArgs e)
        {
            TestSlackButton.IsEnabled = false;
            TestSlackButton.Content = "Sending...";
            TestSlackStatusText.Text = "";

            try
            {
                var url = SlackWebhookUrlTextBox.Text?.Trim() ?? "";
                var proxy = SlackProxyAddressTextBox.Text?.Trim();
                var error = await WebhookAlertService.SendTestSlackAsync(url, proxy, EmailAlertService.Branding);

                if (error == null)
                {
                    TestSlackStatusText.Text = "Test notification sent!";
                    MessageBox.Show("Slack test notification sent successfully!", "Test Webhook", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                else
                {
                    TestSlackStatusText.Text = $"Failed: {error}";
                    MessageBox.Show($"Failed to send Slack test notification:\n\n{error}", "Test Webhook Failed", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            catch (Exception ex)
            {
                TestSlackStatusText.Text = $"Error: {ex.Message}";
                MessageBox.Show($"Failed to send Slack test notification:\n\n{ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            finally
            {
                TestSlackButton.Content = "Send Test Notification";
                TestSlackButton.IsEnabled = true;
            }
        }

        private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
        {
            try
            {
                Process.Start(new ProcessStartInfo { FileName = e.Uri.AbsoluteUri, UseShellExecute = true });
            }
            catch { }
            e.Handled = true;
        }

        private async void TestEmailButton_Click(object sender, RoutedEventArgs e)
        {
            // Build a temporary prefs object with current UI values for the test
            var testPrefs = new Models.UserPreferences
            {
                SmtpEnabled = true,
                SmtpServer = SmtpServerTextBox.Text?.Trim() ?? "",
                SmtpPort = int.TryParse(SmtpPortTextBox.Text, out var port) ? port : 587,
                SmtpUseSsl = SmtpSslCheckBox.IsChecked == true,
                SmtpUsername = SmtpUsernameTextBox.Text?.Trim() ?? "",
                SmtpFromAddress = SmtpFromTextBox.Text?.Trim() ?? "",
                SmtpRecipients = SmtpRecipientsTextBox.Text?.Trim() ?? ""
            };

            // Save password to credential store so SendEmailAsync can read it
            if (!string.IsNullOrEmpty(SmtpPasswordBox.Password))
            {
                DashboardAlertCredentials.SaveSmtpPassword(SmtpPasswordBox.Password, testPrefs.SmtpUsername);
            }

            TestEmailButton.IsEnabled = false;
            TestEmailButton.Content = "Sending...";
            TestEmailStatusText.Text = "";

            try
            {
                /* Test before save: send with the values the user just typed (transient,
                   form-values adapter), NOT the saved-prefs DashboardAlertSettings (MOD-1).
                   Converged shared static — no EmailAlertService instance needed (Plan E E3c). */
                var error = await EmailSendCore.SendTestEmailAsync(
                    new UserPreferencesAlertSettings(testPrefs), EmailAlertService.Branding);

                if (error == null)
                {
                    TestEmailStatusText.Text = "Test email sent successfully!";
                    MessageBox.Show("Test email sent successfully!", "Test Email", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                else
                {
                    TestEmailStatusText.Text = $"Failed: {error}";
                    MessageBox.Show($"Failed to send test email:\n\n{error}", "Test Email Failed", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            catch (Exception ex)
            {
                TestEmailStatusText.Text = $"Error: {ex.Message}";
                MessageBox.Show($"Failed to send test email:\n\n{ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            finally
            {
                TestEmailButton.Content = "Send Test Email";
                TestEmailButton.IsEnabled = true;
            }
        }
    }
}
