using System.Net.Http;
using System.Reflection;
using System.Windows;
using ClaudeDo.Installer.Core;
using ClaudeDo.Releases;
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);

        // --- Self-update pre-flight ---
        // Resolve current exe path. Assembly.Location may point to a .dll for apphost-based
        // .NET apps; swap to the .exe companion when that happens.
        var currentExePath = Assembly.GetEntryAssembly()!.Location;
        if (currentExePath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
        {
            currentExePath = System.IO.Path.ChangeExtension(currentExePath, ".exe");
        }

        // Arg form: --replace-self "<old-path>"
        var replaceSelfIndex = Array.FindIndex(e.Args, a => a.Equals("--replace-self", StringComparison.OrdinalIgnoreCase));
        if (replaceSelfIndex >= 0 && replaceSelfIndex + 1 < e.Args.Length)
        {
            var oldPath = e.Args[replaceSelfIndex + 1];
            var relaunched = await SelfUpdater.HandleReplaceSelfAsync(
                oldPath: oldPath,
                currentExePath: currentExePath,
                launchProcess: path =>
                {
                    try
                    {
                        System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true });
                        return true;
                    }
                    catch { return false; }
                });
            if (relaunched)
            {
                Shutdown(0);
                return;
            }
            // Replacement failed — fall through to normal wizard from the temp location.
        }
        else
        {
            // Normal launch: check for a newer installer.
            using var selfUpdateHttp = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
            var selfUpdateReleases = new ReleaseClient(selfUpdateHttp);
            var currentVersion = GetInstallerVersion();

            var decision = await SelfUpdater.DecideUpdateAsync(selfUpdateReleases, currentVersion, CancellationToken.None);
            if (decision.Kind == SelfUpdateDecisionKind.UpdateAvailable)
            {
                var prompt = new SelfUpdatePromptWindow(currentVersion, decision.LatestVersion!);
                DarkTitleBar.Apply(prompt);
                var ok = prompt.ShowDialog() == true;
                if (!ok)
                {
                    Shutdown(0);
                    return;
                }
                if (prompt.Choice == SelfUpdateChoice.Update)
                {
                    prompt.ShowProgress("Downloading...");
                    var tempDir = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "ClaudeDo.Installer.Update");
                    var verifiedPath = await SelfUpdater.DownloadAndVerifyAsync(
                        selfUpdateReleases,
                        decision.InstallerAsset!,
                        decision.ChecksumsAsset!,
                        tempDir,
                        new Progress<long>(_ => { }),
                        CancellationToken.None);

                    if (verifiedPath is null)
                    {
                        MessageBox.Show(prompt,
                            "Update download or verification failed. Continuing with current installer.",
                            "ClaudeDo Installer", MessageBoxButton.OK, MessageBoxImage.Warning);
                    }
                    else
                    {
                        try
                        {
                            var psi = new System.Diagnostics.ProcessStartInfo(verifiedPath)
                            {
                                UseShellExecute = true,
                            };
                            psi.ArgumentList.Add("--replace-self");
                            psi.ArgumentList.Add(currentExePath);
                            System.Diagnostics.Process.Start(psi);
                            Shutdown(0);
                            return;
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show(prompt,
                                "Failed to launch updated installer: " + ex.Message + "\nContinuing with current installer.",
                                "ClaudeDo Installer", MessageBoxButton.OK, MessageBoxImage.Warning);
                        }
                    }
                }
                // SelfUpdateChoice.Continue — fall through to normal wizard.
            }
            // No-update or check failed — fall through to normal wizard.
        }

        // --- Existing wizard start-up unchanged below this line ---

        _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();
    }
}
