using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;

namespace StellaOps.Vexer.Storage.Mongo;

internal sealed class MongoVexCacheMaintenance : IVexCacheMaintenance
{
    private readonly IMongoCollection<VexCacheEntryRecord> _cache;
    private readonly IMongoCollection<VexExportManifestRecord> _exports;
    private readonly ILogger<MongoVexCacheMaintenance> _logger;

    public MongoVexCacheMaintenance(
        IMongoDatabase database,
        ILogger<MongoVexCacheMaintenance> logger)
    {
        ArgumentNullException.ThrowIfNull(database);
        ArgumentNullException.ThrowIfNull(logger);

        VexMongoMappingRegistry.Register();

        _cache = database.GetCollection<VexCacheEntryRecord>(VexMongoCollectionNames.Cache);
        _exports = database.GetCollection<VexExportManifestRecord>(VexMongoCollectionNames.Exports);
        _logger = logger;
    }

    public async ValueTask<int> RemoveExpiredAsync(DateTimeOffset asOf, CancellationToken cancellationToken)
    {
        var cutoff = asOf.UtcDateTime;
        var filter = Builders<VexCacheEntryRecord>.Filter.Lt(x => x.ExpiresAt, cutoff);
        var result = await _cache.DeleteManyAsync(filter, cancellationToken).ConfigureAwait(false);

        var removed = (int)result.DeletedCount;
        if (removed > 0)
        {
            _logger.LogInformation("Pruned {Count} expired VEX export cache entries (cutoff {Cutoff})", removed, cutoff);
        }

        return removed;
    }

    public async ValueTask<int> RemoveMissingManifestReferencesAsync(CancellationToken cancellationToken)
    {
        var filter = Builders<VexCacheEntryRecord>.Filter.Ne(x => x.ManifestId, null);
        var cursor = await _cache.Find(filter).ToListAsync(cancellationToken).ConfigureAwait(false);

        if (cursor.Count == 0)
        {
            return 0;
        }

        var danglingIds = new List<string>(cursor.Count);
        foreach (var entry in cursor)
        {
            if (string.IsNullOrWhiteSpace(entry.ManifestId))
            {
                continue;
            }

            var manifestExists = await _exports
                .Find(Builders<VexExportManifestRecord>.Filter.Eq(x => x.Id, entry.ManifestId))
                .Limit(1)
                .AnyAsync(cancellationToken)
                .ConfigureAwait(false);

            if (!manifestExists)
            {
                danglingIds.Add(entry.Id);
            }
        }

        if (danglingIds.Count == 0)
        {
            return 0;
        }

        var danglingFilter = Builders<VexCacheEntryRecord>.Filter.In(x => x.Id, danglingIds);
        var result = await _cache.DeleteManyAsync(danglingFilter, cancellationToken).ConfigureAwait(false);

        var removed = (int)result.DeletedCount;
        _logger.LogWarning("Removed {Count} cache entries referencing missing export manifests.", removed);

        return removed;
    }
}
