using System.Security.Cryptography;

namespace Flo.Installer;

internal sealed class ArtifactDownloader(HttpClient http)
{
    public async Task DownloadAsync(
        Uri url,
        string destinationPath,
        string expectedSha256,
        long expectedSize,
        IProgress<DownloadProgress>? progress,
        CancellationToken ct)
    {
        Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!);

        using var response = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, ct);
        response.EnsureSuccessStatusCode();

        await using var source = await response.Content.ReadAsStreamAsync(ct);
        await using var destination = File.Create(destinationPath);
        using var sha = SHA256.Create();

        var buffer = new byte[81920];
        long totalRead = 0;
        int read;
        while ((read = await source.ReadAsync(buffer, ct)) > 0)
        {
            await destination.WriteAsync(buffer.AsMemory(0, read), ct);
            sha.TransformBlock(buffer, 0, read, null, 0);
            totalRead += read;
            progress?.Report(new DownloadProgress(totalRead, expectedSize));
        }
        sha.TransformFinalBlock([], 0, 0);

        string actualHex = Convert.ToHexString(sha.Hash!).ToLowerInvariant();
        if (!string.Equals(actualHex, expectedSha256, StringComparison.OrdinalIgnoreCase))
        {
            File.Delete(destinationPath);
            throw new InvalidOperationException(
                $"SHA-256 mismatch for {url}. expected={expectedSha256} actual={actualHex}");
        }

        if (totalRead != expectedSize)
        {
            File.Delete(destinationPath);
            throw new InvalidOperationException(
                $"Size mismatch for {url}. expected={expectedSize} actual={totalRead}");
        }
    }
}

internal readonly record struct DownloadProgress(long BytesRead, long TotalBytes)
{
    public double PercentComplete => TotalBytes > 0 ? (double)BytesRead / TotalBytes * 100.0 : 0.0;
}
