using System.Diagnostics;
using System.Reflection;
using Microsoft.Extensions.Logging;
using PSLauncher.Core.Configuration;
using PSLauncher.Core.Downloads;
using PSLauncher.Core.Installations;
using PSLauncher.Models;
using SysProcess = System.Diagnostics.Process;

namespace PSLauncher.Core.Updates;

public sealed class LauncherSelfUpdater : ILauncherSelfUpdater
{
    private readonly IDownloadManager _downloadManager;
    private readonly IConfigStore _configStore;
    private readonly ILogger<LauncherSelfUpdater> _logger;

    public LauncherSelfUpdater(
        IDownloadManager downloadManager,
        IConfigStore configStore,
        ILogger<LauncherSelfUpdater> logger)
    {
        _downloadManager = downloadManager;
        _configStore = configStore;
        _logger = logger;
    }

    public string GetCurrentVersion()
    {
        var asm = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
        var v = asm.GetName().Version;
        return v is null ? "0.0.0" : $"{v.Major}.{v.Minor}.{v.Build}";
    }

    public bool IsNewerThanCurrent(LauncherInfo launcher)
    {
        var current = SemVer.Parse(GetCurrentVersion());
        var remote  = SemVer.Parse(launcher.Version);
        return remote.CompareTo(current) > 0;
    }

    public async Task<bool> DownloadAndApplyAsync(
        LauncherInfo launcher,
        IProgress<DownloadProgress>? progress,
        CancellationToken ct)
    {
        // 1. Localise le PS_Launcher.exe en cours d'exécution
        var processModule = SysProcess.GetCurrentProcess().MainModule
                            ?? throw new InvalidOperationException("Cannot locate current process module");
        var targetExe = processModule.FileName!;
        var installDir = Path.GetDirectoryName(targetExe)!;
        _logger.LogInformation("Self-update target: {Path}", targetExe);

        // 2. Télécharge le nouveau exe dans le cache
        var stagingDir = Path.Combine(_configStore.ConfigDirectory, "selfupdate");
        Directory.CreateDirectory(stagingDir);

        var downloadJob = new DownloadJob(
            $"launcher-{launcher.Version}",
            new Uri(launcher.Download.Url),
            launcher.Download.SizeBytes,
            launcher.Download.Sha256);

        var downloadedPath = await _downloadManager.DownloadAsync(downloadJob, progress, ct).ConfigureAwait(false);
        _logger.LogInformation("New launcher downloaded to {Path}", downloadedPath);

        // 3. Localise PS_Launcher.Updater.exe (à côté du target)
        var updaterPath = Path.Combine(installDir, "PS_Launcher.Updater.exe");
        if (!File.Exists(updaterPath))
        {
            throw new FileNotFoundException(
                "PS_Launcher.Updater.exe est manquant à côté de PS_Launcher.exe. " +
                "Réinstalle le launcher depuis l'installeur officiel.",
                updaterPath);
        }

        // 4. Copie le .new dans le staging dir si downloaded est ailleurs
        var newPath = Path.Combine(stagingDir, $"PS_Launcher-{launcher.Version}.exe");
        if (!string.Equals(downloadedPath, newPath, StringComparison.OrdinalIgnoreCase))
        {
            if (File.Exists(newPath)) File.Delete(newPath);
            File.Move(downloadedPath, newPath);
        }

        // 5. Lance l'updater détaché. Si le target est dans un dossier protégé
        // (Program Files, etc.), on ne pourra pas le remplacer sans admin :
        // on demande l'élévation via ShellExecute + Verb=runas (UAC popup).
        var pid = Environment.ProcessId;
        var needsElevation = !CanWriteTo(targetExe);

        var psi = new ProcessStartInfo
        {
            FileName = updaterPath,
            UseShellExecute = needsElevation,           // requis pour Verb=runas
            CreateNoWindow = !needsElevation,
            Verb = needsElevation ? "runas" : "",
            Arguments = string.Join(' ', new[]
            {
                $"--target=\"{targetExe}\"",
                $"--source=\"{newPath}\"",
                $"--pid={pid}",
                "--launch",
            }),
        };

        _logger.LogInformation("Spawning updater {Updater} (target={Target}, source={Source}, pid={Pid}, elevate={Elevate})",
            updaterPath, targetExe, newPath, pid, needsElevation);
        SysProcess.Start(psi);

        return true; // l'appelant doit Application.Current.Shutdown() après
    }

    /// <summary>Test rapide : peut-on écrire à côté du fichier cible (= dans son dossier) ?</summary>
    private static bool CanWriteTo(string filePath)
    {
        try
        {
            var dir = Path.GetDirectoryName(filePath)!;
            var probe = Path.Combine(dir, ".pslauncher-write-test-" + Guid.NewGuid().ToString("N").Substring(0, 8));
            File.WriteAllText(probe, "");
            File.Delete(probe);
            return true;
        }
        catch { return false; }
    }
}
