using Aliyun.OSS;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StorageService.Domain.AggregatesModel.FileAggregate;
using StorageService.Infrastructure.Configuration;
using OssPartETag = Aliyun.OSS.PartETag;
using System.Linq;

namespace StorageService.Infrastructure.Storage;

/// <summary>
/// EN: Aliyun OSS storage provider implementation.
/// VI: Implementation storage provider cho Aliyun OSS.
/// </summary>
public class AliyunOssStorageProvider : IStorageProvider
{
    private readonly IOss _ossClient;
    private readonly ILogger<AliyunOssStorageProvider> _logger;
    private readonly AliyunOssSettings _settings;

    public StorageProvider ProviderType => StorageProvider.AliyunOSS;

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

        _ossClient = new OssClient(
            _settings.Endpoint,
            _settings.AccessKeyId,
            _settings.AccessKeySecret);

        _logger.LogInformation("Aliyun OSS client initialized with endpoint: {Endpoint}", _settings.Endpoint);
    }

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

            var metadata = new ObjectMetadata { ContentType = contentType };
            var result = _ossClient.PutObject(bucketName, objectKey, dataStream, metadata);
            
            _logger.LogInformation("File uploaded to Aliyun OSS: {Bucket}/{ObjectKey}", bucketName, objectKey);
            
            return Task.FromResult(StorageResult.Ok(objectKey, dataStream.Length, result.ETag));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to upload file to Aliyun OSS: {Bucket}/{ObjectKey}", bucketName, objectKey);
            return Task.FromResult(StorageResult.Fail(ex.Message));
        }
    }

    /// <inheritdoc />
    public Task<Stream?> DownloadAsync(
        string bucketName,
        string objectKey,
        CancellationToken cancellationToken = default)
    {
        try
        {
            var ossObject = _ossClient.GetObject(bucketName, objectKey);
            var memoryStream = new MemoryStream();
            ossObject.Content.CopyTo(memoryStream);
            memoryStream.Position = 0;
            
            return Task.FromResult<Stream?>(memoryStream);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to download file from Aliyun OSS: {Bucket}/{ObjectKey}", bucketName, objectKey);
            return Task.FromResult<Stream?>(null);
        }
    }

    /// <inheritdoc />
    public Task<bool> DeleteAsync(
        string bucketName,
        string objectKey,
        CancellationToken cancellationToken = default)
    {
        try
        {
            _ossClient.DeleteObject(bucketName, objectKey);
            _logger.LogInformation("File deleted from Aliyun OSS: {Bucket}/{ObjectKey}", bucketName, objectKey);
            return Task.FromResult(true);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to delete file from Aliyun OSS: {Bucket}/{ObjectKey}", bucketName, objectKey);
            return Task.FromResult(false);
        }
    }

    /// <inheritdoc />
    public Task<bool> ExistsAsync(
        string bucketName,
        string objectKey,
        CancellationToken cancellationToken = default)
    {
        try
        {
            var exists = _ossClient.DoesObjectExist(bucketName, objectKey);
            return Task.FromResult(exists);
        }
        catch
        {
            return Task.FromResult(false);
        }
    }

    /// <inheritdoc />
    public Task<string> GetPreSignedDownloadUrlAsync(
        string bucketName,
        string objectKey,
        int expirationSeconds = 3600,
        CancellationToken cancellationToken = default)
    {
        var expiration = DateTime.UtcNow.AddSeconds(expirationSeconds);
        var uri = _ossClient.GeneratePresignedUri(bucketName, objectKey, expiration, SignHttpMethod.Get);
        return Task.FromResult(uri.ToString());
    }

    /// <inheritdoc />
    public Task<string> GetPreSignedUploadUrlAsync(
        string bucketName,
        string objectKey,
        int expirationSeconds = 3600,
        CancellationToken cancellationToken = default)
    {
        var expiration = DateTime.UtcNow.AddSeconds(expirationSeconds);
        var uri = _ossClient.GeneratePresignedUri(bucketName, objectKey, expiration, SignHttpMethod.Put);
        return Task.FromResult(uri.ToString());
    }

    /// <inheritdoc />
    public Task EnsureBucketExistsAsync(
        string bucketName,
        CancellationToken cancellationToken = default)
    {
        try
        {
            if (!_ossClient.DoesBucketExist(bucketName))
            {
                _ossClient.CreateBucket(bucketName);
                _logger.LogInformation("Created Aliyun OSS bucket: {Bucket}", bucketName);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to ensure bucket exists: {Bucket}", bucketName);
        }
        
        return Task.CompletedTask;
    }

    // ========== Multipart Upload Methods ==========

    /// <inheritdoc />
    public Task<string> InitiateMultipartUploadAsync(
        string bucketName,
        string objectKey,
        string contentType,
        CancellationToken cancellationToken = default)
    {
        try
        {
            EnsureBucketExistsAsync(bucketName, cancellationToken).Wait(cancellationToken);

            var request = new InitiateMultipartUploadRequest(bucketName, objectKey);
            // EN: Set ContentType via ObjectMetadata (SDK doesn't expose it as direct property)
            // VI: Đặt ContentType qua ObjectMetadata (SDK không expose nó như property trực tiếp)
            request.ObjectMetadata = new ObjectMetadata { ContentType = contentType };

            var response = _ossClient.InitiateMultipartUpload(request);
            _logger.LogInformation(
                "Initiated Aliyun OSS multipart upload: {Bucket}/{ObjectKey}, UploadId: {UploadId}",
                bucketName,
                objectKey,
                response.UploadId);

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

    /// <inheritdoc />
    public Task<string> UploadPartAsync(
        string bucketName,
        string objectKey,
        string uploadId,
        int partNumber,
        Stream dataStream,
        CancellationToken cancellationToken = default)
    {
        try
        {
            var request = new UploadPartRequest(bucketName, objectKey, uploadId)
            {
                InputStream = dataStream,
                PartSize = dataStream.Length,
                PartNumber = partNumber
            };

            var response = _ossClient.UploadPart(request);
            var etag = response.PartETag.ETag;

            _logger.LogInformation(
                "Uploaded Aliyun OSS multipart part: {Bucket}/{ObjectKey}, UploadId: {UploadId}, Part: {PartNumber}",
                bucketName,
                objectKey,
                uploadId,
                partNumber);

            return Task.FromResult(etag);
        }
        catch (Exception ex)
        {
            _logger.LogError(
                ex,
                "Failed to upload Aliyun OSS multipart part: {Bucket}/{ObjectKey}, UploadId: {UploadId}, Part: {PartNumber}",
                bucketName,
                objectKey,
                uploadId,
                partNumber);
            throw;
        }
    }

    /// <inheritdoc />
    public Task CompleteMultipartUploadAsync(
        string bucketName,
        string objectKey,
        string uploadId,
        List<PartETag> parts,
        CancellationToken cancellationToken = default)
    {
        try
        {
            var request = new CompleteMultipartUploadRequest(bucketName, objectKey, uploadId);
            var orderedParts = parts
                .OrderBy(part => part.PartNumber)
                .Select(part => new OssPartETag(part.PartNumber, part.ETag))
                .ToList();

            // EN: IList<PartETag> doesn't have AddRange — add items individually
            // VI: IList<PartETag> không có AddRange — thêm từng item
            foreach (var part in orderedParts)
            {
                request.PartETags.Add(part);
            }
            _ossClient.CompleteMultipartUpload(request);

            _logger.LogInformation(
                "Completed Aliyun OSS multipart upload: {Bucket}/{ObjectKey}, UploadId: {UploadId}",
                bucketName,
                objectKey,
                uploadId);
        }
        catch (Exception ex)
        {
            _logger.LogError(
                ex,
                "Failed to complete Aliyun OSS multipart upload: {Bucket}/{ObjectKey}, UploadId: {UploadId}",
                bucketName,
                objectKey,
                uploadId);
            throw;
        }

        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task AbortMultipartUploadAsync(
        string bucketName,
        string objectKey,
        string uploadId,
        CancellationToken cancellationToken = default)
    {
        try
        {
            var request = new AbortMultipartUploadRequest(bucketName, objectKey, uploadId);
            _ossClient.AbortMultipartUpload(request);

            _logger.LogInformation(
                "Aborted Aliyun OSS multipart upload: {Bucket}/{ObjectKey}, UploadId: {UploadId}",
                bucketName,
                objectKey,
                uploadId);
        }
        catch (Exception ex)
        {
            _logger.LogError(
                ex,
                "Failed to abort Aliyun OSS multipart upload: {Bucket}/{ObjectKey}, UploadId: {UploadId}",
                bucketName,
                objectKey,
                uploadId);
            throw;
        }

        return Task.CompletedTask;
    }
}
