using System.Formats.Tar;
using System.IO.Compression;
using System.Runtime.InteropServices;

namespace BrassAndSigil.Server.Services;

/// <summary>
/// Downloads + extracts Adoptium Temurin JRE 21 to server/java/. Used as a fallback
/// when system Java is missing or too old. Adoptium's API gives us a stable
/// platform-keyed download URL without needing API keys or auth.
/// </summary>
public sealed class JavaInstaller
{
    private static readonly HttpClient _http = new() { Timeout = TimeSpan.FromMinutes(15) };

    public string GetJavaInstallDir(string serverDir) => Path.Combine(serverDir, "java");
    public string GetJavaInstallDir(string serverDir, int majorVersion) =>
        Path.Combine(serverDir, "java" + majorVersion);

    /// <summary>If a previous install put a java executable under serverDir/java/, return its path.</summary>
    public string? FindBundledJava(string serverDir)
    {
        var javaDir = GetJavaInstallDir(serverDir);
        if (!Directory.Exists(javaDir)) return null;
        var exe = OperatingSystem.IsWindows() ? "java.exe" : "java";
        return Directory.EnumerateFiles(javaDir, exe, SearchOption.AllDirectories).FirstOrDefault();
    }

    /// <summary>Find a Java install for a specific major version (e.g. javaXX/jdk-XX*/bin/java).</summary>
    public string? FindBundledJava(string serverDir, int majorVersion)
    {
        var javaDir = GetJavaInstallDir(serverDir, majorVersion);
        if (!Directory.Exists(javaDir)) return null;
        var exe = OperatingSystem.IsWindows() ? "java.exe" : "java";
        return Directory.EnumerateFiles(javaDir, exe, SearchOption.AllDirectories).FirstOrDefault();
    }

    public Task<string?> InstallJre21Async(string serverDir, IProgress<string>? progress, CancellationToken ct)
        => InstallJreAsync(21, serverDir, GetJavaInstallDir(serverDir), progress, ct);

    /// <summary>
    /// Download + extract Adoptium Temurin JRE for a specific major version into
    /// <paramref name="installDir"/>. Used by BlueMap to get JRE 25 alongside the
    /// JRE 21 we use for Minecraft.
    /// </summary>
    public async Task<string?> InstallJreAsync(int majorVersion, string serverDir, string installDir,
                                                IProgress<string>? progress, CancellationToken ct)
    {
        var javaDir = installDir;
        Directory.CreateDirectory(javaDir);

        var (url, archiveName, isZip) = PickAdoptiumDownload(majorVersion);
        if (url is null)
        {
            progress?.Report($"[err] No supported Adoptium binary for {RuntimeInformation.OSDescription} {RuntimeInformation.OSArchitecture}.");
            return null;
        }

        var archivePath = Path.Combine(javaDir, archiveName!);
        progress?.Report($"Downloading Adoptium Temurin JRE 21 ({(isZip ? "zip" : "tar.gz")})...");

        try
        {
            using (var resp = await _http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, ct))
            {
                resp.EnsureSuccessStatusCode();
                await using var src = await resp.Content.ReadAsStreamAsync(ct);
                await using var dst = File.Create(archivePath);
                await src.CopyToAsync(dst, ct);
            }
        }
        catch (Exception ex)
        {
            progress?.Report($"  [err] Download failed: {ex.Message}");
            return null;
        }

        progress?.Report($"  Downloaded {new FileInfo(archivePath).Length:N0} bytes");
        progress?.Report("Extracting...");

        try
        {
            if (isZip)
            {
                ZipFile.ExtractToDirectory(archivePath, javaDir, overwriteFiles: true);
            }
            else
            {
                await using var fs = File.OpenRead(archivePath);
                await using var gzip = new GZipStream(fs, CompressionMode.Decompress);
                await TarFile.ExtractToDirectoryAsync(gzip, javaDir, overwriteFiles: true, ct);
            }
        }
        catch (Exception ex)
        {
            progress?.Report($"  [err] Extract failed: {ex.Message}");
            return null;
        }

        try { File.Delete(archivePath); } catch { /* best-effort */ }

        var javaExe = FindBundledJava(serverDir);
        if (javaExe is null)
        {
            progress?.Report("  [err] Extracted, but couldn't locate bin/java in the result.");
            return null;
        }

        // On Linux/macOS, make sure java is executable. TarFile preserves mode bits in
        // most setups, but be defensive.
        if (!OperatingSystem.IsWindows())
        {
            try
            {
                File.SetUnixFileMode(javaExe,
                    UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
                    UnixFileMode.GroupRead | UnixFileMode.GroupExecute |
                    UnixFileMode.OtherRead | UnixFileMode.OtherExecute);
            }
            catch { }
        }

        progress?.Report($"Java {majorVersion} ready: {javaExe}");
        return javaExe;
    }

    private static (string? Url, string? ArchiveName, bool IsZip) PickAdoptiumDownload(int majorVersion)
    {
        // Adoptium API picks the latest GA release matching our os/arch.
        // Docs: https://api.adoptium.net/q/swagger-ui/
        var arch = RuntimeInformation.OSArchitecture switch
        {
            Architecture.X64   => "x64",
            Architecture.Arm64 => "aarch64",
            _ => null
        };
        if (arch is null) return (null, null, false);

        if (OperatingSystem.IsWindows())
        {
            return ($"https://api.adoptium.net/v3/binary/latest/{majorVersion}/ga/windows/{arch}/jre/hotspot/normal/eclipse",
                    $"jre{majorVersion}.zip", true);
        }
        if (OperatingSystem.IsLinux())
        {
            return ($"https://api.adoptium.net/v3/binary/latest/{majorVersion}/ga/linux/{arch}/jre/hotspot/normal/eclipse",
                    $"jre{majorVersion}.tar.gz", false);
        }
        if (OperatingSystem.IsMacOS())
        {
            return ($"https://api.adoptium.net/v3/binary/latest/{majorVersion}/ga/mac/{arch}/jre/hotspot/normal/eclipse",
                    $"jre{majorVersion}.tar.gz", false);
        }
        return (null, null, false);
    }
}
