using System.Collections.Generic;
using Microsoft.Extensions.Logging.Abstractions;
using Mongo2Go;
using MongoDB.Bson;
using MongoDB.Driver;

namespace StellaOps.Vexer.Storage.Mongo.Tests;

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

    public MongoVexCacheMaintenanceTests()
    {
        _runner = MongoDbRunner.Start();
        var client = new MongoClient(_runner.ConnectionString);
        _database = client.GetDatabase("vex-cache-maintenance-tests");
        VexMongoMappingRegistry.Register();
    }

    [Fact]
    public async Task RemoveExpiredAsync_DeletesEntriesBeforeCutoff()
    {
        var collection = _database.GetCollection<VexCacheEntryRecord>(VexMongoCollectionNames.Cache);
        var now = DateTime.UtcNow;

        await collection.InsertManyAsync(new[]
        {
            new VexCacheEntryRecord
            {
                Id = "sig-1|json",
                QuerySignature = "sig-1",
                Format = "json",
                ArtifactAlgorithm = "sha256",
                ArtifactDigest = "deadbeef",
                CreatedAt = now.AddHours(-2),
                ExpiresAt = now.AddHours(-1),
            },
            new VexCacheEntryRecord
            {
                Id = "sig-2|json",
                QuerySignature = "sig-2",
                Format = "json",
                ArtifactAlgorithm = "sha256",
                ArtifactDigest = "cafebabe",
                CreatedAt = now,
                ExpiresAt = now.AddHours(1),
            },
        });

        var maintenance = new MongoVexCacheMaintenance(_database, NullLogger<MongoVexCacheMaintenance>.Instance);
        var removed = await maintenance.RemoveExpiredAsync(DateTimeOffset.UtcNow, CancellationToken.None);

        Assert.Equal(1, removed);

        var remaining = await collection.CountDocumentsAsync(FilterDefinition<VexCacheEntryRecord>.Empty);
        Assert.Equal(1, remaining);
    }

    [Fact]
    public async Task RemoveMissingManifestReferencesAsync_DropsDanglingEntries()
    {
        var cache = _database.GetCollection<VexCacheEntryRecord>(VexMongoCollectionNames.Cache);
        var exports = _database.GetCollection<VexExportManifestRecord>(VexMongoCollectionNames.Exports);

        await exports.InsertOneAsync(new VexExportManifestRecord
        {
            Id = "manifest-existing",
            QuerySignature = "sig-keep",
            Format = "json",
            CreatedAt = DateTime.UtcNow,
            ArtifactAlgorithm = "sha256",
            ArtifactDigest = "keep",
            ClaimCount = 1,
            SourceProviders = new List<string> { "vendor" },
        });

        await cache.InsertManyAsync(new[]
        {
            new VexCacheEntryRecord
            {
                Id = "sig-remove|json",
                QuerySignature = "sig-remove",
                Format = "json",
                ArtifactAlgorithm = "sha256",
                ArtifactDigest = "drop",
                CreatedAt = DateTime.UtcNow,
                ManifestId = "manifest-missing",
            },
            new VexCacheEntryRecord
            {
                Id = "sig-keep|json",
                QuerySignature = "sig-keep",
                Format = "json",
                ArtifactAlgorithm = "sha256",
                ArtifactDigest = "keep",
                CreatedAt = DateTime.UtcNow,
                ManifestId = "manifest-existing",
            },
        });

        var maintenance = new MongoVexCacheMaintenance(_database, NullLogger<MongoVexCacheMaintenance>.Instance);
        var removed = await maintenance.RemoveMissingManifestReferencesAsync(CancellationToken.None);

        Assert.Equal(1, removed);

        var remainingIds = await cache.Find(Builders<VexCacheEntryRecord>.Filter.Empty)
            .Project(x => x.Id)
            .ToListAsync();
        Assert.Single(remainingIds);
        Assert.Contains("sig-keep|json", remainingIds);
    }

    public Task InitializeAsync() => Task.CompletedTask;

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