using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.GridFS;

namespace StellaOps.Feedser.Source.Common.Fetch;

/// <summary>
/// Handles persistence of raw upstream documents in GridFS buckets for later parsing.
/// </summary>
public sealed class RawDocumentStorage
{
    private const string BucketName = "documents";

    private readonly IMongoDatabase _database;

    public RawDocumentStorage(IMongoDatabase database)
    {
        _database = database ?? throw new ArgumentNullException(nameof(database));
    }

    private GridFSBucket CreateBucket() => new(_database, new GridFSBucketOptions
    {
        BucketName = BucketName,
        WriteConcern = _database.Settings.WriteConcern,
        ReadConcern = _database.Settings.ReadConcern,
    });

    public Task<ObjectId> UploadAsync(
        string sourceName,
        string uri,
        byte[] content,
        string? contentType,
        CancellationToken cancellationToken)
        => UploadAsync(sourceName, uri, content, contentType, expiresAt: null, cancellationToken);

    public async Task<ObjectId> UploadAsync(
        string sourceName,
        string uri,
        byte[] content,
        string? contentType,
        DateTimeOffset? expiresAt,
        CancellationToken cancellationToken)
    {
        ArgumentException.ThrowIfNullOrEmpty(sourceName);
        ArgumentException.ThrowIfNullOrEmpty(uri);
        ArgumentNullException.ThrowIfNull(content);

        var bucket = CreateBucket();
        var filename = $"{sourceName}/{Guid.NewGuid():N}";
        var metadata = new BsonDocument
        {
            ["sourceName"] = sourceName,
            ["uri"] = uri,
        };

        if (!string.IsNullOrWhiteSpace(contentType))
        {
            metadata["contentType"] = contentType;
        }

        if (expiresAt.HasValue)
        {
            metadata["expiresAt"] = expiresAt.Value.UtcDateTime;
        }

        return await bucket.UploadFromBytesAsync(filename, content, new GridFSUploadOptions
        {
            Metadata = metadata,
        }, cancellationToken).ConfigureAwait(false);
    }

    public Task<byte[]> DownloadAsync(ObjectId id, CancellationToken cancellationToken)
    {
        var bucket = CreateBucket();
        return bucket.DownloadAsBytesAsync(id, cancellationToken: cancellationToken);
    }

    public async Task DeleteAsync(ObjectId id, CancellationToken cancellationToken)
    {
        var bucket = CreateBucket();
        try
        {
            await bucket.DeleteAsync(id, cancellationToken).ConfigureAwait(false);
        }
        catch (GridFSFileNotFoundException)
        {
            // Already removed; ignore.
        }
    }
}
