using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Windows;
using ClaudeDo.Installer.Checks;
using ClaudeDo.Installer.Checks.Interfaces;
using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Core.Interfaces;
using ClaudeDo.Installer.Localization;
using ClaudeDo.Localization;
using ClaudeDo.Releases;
using ClaudeDo.Installer.Pages.DiagnosePage;
using ClaudeDo.Installer.Pages.InstallPage;
using ClaudeDo.Installer.Pages.PathsPage;
using ClaudeDo.Installer.Pages.ServicePage;
using ClaudeDo.Installer.Pages.SystemCheckPage;
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);

        // The app relaunches us via ShellExecute without an explicit working directory, so we
        // inherit its CWD — which the Start Menu shortcut sets to <InstallDir>\app. A process's
        // current directory is locked by the OS, so we would block DownloadAndExtractStep's
        // Directory.Move of app\ against ourselves (fails on every attempt, reboot included).
        // Step out of the install tree before anything else touches it.
        try { Environment.CurrentDirectory = Path.GetTempPath(); } catch { /* best effort */ }

        // --- Initialize localizer as early as possible so all windows can use {loc:Tr} ---
        var localesDir = Path.Combine(AppContext.BaseDirectory, "locales");
        var localeStore = LocaleStore.Load(localesDir);
        var existingSettings = InstallerAppSettings.Load();
        var initialLang = !string.IsNullOrWhiteSpace(existingSettings.Language)
            ? existingSettings.Language
            : CultureResolver.Resolve(CultureInfo.CurrentUICulture.Name,
                  localeStore.Available.Select(l => l.Code).ToArray(), "en");
        var localizer = new Localizer(localeStore, initialLang);
        TrExtension.Localizer = localizer;

        _services = BuildServices(localizer);

        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(ILocalizer localizer)
    {
        var sc = new ServiceCollection();
        sc.AddSingleton(localizer);

        // 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>();

        // Environment checks — stateless, so their infrastructure is shared; ClaudeCliLookup is
        // rebuilt per EnvironmentCheckService instance so a re-check doesn't reuse a stale result.
        sc.AddSingleton<IProcessRunner, ProcessRunner>();
        sc.AddSingleton<IProcessLauncher, ProcessLauncher>();
        sc.AddSingleton<IPortOwnerResolver, NetstatPortOwnerResolver>();
        sc.AddSingleton<ClaudeHelpLauncher>();
        sc.AddTransient<Func<EnvironmentCheckService>>(sp => () =>
        {
            var processRunner = sp.GetRequiredService<IProcessRunner>();
            var claudeLookup = new ClaudeCliLookup(processRunner);
            return new EnvironmentCheckService(new IEnvironmentCheck[]
            {
                new GitCheck(processRunner),
                new GitIdentityCheck(processRunner),
                new PortCheck(sp.GetRequiredService<IPortOwnerResolver>()),
                new WriteAccessCheck(),
                new ClaudeCliCheck(claudeLookup),
                new ClaudeVersionCheck(claudeLookup),
                new ClaudeAuthCheck(claudeLookup),
                new PermissionModeAutoCheck(claudeLookup),
            });
        });

        // Pages
        sc.AddSingleton<IInstallerPage, WelcomePageViewModel>();
        sc.AddSingleton<IInstallerPage, SystemCheckPageViewModel>();
        sc.AddSingleton<IInstallerPage, PathsPageViewModel>();
        sc.AddSingleton<IInstallerPage, ServicePageViewModel>();
        sc.AddSingleton<IInstallerPage, UiSettingsPageViewModel>();
        sc.AddSingleton<IInstallerPage, InstallPageViewModel>();
        sc.AddSingleton<IInstallerPage>(sp => new DiagnosePageViewModel(
            sp.GetRequiredService<InstallContext>(),
            sp.GetRequiredService<Func<EnvironmentCheckService>>(),
            InstallerWorkerConfig.Load));

        // Steps — execution order matters for the FreshInstall pipeline (IEnumerable<IInstallStep>).
        // Double-registered as both IInstallStep and concrete type so the Update pipeline
        // can pull them out individually via GetRequiredService<T>().
        sc.AddSingleton(sp => new DownloadAndExtractStep(sp.GetRequiredService<IReleaseClient>()));
        sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<DownloadAndExtractStep>());
        sc.AddSingleton<IInstallStep, WriteConfigStep>();
        sc.AddSingleton<IInstallStep, InitDatabaseStep>();
        sc.AddSingleton<RegisterMcpStep>();
        sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<RegisterMcpStep>());
        sc.AddSingleton<RegisterAutostartStep>();
        sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<RegisterAutostartStep>());
        sc.AddSingleton<IInstallStep, CreateShortcutsStep>();
        sc.AddSingleton<WriteUninstallRegistryStep>();
        sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<WriteUninstallRegistryStep>());
        sc.AddSingleton<WriteInstallManifestStep>();
        sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<WriteInstallManifestStep>());
        // Start the worker last in the fresh pipeline (binaries + task must exist first).
        sc.AddSingleton<StartWorkerStep>();
        sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<StartWorkerStep>());

        // Stop — NOT registered as IInstallStep (not part of default FreshInstall pipeline).
        // Pulled by Update flow + Repair/Uninstall.
        sc.AddSingleton<StopWorkerStep>();

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

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

        return sc.BuildServiceProvider();
    }
}
