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

namespace StellaOps.Feedser.Storage.Mongo.Documents;

public sealed class DocumentStore : IDocumentStore
{
    private readonly IMongoCollection<DocumentDocument> _collection;
    private readonly ILogger<DocumentStore> _logger;

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

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

        var document = DocumentDocumentExtensions.FromRecord(record);
        var filter = Builders<DocumentDocument>.Filter.Eq(x => x.SourceName, record.SourceName)
            & Builders<DocumentDocument>.Filter.Eq(x => x.Uri, record.Uri);

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

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

    public async Task<DocumentRecord?> FindBySourceAndUriAsync(string sourceName, string uri, CancellationToken cancellationToken)
    {
        ArgumentException.ThrowIfNullOrEmpty(sourceName);
        ArgumentException.ThrowIfNullOrEmpty(uri);

        var filter = Builders<DocumentDocument>.Filter.Eq(x => x.SourceName, sourceName)
            & Builders<DocumentDocument>.Filter.Eq(x => x.Uri, uri);

        var document = await _collection.Find(filter).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
        return document?.ToRecord();
    }

    public async Task<DocumentRecord?> FindAsync(Guid id, CancellationToken cancellationToken)
    {
        var idValue = id.ToString();
        var document = await _collection.Find(x => x.Id == idValue).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
        return document?.ToRecord();
    }

    public async Task<bool> UpdateStatusAsync(Guid id, string status, CancellationToken cancellationToken)
    {
        ArgumentException.ThrowIfNullOrEmpty(status);

        var update = Builders<DocumentDocument>.Update
            .Set(x => x.Status, status)
            .Set(x => x.LastModified, DateTime.UtcNow);

        var idValue = id.ToString();
        var result = await _collection.UpdateOneAsync(x => x.Id == idValue, update, cancellationToken: cancellationToken).ConfigureAwait(false);
        return result.MatchedCount > 0;
    }
}
