using System.Net.Http;
using System.Reflection;
using System.Windows;
using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Pages.InstallPage;
using ClaudeDo.Installer.Pages.PathsPage;
using ClaudeDo.Installer.Pages.ServicePage;
using ClaudeDo.Installer.Pages.UiSettingsPage;
using ClaudeDo.Installer.Pages.WelcomePage;
using ClaudeDo.Installer.Steps;
using ClaudeDo.Installer.Views;
using Microsoft.Extensions.DependencyInjection;

namespace ClaudeDo.Installer;

public partial class App : Application
{
    private ServiceProvider? _services;

    protected override async void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        _services = BuildServices();

        var context = _services.GetRequiredService<InstallContext>();
        context.InstallerVersion = GetInstallerVersion();

        // Default install dir for detection — on upgrade we stay where we were.
        var detector = _services.GetRequiredService<InstallModeDetector>();
        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

        // Read manifest up front so we can fall back to Config if the API times out
        // on an existing install. If the API is slow, we do NOT want to drop an
        // already-installed user into FreshInstall — that would risk overwriting them.
        var existingManifest = InstallManifestStore.TryRead(context.InstallDirectory);

        DetectedState state;
        try
        {
            state = await detector.DetectAsync(context.InstallDirectory, cts.Token);
        }
        catch (OperationCanceledException)
        {
            state = existingManifest is not null
                ? new DetectedState(InstallerMode.Config, existingManifest, null, null)
                : new DetectedState(InstallerMode.FreshInstall, null, null, null);
        }

        context.Mode = state.Mode;
        context.InstalledVersion = state.Existing?.Version;
        context.LatestVersion = state.LatestVersion;
        context.LatestTagUnparseable = state.LatestTagUnparseable;
        if (state.Existing is not null)
            context.InstallDirectory = state.Existing.InstallDir;

        Window mainWindow = state.Mode switch
        {
            InstallerMode.FreshInstall or InstallerMode.Update => new WizardWindow
            {
                DataContext = _services.GetRequiredService<WizardViewModel>()
            },
            InstallerMode.Config => new SettingsWindow
            {
                DataContext = _services.GetRequiredService<SettingsViewModel>()
            },
            _ => throw new InvalidOperationException($"Unknown installer mode: {state.Mode}")
        };

        DarkTitleBar.Apply(mainWindow);
        mainWindow.Show();
    }

    protected override void OnExit(ExitEventArgs e)
    {
        _services?.Dispose();
        base.OnExit(e);
    }

    private static string GetInstallerVersion()
    {
        var infoAttr = Assembly.GetExecutingAssembly()
            .GetCustomAttribute<AssemblyInformationalVersionAttribute>();
        return infoAttr?.InformationalVersion ?? "0.0.0";
    }

    private static ServiceProvider BuildServices()
    {
        var sc = new ServiceCollection();

        // Core
        sc.AddSingleton<InstallContext>();
        sc.AddSingleton<PageResolver>();
        // HTTP + release client
        sc.AddSingleton(_ => new HttpClient { Timeout = TimeSpan.FromSeconds(15) });
        sc.AddSingleton<IReleaseClient>(sp => new ReleaseClient(sp.GetRequiredService<HttpClient>()));
        sc.AddSingleton<InstallModeDetector>();

        // Pages
        sc.AddSingleton<IInstallerPage, WelcomePageViewModel>();
        sc.AddSingleton<IInstallerPage, PathsPageViewModel>();
        sc.AddSingleton<IInstallerPage, ServicePageViewModel>();
        sc.AddSingleton<IInstallerPage, UiSettingsPageViewModel>();
        sc.AddSingleton<IInstallerPage, InstallPageViewModel>();

        // Steps — execution order matters for the FreshInstall pipeline (IEnumerable<IInstallStep>).
        // Double-registered as both IInstallStep and concrete type so Task 15's Update pipeline
        // can pull them out individually via GetRequiredService<T>().
        sc.AddSingleton<DownloadAndExtractStep>();
        sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<DownloadAndExtractStep>());
        sc.AddSingleton<IInstallStep, WriteConfigStep>();
        sc.AddSingleton<IInstallStep, InitDatabaseStep>();
        sc.AddSingleton<IInstallStep, RegisterServiceStep>();
        sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<StartServiceStep>());
        sc.AddSingleton<IInstallStep, CreateShortcutsStep>();
        sc.AddSingleton<IInstallStep, WriteUninstallRegistryStep>();
        sc.AddSingleton<WriteInstallManifestStep>();
        sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<WriteInstallManifestStep>());

        // Stop — NOT registered as IInstallStep (not part of default FreshInstall pipeline).
        // Pulled by Update flow + Repair/Uninstall.
        sc.AddSingleton<StopServiceStep>();
        // StartServiceStep is also registered as IInstallStep above (fresh-install pipeline).
        sc.AddSingleton<StartServiceStep>();

        // Runners
        sc.AddSingleton<UninstallRunner>();

        // ViewModels
        sc.AddSingleton<WizardViewModel>();
        sc.AddSingleton<SettingsViewModel>();

        return sc.BuildServiceProvider();
    }
}
