using Microsoft.Extensions.Logging;
using MongoDB.Driver;

namespace StellaOps.Feedser.Storage.Mongo.Dtos;

public sealed class DtoStore : IDtoStore
{
    private readonly IMongoCollection<DtoDocument> _collection;
    private readonly ILogger<DtoStore> _logger;

    public DtoStore(IMongoDatabase database, ILogger<DtoStore> logger)
    {
        _collection = (database ?? throw new ArgumentNullException(nameof(database)))
            .GetCollection<DtoDocument>(MongoStorageDefaults.Collections.Dto);
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task<DtoRecord> UpsertAsync(DtoRecord record, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(record);

        var document = DtoDocumentExtensions.FromRecord(record);
        var documentId = record.DocumentId.ToString();
        var filter = Builders<DtoDocument>.Filter.Eq(x => x.DocumentId, documentId)
            & Builders<DtoDocument>.Filter.Eq(x => x.SourceName, record.SourceName);

        var options = new FindOneAndReplaceOptions<DtoDocument>
        {
            IsUpsert = true,
            ReturnDocument = ReturnDocument.After,
        };

        var replaced = await _collection.FindOneAndReplaceAsync(filter, document, options, cancellationToken).ConfigureAwait(false);
        _logger.LogDebug("Upserted DTO for {Source}/{DocumentId}", record.SourceName, record.DocumentId);
        return (replaced ?? document).ToRecord();
    }

    public async Task<DtoRecord?> FindByDocumentIdAsync(Guid documentId, CancellationToken cancellationToken)
    {
        var documentIdValue = documentId.ToString();
        var document = await _collection.Find(x => x.DocumentId == documentIdValue)
            .FirstOrDefaultAsync(cancellationToken)
            .ConfigureAwait(false);
        return document?.ToRecord();
    }

    public async Task<IReadOnlyList<DtoRecord>> GetBySourceAsync(string sourceName, int limit, CancellationToken cancellationToken)
    {
        var cursor = await _collection.Find(x => x.SourceName == sourceName)
            .SortByDescending(x => x.ValidatedAt)
            .Limit(limit)
            .ToListAsync(cancellationToken)
            .ConfigureAwait(false);

        return cursor.Select(static x => x.ToRecord()).ToArray();
    }
}
