using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Localization;
using ClaudeDo.Installer.Steps;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.DependencyInjection;

namespace ClaudeDo.Installer.Pages.InstallPage;

public partial class StepViewModel : ObservableObject
{
    public string Name { get; }

    [ObservableProperty] private StepStatus _status = StepStatus.Pending;
    [ObservableProperty] private bool _isExpanded;

    public ObservableCollection<string> Messages { get; } = [];

    public StepViewModel(string name) => Name = name;
}

public partial class InstallPageViewModel : ObservableObject, IInstallerPage
{
    private readonly InstallContext _context;
    private readonly IServiceProvider _serviceProvider;
    private InstallPageView? _view;
    private CancellationTokenSource? _cts;

    public string Title => TrExtension.Localizer?["installer.install.title"] ?? "Install";
    public string Icon => "\uE896";
    public int Order => 99;
    public bool ShowInWizard => true;
    public bool ShowInSettings => false;
    public UserControl View => _view ??= new InstallPageView { DataContext = this };

    public ObservableCollection<StepViewModel> Steps { get; } = [];

    [ObservableProperty] private bool _isInstalling;
    [ObservableProperty] private bool _isComplete;
    [ObservableProperty] private bool _hasErrors;
    [ObservableProperty] private double _overallProgress;

    public InstallPageViewModel(InstallContext context, IServiceProvider serviceProvider)
    {
        _context = context;
        _serviceProvider = serviceProvider;
    }

    public Task LoadAsync()
    {
        Steps.Clear();
        if (_context.Mode == InstallerMode.Update)
        {
            Steps.Add(new StepViewModel("Stop Worker"));
            Steps.Add(new StepViewModel("Download and Extract"));
            Steps.Add(new StepViewModel("Register Autostart"));
            Steps.Add(new StepViewModel("Register MCP with Claude"));
            Steps.Add(new StepViewModel("Start Worker"));
            Steps.Add(new StepViewModel("Write Install Manifest"));
            Steps.Add(new StepViewModel("Register in Add/Remove Programs"));
        }
        else
        {
            Steps.Add(new StepViewModel("Download and Extract"));
            Steps.Add(new StepViewModel("Write Configuration"));
            Steps.Add(new StepViewModel("Initialize Database"));
            Steps.Add(new StepViewModel("Register MCP with Claude"));
            Steps.Add(new StepViewModel("Register Autostart"));
            Steps.Add(new StepViewModel("Create Shortcuts"));
            Steps.Add(new StepViewModel("Register in Add/Remove Programs"));
            Steps.Add(new StepViewModel("Write Install Manifest"));
            Steps.Add(new StepViewModel("Start Worker"));
        }
        return Task.CompletedTask;
    }

    public Task ApplyAsync() => RunInstallAsync();

    public bool Validate() => true;

    [RelayCommand]
    private async Task RunInstallAsync()
    {
        if (IsInstalling) return;

        // Reset per-step state so a re-run starts clean instead of appending
        // output to the previous run's messages.
        foreach (var s in Steps)
        {
            s.Messages.Clear();
            s.Status = StepStatus.Pending;
            s.IsExpanded = false;
        }

        IsInstalling = true;
        IsComplete = false;
        HasErrors = false;
        OverallProgress = 0;

        _cts = new CancellationTokenSource();

        var progress = new Progress<StepProgress>(p =>
        {
            var step = Steps.FirstOrDefault(s => s.Name == p.StepName);
            if (step is null) return;

            // Status and output lines arrive on two separate Progress<T> channels, so a
            // trailing "Running" line-message can be delivered after the step's terminal
            // Done/Failed. Never let that downgrade a completed step back to Running.
            if (!(step.Status is StepStatus.Done or StepStatus.Failed && p.Status is StepStatus.Running))
                step.Status = p.Status;
            if (p.Message is not null)
            {
                // Messages starting with "\r" overwrite the previous line (live progress).
                if (p.Message.StartsWith('\r'))
                {
                    var line = p.Message[1..];
                    if (step.Messages.Count > 0 && step.Messages[^1].StartsWith("  "))
                        step.Messages[^1] = line;
                    else
                        step.Messages.Add(line);
                }
                else
                {
                    step.Messages.Add(p.Message);
                }
            }

            if (p.Status is StepStatus.Running && !step.IsExpanded)
                step.IsExpanded = true;

            if (p.Status is StepStatus.Done or StepStatus.Failed)
            {
                var completed = Steps.Count(s => s.Status is StepStatus.Done or StepStatus.Failed);
                OverallProgress = (double)completed / Steps.Count * 100;
            }
        });

        try
        {
            IEnumerable<IInstallStep> steps;
            if (_context.Mode == InstallerMode.Update)
            {
                steps = new IInstallStep[]
                {
                    _serviceProvider.GetRequiredService<StopWorkerStep>(),
                    _serviceProvider.GetRequiredService<DownloadAndExtractStep>(),
                    // Migrates the legacy service away and (re)registers the logon task.
                    _serviceProvider.GetRequiredService<RegisterAutostartStep>(),
                    _serviceProvider.GetRequiredService<RegisterMcpStep>(),
                    _serviceProvider.GetRequiredService<StartWorkerStep>(),
                    _serviceProvider.GetRequiredService<WriteInstallManifestStep>(),
                    // Refresh the bundled uninstaller exe + Add/Remove-Programs version so a
                    // manual update also renews the installer that bootstraps future updates.
                    _serviceProvider.GetRequiredService<WriteUninstallRegistryStep>(),
                };
            }
            else
            {
                steps = _serviceProvider.GetServices<IInstallStep>();
            }

            var runner = new InstallerService(steps);
            var results = await runner.ExecuteAsync(_context, progress, _cts.Token);
            HasErrors = results.Any(r => !r.Result.Success);
        }
        catch (OperationCanceledException)
        {
            HasErrors = true;
        }
        finally
        {
            IsInstalling = false;
            IsComplete = true;
            _cts.Dispose();
            _cts = null;
        }
    }

    [RelayCommand]
    private void CancelInstall()
    {
        _cts?.Cancel();
    }

    [RelayCommand]
    private void LaunchApp()
    {
        var appExe = System.IO.Path.Combine(_context.InstallDirectory, "app", "ClaudeDo.App.exe");
        if (System.IO.File.Exists(appExe))
        {
            Process.Start(new ProcessStartInfo(appExe) { UseShellExecute = true });
            Application.Current.Shutdown();
        }
    }
}
