using Amazon.S3;
using Amazon.S3.Model;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace StellaOps.Scanner.Storage.ObjectStore;

public sealed class S3ArtifactObjectStore : IArtifactObjectStore
{
    private readonly IAmazonS3 _s3;
    private readonly ObjectStoreOptions _options;
    private readonly ILogger<S3ArtifactObjectStore> _logger;

    public S3ArtifactObjectStore(IAmazonS3 s3, IOptions<ScannerStorageOptions> options, ILogger<S3ArtifactObjectStore> logger)
    {
        _s3 = s3 ?? throw new ArgumentNullException(nameof(s3));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _options = (options ?? throw new ArgumentNullException(nameof(options))).Value.ObjectStore;
    }

    public async Task PutAsync(ArtifactObjectDescriptor descriptor, Stream content, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(descriptor);
        ArgumentNullException.ThrowIfNull(content);

        var request = new PutObjectRequest
        {
            BucketName = descriptor.Bucket,
            Key = descriptor.Key,
            InputStream = content,
            AutoCloseStream = false,
        };

        if (descriptor.Immutable && _options.EnableObjectLock)
        {
            request.ObjectLockMode = ObjectLockMode.Compliance;
            if (descriptor.RetainFor is { } retention && retention > TimeSpan.Zero)
            {
                request.ObjectLockRetainUntilDate = DateTime.UtcNow + retention;
            }
            else if (_options.ComplianceRetention is { } defaultRetention && defaultRetention > TimeSpan.Zero)
            {
                request.ObjectLockRetainUntilDate = DateTime.UtcNow + defaultRetention;
            }
        }

        await _s3.PutObjectAsync(request, cancellationToken).ConfigureAwait(false);
        _logger.LogDebug("Uploaded scanner object {Bucket}/{Key}", descriptor.Bucket, descriptor.Key);
    }

    public async Task<Stream?> GetAsync(ArtifactObjectDescriptor descriptor, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(descriptor);
        try
        {
            var response = await _s3.GetObjectAsync(descriptor.Bucket, descriptor.Key, cancellationToken).ConfigureAwait(false);
            var buffer = new MemoryStream();
            await response.ResponseStream.CopyToAsync(buffer, cancellationToken).ConfigureAwait(false);
            buffer.Position = 0;
            return buffer;
        }
        catch (AmazonS3Exception ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
        {
            _logger.LogDebug("Scanner object {Bucket}/{Key} not found", descriptor.Bucket, descriptor.Key);
            return null;
        }
    }

    public async Task DeleteAsync(ArtifactObjectDescriptor descriptor, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(descriptor);
        await _s3.DeleteObjectAsync(descriptor.Bucket, descriptor.Key, cancellationToken).ConfigureAwait(false);
        _logger.LogDebug("Deleted scanner object {Bucket}/{Key}", descriptor.Bucket, descriptor.Key);
    }
}
