using System.Collections.Generic;
using System.Collections.Immutable;
using StellaOps.Vexer.Core;
using Xunit;

namespace StellaOps.Vexer.Core.Tests;

public sealed class VexCanonicalJsonSerializerTests
{
    [Fact]
    public void SerializeClaim_ProducesDeterministicOrder()
    {
        var product = new VexProduct(
            key: "pkg:redhat/demo",
            name: "Demo App",
            version: "1.2.3",
            purl: "pkg:rpm/redhat/demo@1.2.3",
            cpe: "cpe:2.3:a:redhat:demo:1.2.3",
            componentIdentifiers: new[] { "componentB", "componentA" });

        var document = new VexClaimDocument(
            format: VexDocumentFormat.Csaf,
            digest: "sha256:6d5a",
            sourceUri: new Uri("https://example.org/vex/csaf.json"),
            revision: "2024-09-15",
            signature: new VexSignatureMetadata(
                type: "pgp",
                subject: "CN=Red Hat",
                issuer: "CN=Red Hat Root",
                keyId: "0xABCD",
                verifiedAt: new DateTimeOffset(2025, 10, 14, 9, 30, 0, TimeSpan.Zero)));

        var claim = new VexClaim(
            vulnerabilityId: "CVE-2025-12345",
            providerId: "redhat",
            product: product,
            status: VexClaimStatus.NotAffected,
            document: document,
            firstSeen: new DateTimeOffset(2025, 10, 10, 12, 0, 0, TimeSpan.Zero),
            lastSeen: new DateTimeOffset(2025, 10, 11, 12, 0, 0, TimeSpan.Zero),
            justification: VexJustification.ComponentNotPresent,
            detail: "Package not shipped in this channel.",
            confidence: new VexConfidence("high", 0.95, "policy/default"),
            additionalMetadata: ImmutableDictionary<string, string>.Empty
                .Add("source", "csaf")
                .Add("revision", "2024-09-15"));

        var json = VexCanonicalJsonSerializer.Serialize(claim);

        Assert.Equal(
            "{\"vulnerabilityId\":\"CVE-2025-12345\",\"providerId\":\"redhat\",\"product\":{\"key\":\"pkg:redhat/demo\",\"name\":\"Demo App\",\"version\":\"1.2.3\",\"purl\":\"pkg:rpm/redhat/demo@1.2.3\",\"cpe\":\"cpe:2.3:a:redhat:demo:1.2.3\",\"componentIdentifiers\":[\"componentA\",\"componentB\"]},\"status\":\"not_affected\",\"justification\":\"component_not_present\",\"detail\":\"Package not shipped in this channel.\",\"document\":{\"format\":\"csaf\",\"digest\":\"sha256:6d5a\",\"sourceUri\":\"https://example.org/vex/csaf.json\",\"revision\":\"2024-09-15\",\"signature\":{\"type\":\"pgp\",\"subject\":\"CN=Red Hat\",\"issuer\":\"CN=Red Hat Root\",\"keyId\":\"0xABCD\",\"verifiedAt\":\"2025-10-14T09:30:00+00:00\",\"transparencyLogReference\":null}},\"firstSeen\":\"2025-10-10T12:00:00+00:00\",\"lastSeen\":\"2025-10-11T12:00:00+00:00\",\"confidence\":{\"level\":\"high\",\"score\":0.95,\"method\":\"policy/default\"},\"additionalMetadata\":{\"revision\":\"2024-09-15\",\"source\":\"csaf\"}}",
            json);
    }

    [Fact]
    public void QuerySignature_FromFilters_SortsAndNormalizesKeys()
    {
        var signature = VexQuerySignature.FromFilters(new[]
        {
            new KeyValuePair<string, string>(" provider ", " redhat "),
            new KeyValuePair<string, string>("vulnId", "CVE-2025-12345"),
            new KeyValuePair<string, string>("provider", "canonical"),
        });

        Assert.Equal("provider=canonical&provider=redhat&vulnId=CVE-2025-12345", signature.Value);
    }

    [Fact]
    public void SerializeExportManifest_OrdersArraysAndNestedObjects()
    {
        var manifest = new VexExportManifest(
            exportId: "export/2025/10/15/1",
            querySignature: new VexQuerySignature("provider=redhat&format=consensus"),
            format: VexExportFormat.OpenVex,
            createdAt: new DateTimeOffset(2025, 10, 15, 8, 45, 0, TimeSpan.Zero),
            artifact: new VexContentAddress("sha256", "abcd1234"),
            claimCount: 42,
            sourceProviders: new[] { "cisco", "redhat", "redhat" },
            fromCache: true,
            consensusRevision: "rev-7",
            attestation: new VexAttestationMetadata(
                predicateType: "https://in-toto.io/Statement/v0.1",
                rekor: new VexRekorReference("v2", "rekor://uuid/1234", "17", new Uri("https://rekor.example/log/17")),
                envelopeDigest: "sha256:deadbeef",
                signedAt: new DateTimeOffset(2025, 10, 15, 8, 46, 0, TimeSpan.Zero)),
            sizeBytes: 4096);

        var json = VexCanonicalJsonSerializer.SerializeIndented(manifest);

        const string expected = """
        {
          "exportId": "export/2025/10/15/1",
          "querySignature": {
            "value": "provider=redhat&format=consensus"
          },
          "format": "openvex",
          "createdAt": "2025-10-15T08:45:00+00:00",
          "artifact": {
            "algorithm": "sha256",
            "digest": "abcd1234"
          },
          "claimCount": 42,
          "fromCache": true,
          "sourceProviders": [
            "cisco",
            "redhat"
          ],
          "consensusRevision": "rev-7",
          "attestation": {
            "predicateType": "https://in-toto.io/Statement/v0.1",
            "rekor": {
              "apiVersion": "v2",
              "location": "rekor://uuid/1234",
              "logIndex": "17",
              "inclusionProofUri": "https://rekor.example/log/17"
            },
            "envelopeDigest": "sha256:deadbeef",
            "signedAt": "2025-10-15T08:46:00+00:00"
          },
          "sizeBytes": 4096
        }
        """;

        Assert.Equal(expected.ReplaceLineEndings(), json.ReplaceLineEndings());
    }
}
