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

namespace StellaOps.Vexer.Storage.Mongo.Migrations;

internal sealed class VexMongoMigrationRunner
{
    private readonly IMongoDatabase _database;
    private readonly IReadOnlyList<IVexMongoMigration> _migrations;
    private readonly ILogger<VexMongoMigrationRunner> _logger;

    public VexMongoMigrationRunner(
        IMongoDatabase database,
        IEnumerable<IVexMongoMigration> migrations,
        ILogger<VexMongoMigrationRunner> logger)
    {
        _database = database ?? throw new ArgumentNullException(nameof(database));
        _migrations = (migrations ?? throw new ArgumentNullException(nameof(migrations)))
            .OrderBy(migration => migration.Id, StringComparer.Ordinal)
            .ToArray();
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async ValueTask RunAsync(CancellationToken cancellationToken)
    {
        if (_migrations.Count == 0)
        {
            return;
        }

        var migrationsCollection = _database.GetCollection<VexMigrationRecord>(VexMongoCollectionNames.Migrations);
        await EnsureMigrationsIndexAsync(migrationsCollection, cancellationToken).ConfigureAwait(false);

        var applied = await LoadAppliedMigrationsAsync(migrationsCollection, cancellationToken).ConfigureAwait(false);

        foreach (var migration in _migrations)
        {
            if (applied.Contains(migration.Id))
            {
                continue;
            }

            _logger.LogInformation("Applying Vexer Mongo migration {MigrationId}", migration.Id);
            await migration.ExecuteAsync(_database, cancellationToken).ConfigureAwait(false);

            var record = new VexMigrationRecord(migration.Id, DateTimeOffset.UtcNow);
            await migrationsCollection.InsertOneAsync(record, cancellationToken: cancellationToken).ConfigureAwait(false);
            _logger.LogInformation("Completed Vexer Mongo migration {MigrationId}", migration.Id);
        }
    }

    private static ValueTask EnsureMigrationsIndexAsync(
        IMongoCollection<VexMigrationRecord> collection,
        CancellationToken cancellationToken)
    {
        // default _id index already enforces uniqueness
        return ValueTask.CompletedTask;
    }

    private static async ValueTask<HashSet<string>> LoadAppliedMigrationsAsync(
        IMongoCollection<VexMigrationRecord> collection,
        CancellationToken cancellationToken)
    {
        var records = await collection.Find(FilterDefinition<VexMigrationRecord>.Empty)
            .ToListAsync(cancellationToken)
            .ConfigureAwait(false);

        return records.Select(static record => record.Id)
            .ToHashSet(StringComparer.Ordinal);
    }
}
