using System.Diagnostics;

namespace BrassAndSigil.Server.Services;

/// <summary>
/// Downloads NeoForge's official server installer JAR and runs it with --installServer
/// to produce run.sh/run.bat + the server library tree. Handles Java invocation and
/// streams installer output via a progress callback.
/// </summary>
public sealed class NeoForgeInstaller
{
    private static readonly HttpClient _http = new() { Timeout = TimeSpan.FromMinutes(10) };

    public bool IsAlreadyInstalled(string serverDir)
    {
        return File.Exists(Path.Combine(serverDir, OperatingSystem.IsWindows() ? "run.bat" : "run.sh"));
    }

    public async Task<bool> InstallAsync(string version, string serverDir, string javaPath,
                                         IProgress<string>? progress, CancellationToken ct)
    {
        Directory.CreateDirectory(serverDir);

        // 1. Download installer
        var installerName = $"neoforge-{version}-installer.jar";
        var installerPath = Path.Combine(serverDir, installerName);
        var url = $"https://maven.neoforged.net/releases/net/neoforged/neoforge/{version}/{installerName}";

        if (!File.Exists(installerPath))
        {
            progress?.Report($"Downloading NeoForge {version} installer...");
            var bytes = await _http.GetByteArrayAsync(url, ct);
            await File.WriteAllBytesAsync(installerPath, bytes, ct);
            progress?.Report($"  Saved {bytes.Length:N0} bytes to {installerName}");
        }
        else
        {
            progress?.Report($"NeoForge installer already present, skipping download.");
        }

        // 2. Run installer
        progress?.Report("Running NeoForge installer (java -jar ... --installServer)...");
        var psi = new ProcessStartInfo
        {
            FileName = javaPath,
            WorkingDirectory = serverDir,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true,
        };
        psi.ArgumentList.Add("-jar");
        psi.ArgumentList.Add(installerName);
        psi.ArgumentList.Add("--installServer");

        Process? proc;
        try
        {
            proc = Process.Start(psi);
        }
        catch (Exception ex)
        {
            progress?.Report($"  [error] Could not start java: {ex.Message}");
            return false;
        }
        if (proc is null)
        {
            progress?.Report("  [error] Failed to start java.");
            return false;
        }

        var stdoutTask = StreamLines(proc.StandardOutput, line => progress?.Report($"  {line}"), ct);
        var stderrTask = StreamLines(proc.StandardError, line => progress?.Report($"  [err] {line}"), ct);

        await proc.WaitForExitAsync(ct);
        await Task.WhenAll(stdoutTask, stderrTask);

        if (proc.ExitCode != 0)
        {
            progress?.Report($"  [error] NeoForge installer exited with code {proc.ExitCode}");
            return false;
        }

        // 3. Verify run script exists
        if (!IsAlreadyInstalled(serverDir))
        {
            progress?.Report("  [error] NeoForge installer ran but run.sh/run.bat is missing.");
            return false;
        }

        progress?.Report($"NeoForge {version} installed.");

        // 4. Clean up the installer JAR (large, no longer needed)
        try { File.Delete(installerPath); } catch { /* best-effort */ }

        return true;
    }

    private static async Task StreamLines(StreamReader reader, Action<string> onLine, CancellationToken ct)
    {
        try
        {
            while (!reader.EndOfStream)
            {
                var line = await reader.ReadLineAsync(ct);
                if (line is null) break;
                onLine(line);
            }
        }
        catch { }
    }
}
