using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Metin2Launcher.Config;
using Metin2Launcher.GameLaunch;
using Metin2Launcher.Logging;
using Metin2Launcher.Manifest;
using Metin2Launcher.Orchestration;
using Metin2Launcher.Runtime;
using Metin2Launcher.Telemetry;
using Metin2Launcher.UI;
using Metin2Launcher.UI.ViewModels;
using Velopack;
using Velopack.Sources;

namespace Metin2Launcher;

public static class Program
{
    [STAThread]
    public static int Main(string[] args)
    {
        // Velopack must be wired at the very top of Main so it can handle the
        // install / firstrun / uninstall hooks without the rest of our code running.
        VelopackApp.Build().SetArgs(args).Run();

        var clientRoot = Directory.GetCurrentDirectory();
        var stateDir = Path.Combine(clientRoot, LauncherConfig.StateDirName);
        Directory.CreateDirectory(stateDir);
        Log.SetLogFilePath(Path.Combine(stateDir, "launcher.log"));
        Log.Info($"metin2 launcher starting in {clientRoot}");

        // Single-instance guard: two launchers running in the same install
        // dir race on the staging files and blow up with "The process cannot
        // access the file ... because it is being used by another process".
        // Use a per-install-dir file lock (not a named mutex — named mutexes
        // behave differently across Wine prefixes and Windows sessions). The
        // lock is released when the process exits.
        FileStream? instanceLock = null;
        try
        {
            var lockPath = Path.Combine(stateDir, "launcher.lock");
            instanceLock = new FileStream(
                lockPath,
                FileMode.OpenOrCreate,
                FileAccess.ReadWrite,
                FileShare.None,
                bufferSize: 1,
                FileOptions.DeleteOnClose);
        }
        catch (IOException)
        {
            Log.Error($"another Metin2Launcher is already running in {clientRoot} — refusing to start a second instance");
            return 3;
        }

        if (args.Contains("--nogui"))
        {
            return RunHeadlessAsync(clientRoot, args).GetAwaiter().GetResult();
        }

        try
        {
            return BuildAvaloniaApp()
                .StartWithClassicDesktopLifetime(args, Avalonia.Controls.ShutdownMode.OnMainWindowClose);
        }
        catch (Exception ex)
        {
            Log.Error("avalonia host crashed", ex);
            return 1;
        }
    }

    public static AppBuilder BuildAvaloniaApp()
        => AppBuilder.Configure<App>()
            .UsePlatformDetect()
            .WithInterFont()
            .LogToTrace();

    private static async Task<int> RunHeadlessAsync(string clientRoot, string[] args)
    {
        using var cts = new CancellationTokenSource(LauncherConfig.TotalUpdateTimeout);
        using var http = new HttpClient { Timeout = LauncherConfig.BlobFetchTimeout };
        http.DefaultRequestHeaders.UserAgent.ParseAdd("Metin2Launcher/0.1");

        var settingsPath = Path.Combine(clientRoot, LauncherConfig.StateDirName, "launcher-settings.json");
        var settings = LauncherSettings.Load(settingsPath);
        var reporter = new ClientAppliedReporter(http, LauncherConfig.TelemetryUrlTemplate);
        var orchestrator = new UpdateOrchestrator(clientRoot, settings, http, reporter);
        var progress = new Progress<UpdateProgress>(p =>
        {
            var pct = p.Percent.HasValue ? $" {(int)p.Percent.Value}%" : "";
            var detail = string.IsNullOrEmpty(p.Detail) ? "" : $" — {p.Detail}";
            Log.Info($"[{p.State}]{pct}{detail}");
        });

        var updatedOk = false;
        RuntimeKey? runtimeKey = null;
        try
        {
            var result = await orchestrator.RunAsync(progress, cts.Token).ConfigureAwait(false);
            updatedOk = result.Success;
            runtimeKey = result.RuntimeKey;
        }
        catch (ManifestSignatureException ex)
        {
            Log.Error("manifest signature verification FAILED — refusing to launch", ex);
            return 2;
        }
        catch (OperationCanceledException)
        {
            Log.Warn("update cancelled / timed out");
        }
        catch (Exception ex)
        {
            Log.Error("update flow crashed, falling back to offline launch", ex);
        }

        try { await TrySelfUpdateAsync(cts.Token).ConfigureAwait(false); }
        catch (Exception ex) { Log.Warn("velopack self-update check failed: " + ex.Message); }

        if (!updatedOk)
            Log.Warn("proceeding with current local client (offline fallback)");

        var launched = GameProcess.Launch(clientRoot, LauncherConfig.GameExecutable, runtimeKey);
        return launched ? 0 : 1;
    }

    private static async Task TrySelfUpdateAsync(CancellationToken ct)
    {
        var um = new UpdateManager(new SimpleWebSource(LauncherConfig.VelopackFeedUrl));
        if (!um.IsInstalled)
        {
            Log.Info("velopack: not running as an installed app, skipping self-update");
            return;
        }
        var info = await um.CheckForUpdatesAsync().ConfigureAwait(false);
        if (info is null)
        {
            Log.Info("velopack: launcher is up to date");
            return;
        }
        Log.Info($"velopack: downloading launcher update {info.TargetFullRelease?.Version}");
        await um.DownloadUpdatesAsync(info).ConfigureAwait(false);
        Log.Info("velopack: launcher update staged, will apply on next restart");
    }
}
