using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Descriptor;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Tests.TestUtilities;
using Xunit;

namespace StellaOps.Scanner.Sbomer.BuildXPlugin.Tests.Descriptor;

public sealed class DescriptorGeneratorTests
{
    [Fact]
    public async Task CreateAsync_BuildsDeterministicDescriptor()
    {
        await using var temp = new TempDirectory();
        var sbomPath = Path.Combine(temp.Path, "sample.cdx.json");
        await File.WriteAllTextAsync(sbomPath, "{\"bomFormat\":\"CycloneDX\",\"specVersion\":\"1.5\"}");

        var fakeTime = new FakeTimeProvider(new DateTimeOffset(2025, 10, 18, 12, 0, 0, TimeSpan.Zero));
        var generator = new DescriptorGenerator(fakeTime);

        var request = new DescriptorRequest
        {
            ImageDigest = "sha256:0123456789abcdef",
            SbomPath = sbomPath,
            SbomMediaType = "application/vnd.cyclonedx+json",
            SbomFormat = "cyclonedx-json",
            SbomKind = "inventory",
            SbomArtifactType = "application/vnd.stellaops.sbom.layer+json",
            SubjectMediaType = "application/vnd.oci.image.manifest.v1+json",
            GeneratorVersion = "1.2.3",
            GeneratorName = "StellaOps.Scanner.Sbomer.BuildXPlugin",
            LicenseId = "lic-123",
            SbomName = "sample.cdx.json",
            Repository = "git.stella-ops.org/stellaops",
            BuildRef = "refs/heads/main",
            AttestorUri = "https://attestor.local/api/v1/provenance"
        }.Validate();

        var document = await generator.CreateAsync(request, CancellationToken.None);

        Assert.Equal(DescriptorGenerator.Schema, document.Schema);
        Assert.Equal(fakeTime.GetUtcNow(), document.GeneratedAt);
        Assert.Equal(request.ImageDigest, document.Subject.Digest);
        Assert.Equal(request.SbomMediaType, document.Artifact.MediaType);
        Assert.Equal(request.SbomName, document.Artifact.Annotations["org.opencontainers.image.title"]);
        Assert.Equal("pending", document.Provenance.Status);
        Assert.Equal(request.AttestorUri, document.Provenance.AttestorUri);
        Assert.Equal(request.PredicateType, document.Provenance.PredicateType);

        var expectedSbomDigest = ComputeSha256File(sbomPath);
        Assert.Equal(expectedSbomDigest, document.Artifact.Digest);
        Assert.Equal(expectedSbomDigest, document.Metadata["sbomDigest"]);

        var expectedDsse = ComputeExpectedDsse(request.ImageDigest, expectedSbomDigest, document.Provenance.Nonce);
        Assert.Equal(expectedDsse, document.Provenance.ExpectedDsseSha256);
        Assert.Equal(expectedDsse, document.Artifact.Annotations["org.stellaops.provenance.dsse.sha256"]);
    }

    private static string ComputeSha256File(string path)
    {
        using var stream = File.OpenRead(path);
        var hash = SHA256.HashData(stream);
        return $"sha256:{Convert.ToHexString(hash).ToLower(CultureInfo.InvariantCulture)}";
    }

    private static string ComputeExpectedDsse(string imageDigest, string sbomDigest, string nonce)
    {
        var payload = $"{imageDigest}\n{sbomDigest}\n{nonce}";
        Span<byte> hash = stackalloc byte[32];
        SHA256.HashData(Encoding.UTF8.GetBytes(payload), hash);
        return $"sha256:{Convert.ToHexString(hash).ToLower(CultureInfo.InvariantCulture)}";
    }
}
