using System.Globalization;
using Mongo2Go;
using MongoDB.Bson;
using MongoDB.Driver;
using StellaOps.Vexer.Core;

namespace StellaOps.Vexer.Storage.Mongo.Tests;

public sealed class MongoVexStoreMappingTests : IAsyncLifetime
{
    private readonly MongoDbRunner _runner;
    private readonly IMongoDatabase _database;

    public MongoVexStoreMappingTests()
    {
        _runner = MongoDbRunner.Start();
        var client = new MongoClient(_runner.ConnectionString);
        _database = client.GetDatabase("vexer-storage-mapping-tests");
        VexMongoMappingRegistry.Register();
    }

    [Fact]
    public async Task ProviderStore_RoundTrips_WithExtraFields()
    {
        var providers = _database.GetCollection<BsonDocument>(VexMongoCollectionNames.Providers);
        var providerId = "red-hat";

        var document = new BsonDocument
        {
            { "_id", providerId },
            { "DisplayName", "Red Hat CSAF" },
            { "Kind", "vendor" },
            { "BaseUris", new BsonArray { "https://example.com/csaf" } },
            {
                "Discovery",
                new BsonDocument
                {
                    { "WellKnownMetadata", "https://example.com/.well-known/csaf" },
                    { "RolIeService", "https://example.com/service/rolie" },
                    { "UnsupportedField", "ignored" },
                }
            },
            {
                "Trust",
                new BsonDocument
                {
                    { "Weight", 0.75 },
                    {
                        "Cosign",
                        new BsonDocument
                        {
                            { "Issuer", "issuer@example.com" },
                            { "IdentityPattern", "spiffe://example/*" },
                            { "Unexpected", true },
                        }
                    },
                    { "PgpFingerprints", new BsonArray { "ABCDEF1234567890" } },
                    { "AnotherIgnoredField", 123 },
                }
            },
            { "Enabled", true },
            { "UnexpectedRoot", new BsonDocument { { "flag", true } } },
        };

        await providers.InsertOneAsync(document);

        var store = new MongoVexProviderStore(_database);
        var result = await store.FindAsync(providerId, CancellationToken.None);

        Assert.NotNull(result);
        Assert.Equal(providerId, result!.Id);
        Assert.Equal("Red Hat CSAF", result.DisplayName);
        Assert.Equal(VexProviderKind.Vendor, result.Kind);
        Assert.Single(result.BaseUris);
        Assert.Equal("https://example.com/csaf", result.BaseUris[0].ToString());
        Assert.Equal("https://example.com/.well-known/csaf", result.Discovery.WellKnownMetadata?.ToString());
        Assert.Equal("https://example.com/service/rolie", result.Discovery.RolIeService?.ToString());
        Assert.Equal(0.75, result.Trust.Weight);
        Assert.NotNull(result.Trust.Cosign);
        Assert.Equal("issuer@example.com", result.Trust.Cosign!.Issuer);
        Assert.Equal("spiffe://example/*", result.Trust.Cosign!.IdentityPattern);
        Assert.Contains("ABCDEF1234567890", result.Trust.PgpFingerprints);
        Assert.True(result.Enabled);
    }

    [Fact]
    public async Task ConsensusStore_IgnoresUnknownFields()
    {
        var consensus = _database.GetCollection<BsonDocument>(VexMongoCollectionNames.Consensus);
        var vulnerabilityId = "CVE-2025-12345";
        var productKey = "pkg:maven/org.example/app@1.2.3";
        var consensusId = string.Format(CultureInfo.InvariantCulture, "{0}|{1}", vulnerabilityId.Trim(), productKey.Trim());

        var document = new BsonDocument
        {
            { "_id", consensusId },
            { "VulnerabilityId", vulnerabilityId },
            {
                "Product",
                new BsonDocument
                {
                    { "Key", productKey },
                    { "Name", "Example App" },
                    { "Version", "1.2.3" },
                    { "Purl", productKey },
                    { "Extra", "ignored" },
                }
            },
            { "Status", "notaffected" },
            { "CalculatedAt", DateTime.UtcNow },
            {
                "Sources",
                new BsonArray
                {
                    new BsonDocument
                    {
                        { "ProviderId", "red-hat" },
                        { "Status", "notaffected" },
                        { "DocumentDigest", "sha256:123" },
                        { "Weight", 0.9 },
                        { "Justification", "componentnotpresent" },
                        { "Detail", "Vendor statement" },
                        {
                            "Confidence",
                            new BsonDocument
                            {
                                { "Level", "high" },
                                { "Score", 0.7 },
                                { "Method", "review" },
                                { "Unexpected", "ignored" },
                            }
                        },
                        { "UnknownField", true },
                    },
                }
            },
            {
                "Conflicts",
                new BsonArray
                {
                    new BsonDocument
                    {
                        { "ProviderId", "cisco" },
                        { "Status", "affected" },
                        { "DocumentDigest", "sha256:999" },
                        { "Justification", "requiresconfiguration" },
                        { "Detail", "Different guidance" },
                        { "Reason", "policy_override" },
                        { "Other", 1 },
                    },
                }
            },
            { "PolicyVersion", "2025.10" },
            { "PolicyRevisionId", "rev-1" },
            { "PolicyDigest", "sha256:abc" },
            { "Summary", "Vendor confirms not affected." },
            { "Unexpected", new BsonDocument { { "foo", "bar" } } },
        };

        await consensus.InsertOneAsync(document);

        var store = new MongoVexConsensusStore(_database);
        var result = await store.FindAsync(vulnerabilityId, productKey, CancellationToken.None);

        Assert.NotNull(result);
        Assert.Equal(vulnerabilityId, result!.VulnerabilityId);
        Assert.Equal(productKey, result.Product.Key);
        Assert.Equal("Example App", result.Product.Name);
        Assert.Equal(VexConsensusStatus.NotAffected, result.Status);
        Assert.Single(result.Sources);
        var source = result.Sources[0];
        Assert.Equal("red-hat", source.ProviderId);
        Assert.Equal(VexClaimStatus.NotAffected, source.Status);
        Assert.Equal("sha256:123", source.DocumentDigest);
        Assert.Equal(0.9, source.Weight);
        Assert.Equal(VexJustification.ComponentNotPresent, source.Justification);
        Assert.NotNull(source.Confidence);
        Assert.Equal("high", source.Confidence!.Level);
        Assert.Equal(0.7, source.Confidence!.Score);
        Assert.Equal("review", source.Confidence!.Method);
        Assert.Single(result.Conflicts);
        var conflict = result.Conflicts[0];
        Assert.Equal("cisco", conflict.ProviderId);
        Assert.Equal(VexClaimStatus.Affected, conflict.Status);
        Assert.Equal(VexJustification.RequiresConfiguration, conflict.Justification);
        Assert.Equal("policy_override", conflict.Reason);
        Assert.Equal("Vendor confirms not affected.", result.Summary);
        Assert.Equal("2025.10", result.PolicyVersion);
    }

    [Fact]
    public async Task CacheIndex_RoundTripsGridFsMetadata()
    {
        var gridObjectId = ObjectId.GenerateNewId().ToString();
        var index = new MongoVexCacheIndex(_database);
        var signature = new VexQuerySignature("format=csaf|vendor=redhat");
        var now = DateTimeOffset.UtcNow;
        var expires = now.AddHours(12);
        var entry = new VexCacheEntry(
            signature,
            VexExportFormat.Csaf,
            new VexContentAddress("sha256", "abcdef123456"),
            now,
            sizeBytes: 1024,
            manifestId: "manifest-001",
            gridFsObjectId: gridObjectId,
            expiresAt: expires);

        await index.SaveAsync(entry, CancellationToken.None);

        var cacheId = string.Format(
            CultureInfo.InvariantCulture,
            "{0}|{1}",
            signature.Value,
            entry.Format.ToString().ToLowerInvariant());

        var cache = _database.GetCollection<BsonDocument>(VexMongoCollectionNames.Cache);
        var filter = Builders<BsonDocument>.Filter.Eq("_id", cacheId);
        var update = Builders<BsonDocument>.Update.Set("UnexpectedField", true);
        await cache.UpdateOneAsync(filter, update);

        var roundTrip = await index.FindAsync(signature, VexExportFormat.Csaf, CancellationToken.None);

        Assert.NotNull(roundTrip);
        Assert.Equal(entry.QuerySignature.Value, roundTrip!.QuerySignature.Value);
        Assert.Equal(entry.Format, roundTrip.Format);
        Assert.Equal(entry.Artifact.Digest, roundTrip.Artifact.Digest);
        Assert.Equal(entry.ManifestId, roundTrip.ManifestId);
        Assert.Equal(entry.GridFsObjectId, roundTrip.GridFsObjectId);
        Assert.Equal(entry.SizeBytes, roundTrip.SizeBytes);
        Assert.NotNull(roundTrip.ExpiresAt);
        Assert.Equal(expires.ToUnixTimeMilliseconds(), roundTrip.ExpiresAt!.Value.ToUnixTimeMilliseconds());
    }

    public Task InitializeAsync() => Task.CompletedTask;

    public Task DisposeAsync()
    {
        _runner.Dispose();
        return Task.CompletedTask;
    }
}
