using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using StellaOps.Authority.Storage.Mongo.Documents;

namespace StellaOps.Authority.Storage.Mongo.Stores;

internal sealed class AuthorityRevocationExportStateStore : IAuthorityRevocationExportStateStore
{
    private const string StateId = "state";

    private readonly IMongoCollection<AuthorityRevocationExportStateDocument> collection;
    private readonly ILogger<AuthorityRevocationExportStateStore> logger;

    public AuthorityRevocationExportStateStore(
        IMongoCollection<AuthorityRevocationExportStateDocument> collection,
        ILogger<AuthorityRevocationExportStateStore> logger)
    {
        this.collection = collection ?? throw new ArgumentNullException(nameof(collection));
        this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async ValueTask<AuthorityRevocationExportStateDocument?> GetAsync(CancellationToken cancellationToken)
    {
        var filter = Builders<AuthorityRevocationExportStateDocument>.Filter.Eq(d => d.Id, StateId);
        return await collection.Find(filter).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
    }

    public async ValueTask<AuthorityRevocationExportStateDocument> UpdateAsync(
        long expectedSequence,
        long newSequence,
        string bundleId,
        DateTimeOffset issuedAt,
        CancellationToken cancellationToken)
    {
        if (newSequence <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(newSequence), "Sequence must be positive.");
        }

        var filter = Builders<AuthorityRevocationExportStateDocument>.Filter.Eq(d => d.Id, StateId);

        if (expectedSequence > 0)
        {
            filter &= Builders<AuthorityRevocationExportStateDocument>.Filter.Eq(d => d.Sequence, expectedSequence);
        }
        else
        {
            filter &= Builders<AuthorityRevocationExportStateDocument>.Filter.Or(
                Builders<AuthorityRevocationExportStateDocument>.Filter.Exists(d => d.Sequence, false),
                Builders<AuthorityRevocationExportStateDocument>.Filter.Eq(d => d.Sequence, 0));
        }

        var update = Builders<AuthorityRevocationExportStateDocument>.Update
            .Set(d => d.Sequence, newSequence)
            .Set(d => d.LastBundleId, bundleId)
            .Set(d => d.LastIssuedAt, issuedAt);

        var options = new FindOneAndUpdateOptions<AuthorityRevocationExportStateDocument>
        {
            IsUpsert = expectedSequence == 0,
            ReturnDocument = ReturnDocument.After
        };

        try
        {
            var result = await collection.FindOneAndUpdateAsync(filter, update, options, cancellationToken).ConfigureAwait(false);
            if (result is null)
            {
                throw new InvalidOperationException("Revocation export state update conflict.");
            }

            logger.LogDebug("Updated revocation export state to sequence {Sequence}.", result.Sequence);
            return result;
        }
        catch (MongoCommandException ex) when (string.Equals(ex.CodeName, "DuplicateKey", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Revocation export state update conflict due to concurrent writer.", ex);
        }
    }
}
