using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Platform.Storage;
using Avalonia.Threading;

namespace Lumi.Companion.App;

public partial class MainWindow : Window
{
    private readonly CompanionRuntime _runtime;
    private readonly CompanionSettingsStore _settings;
    private bool _allowExit;
    private bool _renderingSources;

    public MainWindow() : this(CreateDefaultServices()) { }

    private MainWindow((CompanionRuntime Runtime, CompanionSettingsStore Settings) services)
        : this(services.Runtime, services.Settings) { }

    public MainWindow(CompanionRuntime runtime, CompanionSettingsStore settings)
    {
        _runtime = runtime;
        _settings = settings;
        InitializeComponent();
        WireActions();
        RenderState(runtime.State);
        RenderTestStages(runtime.TestStages);
        RenderBenchmark(runtime.Benchmark);
        Closing += OnClosing;
        runtime.StateChanged += state => Dispatcher.UIThread.Post(() => RenderState(state));
        runtime.TestStagesChanged += stages => Dispatcher.UIThread.Post(() => RenderTestStages(stages));
        runtime.ObsSourcesChanged += sources => Dispatcher.UIThread.Post(() => RenderSources(sources));
        runtime.BenchmarkChanged += benchmark => Dispatcher.UIThread.Post(() => RenderBenchmark(benchmark));
        runtime.VoiceLevelChanged += level => Dispatcher.UIThread.Post(() => RenderVoiceLevel(level));
        runtime.CaptionReceived += (text, simulated) => Dispatcher.UIThread.Post(() =>
        {
            if (simulated) SimulatedCaption.Text = text;
        });
        runtime.LogAdded += line => Dispatcher.UIThread.Post(() => AddLog(line));
        settings.Changed += value => Dispatcher.UIThread.Post(() => RenderSettings(value));
    }

    private void WireActions()
    {
        foreach (var button in Navigation.Children.OfType<Button>()) button.Click += OnNavigate;
        NextActionButton.Click += OnNextAction;
        PairButton.Click += async (_, _) => await ChoosePairingPackageAsync();
        ReconnectButton.Click += async (_, _) => await RunUiActionAsync(() => _runtime.RetryConnectionAsync(), ReconnectButton);
        OpenWebButton.Click += (_, _) => _runtime.OpenLumiWebUi();
        OpenLogsButton.Click += (_, _) => _runtime.OpenLogsDirectory();
        RunTestButton.Click += async (_, _) => await RunUiActionAsync(() => _runtime.RunTestAsync(), RunTestButton);
        StartBenchmarkButton.Click += async (_, _) => await RunUiActionAsync(() => _runtime.StartBenchmarkAsync(), StartBenchmarkButton);
        StopBenchmarkButton.Click += async (_, _) => await RunUiActionAsync(() => _runtime.StopBenchmarkAsync(), StopBenchmarkButton);
        ForgetButton.Click += async (_, _) => await ForgetDeviceAsync();
        SaveSettingsButton.Click += async (_, _) => await SaveSettingsAsync();
        CheckUpdateButton.Click += async (_, _) => await RunUiActionAsync(() => _runtime.CheckForUpdatesAsync(), CheckUpdateButton);
        ApplyUpdateButton.Click += async (_, _) => await ApplyUpdateAsync();
        InstallBridgeButton.Click += async (_, _) => await InstallBridgeAsync();
        RemoveBridgeButton.Click += async (_, _) => await RemoveBridgeAsync();
        AdvancedToggle.IsCheckedChanged += (_, _) => AdvancedPanel.IsVisible = AdvancedToggle.IsChecked == true;
        SourcePicker.SelectionChanged += async (_, _) =>
        {
            if (!_renderingSources && SourcePicker.SelectedItem is ObsSource source) await _runtime.SelectSourceAsync(source);
        };
    }

    private void OnNavigate(object? sender, RoutedEventArgs args)
    {
        if (sender is Button { Tag: string tag } && Enum.TryParse<CompanionPage>(tag, out var page)) ShowPage(page);
    }

    public void ShowPage(CompanionPage page)
    {
        var pages = new Dictionary<CompanionPage, Control>
        {
            [CompanionPage.Overview] = OverviewPage, [CompanionPage.Transcription] = TranscriptionPage,
            [CompanionPage.Test] = TestPage, [CompanionPage.Connection] = ConnectionPage,
            [CompanionPage.Logs] = LogsPage, [CompanionPage.Settings] = SettingsPage
        };
        foreach (var item in pages) item.Value.IsVisible = item.Key == page;
        foreach (var button in Navigation.Children.OfType<Button>())
        {
            button.Classes.Set("selected", string.Equals(button.Tag as string, page.ToString(), StringComparison.Ordinal));
        }
    }

    private async void OnNextAction(object? sender, RoutedEventArgs args)
    {
        if (!_runtime.State.Paired) { await ChoosePairingPackageAsync(); return; }
        if (!_runtime.State.Connected) { await RunUiActionAsync(() => _runtime.RetryConnectionAsync(), NextActionButton); return; }
        if (!_runtime.State.ObsBridgeInstalled) { ShowPage(CompanionPage.Connection); return; }
        ShowPage(CompanionPage.Test);
    }

    private async Task ChoosePairingPackageAsync()
    {
        var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
        {
            Title = "Choose a Lumi pairing package",
            AllowMultiple = false,
            FileTypeFilter = [new FilePickerFileType("Lumi pairing package") { Patterns = ["*.lumi-pairing.json", "*.json"] }]
        });
        var path = files.FirstOrDefault()?.TryGetLocalPath();
        if (path is null) return;
        await RunUiActionAsync(() => _runtime.PairAsync(path), PairButton);
    }

    private async Task RunUiActionAsync(Func<Task> action, Button button)
    {
        button.IsEnabled = false;
        try { await action(); }
        catch (Exception error) { AddLog($"{DateTime.Now:HH:mm:ss}  {error.Message}"); }
        finally { RenderState(_runtime.State); }
    }

    private async Task InstallBridgeAsync()
    {
        InstallBridgeButton.IsEnabled = false;
        BridgeStatusText.Text = "Checking the packaged integration and requesting Windows approval…";
        try { await _runtime.InstallOrRepairObsBridgeAsync(); }
        catch (Exception error)
        {
            AddLog($"{DateTime.Now:HH:mm:ss}  {error.Message}");
            RenderState(_runtime.State);
            var dialog = new DecisionWindow("OBS integration could not be installed", error.Message, "Close");
            await dialog.ShowDialog<bool>(this);
        }
        finally { RenderState(_runtime.State); }
    }

    private async Task SaveSettingsAsync()
    {
        SaveSettingsButton.IsEnabled = false;
        SettingsFeedback.Text = "Saving…";
        try
        {
            await _runtime.SaveSettingsAsync(_settings.Current with
            {
                AutoStartWithWindows = AutoStartToggle.IsChecked == true,
                StartWithObs = StartWithObsToggle.IsChecked == true,
                AdvancedMode = AdvancedToggle.IsChecked == true
            });
            SettingsFeedback.Text = "Preferences saved.";
        }
        catch (Exception error) { SettingsFeedback.Text = $"Could not save preferences: {error.Message}"; }
        finally { SaveSettingsButton.IsEnabled = true; }
    }

    private async Task ForgetDeviceAsync()
    {
        var dialog = new DecisionWindow("Forget this device?", "This removes the protected credential from this computer. Pairing can be restored with a new package.", "Forget device");
        if (!await dialog.ShowDialog<bool>(this)) return;
        await _runtime.ForgetDeviceAsync();
    }

    private async Task ApplyUpdateAsync()
    {
        if (_runtime.State.RequiresQuitConfirmation)
        {
            var blocked = new DecisionWindow("Update after the stream?", "Companion updates never interrupt streaming or recording. Stop OBS output, then choose Update Companion again.", "Got it");
            await blocked.ShowDialog<bool>(this);
            return;
        }
        var dialog = new DecisionWindow("Update Lumi Companion?", $"Download, verify, and install {_runtime.State.AvailableVersion}. Companion will restart and keep your pairing and settings.", "Update Companion");
        if (!await dialog.ShowDialog<bool>(this)) return;
        await RunUiActionAsync(() => _runtime.ApplyUpdateAsync(), ApplyUpdateButton);
    }

    private async Task RemoveBridgeAsync()
    {
        var dialog = new DecisionWindow("Remove the OBS integration?", "This removes only the Companion-managed OBS plugin. Pairing, preferences, and Lumi Companion remain installed.", "Remove integration");
        if (!await dialog.ShowDialog<bool>(this)) return;
        await RunUiActionAsync(() => _runtime.RemoveObsBridgeAsync(), RemoveBridgeButton);
    }

    private void RenderState(CompanionState state)
    {
        StatusLabel.Text = state.Summary;
        StatusSymbol.Text = state.Health switch { TrayHealth.Ready => "✓ Ready", TrayHealth.Operating => "▶ Active", TrayHealth.Failed => "! Action required", _ => "△ Incomplete" };
        StatusMark.Background = new SolidColorBrush(Color.Parse(state.Health switch { TrayHealth.Ready => "#176B75", TrayHealth.Operating => "#23845B", TrayHealth.Failed => "#BD4D4D", _ => "#A96612" }));
        OverviewDetail.Text = state.Detail;
        PairingStepSymbol.Text = state.Paired ? "✓" : "○";
        PairingStepDetail.Text = state.Connected ? "Securely connected" : state.Paired ? "Paired, currently offline" : "Not paired";
        ObsStepSymbol.Text = state.ObsConnected ? "✓" : state.ObsBridgeInstalled ? "◐" : "○";
        ObsStepDetail.Text = state.ObsConnected ? "Connected to OBS" : state.ObsBridgeInstalled ? "Installed; waiting for OBS" : "Installation required";
        TestStepSymbol.Text = state.PathTestValid ? "✓" : "○";
        TestStepDetail.Text = state.PathTestDetail;
        DeviceNameText.Text = state.DeviceName ?? "Not paired";
        HostText.Text = state.Host ?? "—";
        LastConnectedText.Text = state.LastConnectedAt?.ToString("g") ?? "Never";
        OpenWebButton.IsEnabled = state.Host is not null;
        ReconnectButton.IsEnabled = state.Paired && !state.Connected;
        ForgetButton.IsEnabled = state.Paired;
        RunTestButton.Content = state.TestRunning ? "Testing…" : "Run full path test";
        RunTestButton.IsEnabled = !state.TestRunning && !state.BenchmarkRunning;
        StartBenchmarkButton.IsEnabled = !state.TestRunning && !state.BenchmarkRunning;
        StopBenchmarkButton.IsEnabled = state.BenchmarkRunning;
        BenchmarkStatusText.Text = state.BenchmarkDetail;
        UpdatePanel.IsVisible = state.UpdateAvailable;
        UpdateTitle.Text = state.UpdateAvailable ? $"Lumi Companion {state.AvailableVersion} is available" : "Lumi Companion is current";
        UpdateDetail.Text = state.UpdateDetail;
        ApplyUpdateButton.IsEnabled = state.UpdateAvailable && !state.ObsStreaming && !state.ObsRecording;
        CurrentVersionText.Text = CompanionRuntime.Version;
        UpdateStatusText.Text = state.UpdateDetail;
        BridgeStatusText.Text = state.ObsBridgeDetail;
        InstallBridgeButton.Content = state.ObsBridgeRepairNeeded ? "Repair integration" : state.ObsBridgeInstalled ? "Reinstall integration" : "Install integration";
        // The maintenance action performs a fresh package/process check and reports the
        // actual blocker, so a stale OBS connection signal must never strand repair.
        InstallBridgeButton.IsEnabled = !state.TestRunning && !state.BenchmarkRunning;
        RemoveBridgeButton.IsEnabled = state.ObsBridgeInstalled || state.ObsBridgeRepairNeeded;

        if (!state.Paired)
        {
            NextActionTitle.Text = "Pair this computer";
            NextActionDetail.Text = "Use a one-time pairing package from Lumi to connect this streaming computer.";
            NextActionButton.Content = "Choose pairing package";
        }
        else if (!state.Connected)
        {
            NextActionTitle.Text = "Reconnect to Lumi";
            NextActionDetail.Text = "Your device is paired, but Lumi cannot currently be reached.";
            NextActionButton.Content = "Retry connection";
        }
        else if (!state.ObsBridgeInstalled)
        {
            NextActionTitle.Text = "Install the OBS integration";
            NextActionDetail.Text = state.ObsBridgeDetail;
            NextActionButton.Content = "Manage OBS integration";
        }
        else
        {
            NextActionTitle.Text = state.PathTestValid ? "Ready when you are" : "Run one short readiness check";
            NextActionDetail.Text = state.PathTestValid ? state.PathTestDetail : "No speaking is required. The result remains valid until a relevant setup or model setting changes.";
            NextActionButton.Content = state.PathTestValid ? "View status" : "Open voice-free check";
        }
    }

    private void RenderSettings(CompanionSettings settings)
    {
        AutoStartToggle.IsChecked = settings.AutoStartWithWindows;
        StartWithObsToggle.IsChecked = settings.StartWithObs;
        AdvancedToggle.IsChecked = settings.AdvancedMode;
        AdvancedPanel.IsVisible = settings.AdvancedMode;
        if (settings.PrimarySourceUuid is not null)
            SourcePicker.SelectedItem = _runtime.ObsSources.FirstOrDefault(source => source.Uuid == settings.PrimarySourceUuid);
    }


    private void RenderSources(IReadOnlyList<ObsSource> sources)
    {
        _renderingSources = true;
        SourcePicker.ItemsSource = sources;
        SourcePicker.IsEnabled = sources.Count > 0;
        SourcePicker.SelectedItem = sources.FirstOrDefault(source => source.Uuid == _settings.Current.PrimarySourceUuid);
        SourceHelp.Text = sources.Count == 0 ? "OBS is connected, but no audio sources were reported." : "Choose the source that carries the primary speaker.";
        _renderingSources = false;
    }

    private void RenderTestStages(IReadOnlyList<TestStage> stages)
    {
        TestStagesPanel.Children.Clear();
        foreach (var stage in stages)
        {
            var symbol = stage.State switch { TestStageState.Passed => "✓", TestStageState.Running => "…", TestStageState.Blocked => "△", TestStageState.Failed => "!", _ => "○" };
            var row = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("28,180,*"), ColumnSpacing = 10 };
            row.Children.Add(new TextBlock { Text = symbol, FontSize = 17, VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center });
            var name = new TextBlock { Text = stage.Name, FontWeight = FontWeight.SemiBold, VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center };
            Grid.SetColumn(name, 1); row.Children.Add(name);
            var detail = new TextBlock { Text = stage.Detail, Foreground = new SolidColorBrush(Color.Parse("#5A6872")), TextWrapping = TextWrapping.Wrap };
            Grid.SetColumn(detail, 2); row.Children.Add(detail);
            TestStagesPanel.Children.Add(new Border { Classes = { "soft" }, Child = row, Padding = new Thickness(14, 11) });
        }
        var currentPass = stages.Count > 0 && stages.All(stage => stage.State == TestStageState.Passed);
        var valid = currentPass || _runtime.State.PathTestValid;
        TestStepSymbol.Text = valid ? "✓" : "○";
        TestStepDetail.Text = currentPass ? "Passed just now; this result will be reused while the relevant setup stays unchanged." : _runtime.State.PathTestDetail;
        RunTestButton.Content = _runtime.State.TestRunning ? "Testing…" : "Run full path test";
        RunTestButton.IsEnabled = !_runtime.State.TestRunning && !_runtime.State.BenchmarkRunning;
    }

    private void RenderBenchmark(BenchmarkSnapshot benchmark)
    {
        BenchmarkTranscript.Text = string.IsNullOrWhiteSpace(benchmark.Transcript) ? "Finalized speech will appear here." : benchmark.Transcript;
        RenderMetric(LatencyStatsPanel, benchmark.Latency, value => $"{value:0} ms");
        RenderMetric(ConfidenceStatsPanel, benchmark.Confidence, value => $"{value:P0}");
    }

    private void RenderVoiceLevel(double level)
    {
        var clamped = Math.Clamp(level, -60, 0);
        VoiceLevelMeter.Value = clamped;
        VoiceLevelText.Text = $"{clamped:0} dBFS";
        var color = clamped >= -30 && clamped <= -12 ? "#23845B" :
            clamped >= -40 && clamped <= -2 ? "#E58B2B" : "#BD4D4D";
        VoiceLevelMeter.Foreground = new SolidColorBrush(Color.Parse(color));
    }

    private static void RenderMetric(Panel panel, MetricStatistics metric, Func<double, string> formatter)
    {
        panel.Children.Clear();
        var values = new (string Label, double? Value)[]
        {
            ("Minimum", metric.Minimum), ("Low 1% avg", metric.LowOnePercentAverage),
            ("Median", metric.Median), ("Average", metric.Average), ("P99", metric.P99),
            ("High 1% avg", metric.HighOnePercentAverage), ("Maximum", metric.Maximum)
        };
        foreach (var item in values)
        {
            var content = new StackPanel { Spacing = 2 };
            content.Children.Add(new TextBlock { Text = item.Label, FontSize = 11, Foreground = new SolidColorBrush(Color.Parse("#5A6872")) });
            content.Children.Add(new TextBlock { Text = item.Value.HasValue ? formatter(item.Value.Value) : "—", FontWeight = FontWeight.SemiBold });
            panel.Children.Add(new Border { Classes = { "soft" }, Child = content, MinWidth = 96, Margin = new Thickness(0, 0, 8, 8), Padding = new Thickness(12, 9) });
        }
    }

    private void AddLog(string line)
    {
        if (RecentLogsPanel.Children.Count == 1 && RecentLogsPanel.Children[0] is TextBlock text && text.Text?.StartsWith("Waiting") == true) RecentLogsPanel.Children.Clear();
        RecentLogsPanel.Children.Insert(0, new TextBlock { Text = line, Foreground = new SolidColorBrush(Color.Parse("#CFDADD")), FontFamily = new FontFamily("Consolas"), FontSize = 12, TextWrapping = TextWrapping.Wrap });
        while (RecentLogsPanel.Children.Count > 20) RecentLogsPanel.Children.RemoveAt(RecentLogsPanel.Children.Count - 1);
    }

    private void OnClosing(object? sender, WindowClosingEventArgs args)
    {
        if (_allowExit) return;
        args.Cancel = true;
        Hide();
    }

    public async Task<bool> ConfirmQuitAsync()
    {
        if (!_runtime.State.RequiresQuitConfirmation) return true;
        var dialog = new DecisionWindow("Quit while OBS is active?", _runtime.State.QuitWarning, "Quit Companion");
        return await dialog.ShowDialog<bool>(this);
    }

    public void AllowExit() => _allowExit = true;

    private static (CompanionRuntime, CompanionSettingsStore) CreateDefaultServices()
    {
        var paths = CompanionPaths.ForCurrentUser();
        var settings = new CompanionSettingsStore(paths.SettingsPath);
        return (new CompanionRuntime(paths, settings), settings);
    }
}
