using Amazon;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Minio;
using Minio.DataModel.Args;
using StorageService.Domain.AggregatesModel.FileAggregate;
using StorageService.Infrastructure.Configuration;

namespace StorageService.Infrastructure.Storage;

/// <summary>
/// EN: MinIO storage provider implementation using S3-compatible APIs.
/// VI: Implementation storage provider cho MinIO sử dụng S3-compatible APIs.
/// </summary>
public class MinioStorageProvider : IStorageProvider
{
    private readonly IMinioClient _minioClient;
    private readonly IAmazonS3 _s3Client;
    private readonly ILogger<MinioStorageProvider> _logger;
    private readonly MinioSettings _settings;

    public StorageProvider ProviderType => StorageProvider.MinIO;

    public MinioStorageProvider(
        IOptions<StorageSettings> settings,
        ILogger<MinioStorageProvider> logger)
    {
        _logger = logger;
        _settings = settings.Value.MinIO;

        // EN: Initialize MinIO client for normal operations
        // VI: Khởi tạo MinIO client cho các thao tác thông thường
        _minioClient = new MinioClient()
            .WithEndpoint(_settings.Endpoint)
            .WithCredentials(_settings.AccessKey, _settings.SecretKey)
            .WithSSL(_settings.UseSSL)
            .Build();

        // EN: Initialize S3 client for low-level multipart upload operations
        // VI: Khởi tạo S3 client cho các thao tác multipart upload low-level
        var s3Config = new AmazonS3Config
        {
            ServiceURL = $"{(_settings.UseSSL ? "https" : "http")}://{_settings.Endpoint}",
            ForcePathStyle = true, // EN: Required for MinIO / VI: Bắt buộc cho MinIO
            UseHttp = !_settings.UseSSL
        };

        var credentials = new BasicAWSCredentials(_settings.AccessKey, _settings.SecretKey);
        _s3Client = new AmazonS3Client(credentials, s3Config);

        _logger.LogInformation(
            "MinIO client initialized with endpoint: {Endpoint} (S3-compatible multipart enabled)",
            _settings.Endpoint);
    }

    /// <inheritdoc />
    public async Task<StorageResult> UploadAsync(
        string bucketName,
        string objectKey,
        Stream dataStream,
        string contentType,
        CancellationToken cancellationToken = default)
    {
        try
        {
            await EnsureBucketExistsAsync(bucketName, cancellationToken);

            var putArgs = new PutObjectArgs()
                .WithBucket(bucketName)
                .WithObject(objectKey)
                .WithStreamData(dataStream)
                .WithObjectSize(dataStream.Length)
                .WithContentType(contentType);

            var response = await _minioClient.PutObjectAsync(putArgs, cancellationToken);
            
            _logger.LogInformation("File uploaded to MinIO: {Bucket}/{ObjectKey}", bucketName, objectKey);
            
            return StorageResult.Ok(objectKey, dataStream.Length, response.Etag);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to upload file to MinIO: {Bucket}/{ObjectKey}", bucketName, objectKey);
            return StorageResult.Fail(ex.Message);
        }
    }

    /// <inheritdoc />
    public async Task<Stream?> DownloadAsync(
        string bucketName,
        string objectKey,
        CancellationToken cancellationToken = default)
    {
        try
        {
            var memoryStream = new MemoryStream();
            
            var getArgs = new GetObjectArgs()
                .WithBucket(bucketName)
                .WithObject(objectKey)
                .WithCallbackStream(stream => stream.CopyTo(memoryStream));

            await _minioClient.GetObjectAsync(getArgs, cancellationToken);
            
            memoryStream.Position = 0;
            return memoryStream;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to download file from MinIO: {Bucket}/{ObjectKey}", bucketName, objectKey);
            return null;
        }
    }

    /// <inheritdoc />
    public async Task<bool> DeleteAsync(
        string bucketName,
        string objectKey,
        CancellationToken cancellationToken = default)
    {
        try
        {
            var removeArgs = new RemoveObjectArgs()
                .WithBucket(bucketName)
                .WithObject(objectKey);

            await _minioClient.RemoveObjectAsync(removeArgs, cancellationToken);
            
            _logger.LogInformation("File deleted from MinIO: {Bucket}/{ObjectKey}", bucketName, objectKey);
            return true;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to delete file from MinIO: {Bucket}/{ObjectKey}", bucketName, objectKey);
            return false;
        }
    }

    /// <inheritdoc />
    public async Task<bool> ExistsAsync(
        string bucketName,
        string objectKey,
        CancellationToken cancellationToken = default)
    {
        try
        {
            var statArgs = new StatObjectArgs()
                .WithBucket(bucketName)
                .WithObject(objectKey);

            await _minioClient.StatObjectAsync(statArgs, cancellationToken);
            return true;
        }
        catch
        {
            return false;
        }
    }

    /// <inheritdoc />
    public async Task<string> GetPreSignedDownloadUrlAsync(
        string bucketName,
        string objectKey,
        int expirationSeconds = 3600,
        CancellationToken cancellationToken = default)
    {
        var presignedArgs = new PresignedGetObjectArgs()
            .WithBucket(bucketName)
            .WithObject(objectKey)
            .WithExpiry(expirationSeconds);

        return await _minioClient.PresignedGetObjectAsync(presignedArgs);
    }

    /// <inheritdoc />
    public async Task<string> GetPreSignedUploadUrlAsync(
        string bucketName,
        string objectKey,
        int expirationSeconds = 3600,
        CancellationToken cancellationToken = default)
    {
        var presignedArgs = new PresignedPutObjectArgs()
            .WithBucket(bucketName)
            .WithObject(objectKey)
            .WithExpiry(expirationSeconds);

        return await _minioClient.PresignedPutObjectAsync(presignedArgs);
    }

    /// <inheritdoc />
    public async Task EnsureBucketExistsAsync(
        string bucketName,
        CancellationToken cancellationToken = default)
    {
        var bucketExistsArgs = new BucketExistsArgs().WithBucket(bucketName);
        var exists = await _minioClient.BucketExistsAsync(bucketExistsArgs, cancellationToken);

        if (!exists)
        {
            var makeBucketArgs = new MakeBucketArgs().WithBucket(bucketName);
            await _minioClient.MakeBucketAsync(makeBucketArgs, cancellationToken);
            _logger.LogInformation("Created MinIO bucket: {Bucket}", bucketName);
        }
    }

    // ========== Multipart Upload Methods (S3-Compatible) ==========

    /// <inheritdoc />
    public async Task<string> InitiateMultipartUploadAsync(
        string bucketName,
        string objectKey,
        string contentType,
        CancellationToken cancellationToken = default)
    {
        try
        {
            _logger.LogInformation(
                "Initiating multipart upload for {Bucket}/{ObjectKey}",
                bucketName, objectKey);

            // EN: Ensure bucket exists / VI: Đảm bảo bucket tồn tại
            await EnsureBucketExistsAsync(bucketName, cancellationToken);

            // EN: Initiate multipart upload using S3 API / VI: Khởi tạo multipart upload sử dụng S3 API
            var request = new InitiateMultipartUploadRequest
            {
                BucketName = bucketName,
                Key = objectKey,
                ContentType = contentType
            };

            var response = await _s3Client.InitiateMultipartUploadAsync(request, cancellationToken);

            _logger.LogInformation(
                "Multipart upload initiated. UploadId: {UploadId}",
                response.UploadId);

            return response.UploadId;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,
                "Failed to initiate multipart upload for {Bucket}/{ObjectKey}",
                bucketName, objectKey);
            throw;
        }
    }

    /// <inheritdoc />
    public async Task<string> UploadPartAsync(
        string bucketName,
        string objectKey,
        string uploadId,
        int partNumber,
        Stream dataStream,
        CancellationToken cancellationToken = default)
    {
        try
        {
            _logger.LogDebug(
                "Uploading part {PartNumber} for {Bucket}/{ObjectKey}, UploadId: {UploadId}",
                partNumber, bucketName, objectKey, uploadId);

            // EN: Upload part using S3 API / VI: Upload part sử dụng S3 API
            var request = new UploadPartRequest
            {
                BucketName = bucketName,
                Key = objectKey,
                UploadId = uploadId,
                PartNumber = partNumber,
                InputStream = dataStream
            };

            var response = await _s3Client.UploadPartAsync(request, cancellationToken);

            _logger.LogInformation(
                "Part {PartNumber} uploaded. ETag: {ETag}",
                partNumber, response.ETag);

            return response.ETag;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,
                "Failed to upload part {PartNumber} for {Bucket}/{ObjectKey}",
                partNumber, bucketName, objectKey);
            throw;
        }
    }

    /// <inheritdoc />
    public async Task CompleteMultipartUploadAsync(
        string bucketName,
        string objectKey,
        string uploadId,
        List<PartETag> parts,
        CancellationToken cancellationToken = default)
    {
        try
        {
            _logger.LogInformation(
                "Completing multipart upload for {Bucket}/{ObjectKey}, UploadId: {UploadId}, Parts: {PartCount}",
                bucketName, objectKey, uploadId, parts.Count);

            // EN: Convert to S3 SDK format / VI: Chuyển đổi sang định dạng S3 SDK
            var s3Parts = parts
                .OrderBy(p => p.PartNumber)
                .Select(p => new Amazon.S3.Model.PartETag
                {
                    PartNumber = p.PartNumber,
                    ETag = p.ETag
                })
                .ToList();

            // EN: Complete multipart upload using S3 API / VI: Hoàn thành multipart upload sử dụng S3 API
            var request = new CompleteMultipartUploadRequest
            {
                BucketName = bucketName,
                Key = objectKey,
                UploadId = uploadId,
                PartETags = s3Parts
            };

            await _s3Client.CompleteMultipartUploadAsync(request, cancellationToken);

            _logger.LogInformation(
                "Multipart upload completed successfully for {Bucket}/{ObjectKey}",
                bucketName, objectKey);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,
                "Failed to complete multipart upload for {Bucket}/{ObjectKey}",
                bucketName, objectKey);
            throw;
        }
    }

    /// <inheritdoc />
    public async Task AbortMultipartUploadAsync(
        string bucketName,
        string objectKey,
        string uploadId,
        CancellationToken cancellationToken = default)
    {
        try
        {
            _logger.LogInformation(
                "Aborting multipart upload for {Bucket}/{ObjectKey}, UploadId: {UploadId}",
                bucketName, objectKey, uploadId);

            // EN: Abort multipart upload using S3 API / VI: Hủy multipart upload sử dụng S3 API
            var request = new AbortMultipartUploadRequest
            {
                BucketName = bucketName,
                Key = objectKey,
                UploadId = uploadId
            };

            await _s3Client.AbortMultipartUploadAsync(request, cancellationToken);

            _logger.LogInformation(
                "Multipart upload aborted for {Bucket}/{ObjectKey}",
                bucketName, objectKey);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,
                "Failed to abort multipart upload for {Bucket}/{ObjectKey}",
                bucketName, objectKey);
            throw;
        }
    }
}
