using System.Diagnostics;

namespace PSLauncher.Updater;

/// <summary>
/// Mini exe qui remplace le launcher principal (qui ne peut pas s'auto-écraser pendant qu'il tourne).
///
/// Args attendus :
///   --target=&lt;chemin_complet_de_PSLauncher.exe&gt;
///   --source=&lt;chemin_du_nouveau_PSLauncher.exe&gt;
///   [--launch] : relance PSLauncher.exe une fois la copie faite
///   [--pid=&lt;PID&gt;] : attend que ce process se termine avant de copier
///
/// Workflow :
///  1. Si --pid fourni, attend la sortie du process.
///  2. Sinon, attend que le fichier target soit déverrouillé (poll 250 ms).
///  3. Backup target → target.bak (au cas où).
///  4. Copie source → target.
///  5. Optionnellement relance target.
///  6. Supprime source (le .new) puis target.bak après 30 s.
/// </summary>
internal static class Program
{
    [STAThread]
    private static int Main(string[] args)
    {
        try
        {
            var opts = ParseArgs(args);
            if (string.IsNullOrEmpty(opts.Target) || string.IsNullOrEmpty(opts.Source))
            {
                Console.Error.WriteLine("Usage: PSLauncher.Updater --target=<exe> --source=<new.exe> [--launch] [--pid=N]");
                return 2;
            }

            // 1. Attendre la sortie du launcher principal
            if (opts.Pid is int pid)
            {
                try
                {
                    var proc = Process.GetProcessById(pid);
                    proc.WaitForExit(60_000);
                }
                catch { /* déjà mort, OK */ }
            }

            // 2. Attendre que le target soit déverrouillé
            var deadline = DateTime.UtcNow.AddSeconds(60);
            while (DateTime.UtcNow < deadline)
            {
                if (!File.Exists(opts.Target)) break;
                try
                {
                    using var fs = File.Open(opts.Target, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
                    break;
                }
                catch (IOException)
                {
                    Thread.Sleep(250);
                }
            }

            // 3. Backup
            var backup = opts.Target + ".bak";
            try { if (File.Exists(backup)) File.Delete(backup); } catch { }
            try { if (File.Exists(opts.Target)) File.Move(opts.Target, backup); } catch (Exception ex)
            {
                Console.Error.WriteLine($"Backup failed: {ex.Message}");
                return 3;
            }

            // 4. Copy new file in place
            try
            {
                File.Copy(opts.Source, opts.Target, overwrite: true);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"Copy failed: {ex.Message}");
                // Tente la restauration
                try { if (File.Exists(backup)) File.Move(backup, opts.Target, overwrite: true); } catch { }
                return 4;
            }

            // 5. Relance
            if (opts.Launch)
            {
                try
                {
                    Process.Start(new ProcessStartInfo
                    {
                        FileName = opts.Target,
                        UseShellExecute = true,
                        WorkingDirectory = Path.GetDirectoryName(opts.Target)!,
                    });
                }
                catch (Exception ex)
                {
                    Console.Error.WriteLine($"Relaunch failed: {ex.Message}");
                }
            }

            // 6. Cleanup différé (le .new + .bak après 30s, en background détaché)
            try { File.Delete(opts.Source); } catch { }
            // .bak laissé sur place : si le nouveau exe plante au lancement, l'utilisateur
            // peut renommer .bak → .exe pour récupérer l'ancien.
            return 0;
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"Updater fatal: {ex}");
            return 1;
        }
    }

    private sealed class Options
    {
        public string? Target { get; set; }
        public string? Source { get; set; }
        public bool Launch { get; set; }
        public int? Pid { get; set; }
    }

    private static Options ParseArgs(string[] args)
    {
        var o = new Options();
        foreach (var a in args)
        {
            if (a.StartsWith("--target=", StringComparison.OrdinalIgnoreCase))      o.Target = a["--target=".Length..];
            else if (a.StartsWith("--source=", StringComparison.OrdinalIgnoreCase)) o.Source = a["--source=".Length..];
            else if (a.StartsWith("--pid=", StringComparison.OrdinalIgnoreCase))    o.Pid = int.TryParse(a["--pid=".Length..], out var p) ? p : null;
            else if (a.Equals("--launch", StringComparison.OrdinalIgnoreCase))      o.Launch = true;
        }
        return o;
    }
}
