using MediatR;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StorageService.Domain.AggregatesModel.MultipartUploadAggregate;
using StorageService.Domain.AggregatesModel.QuotaAggregate;
using StorageService.Infrastructure.Configuration;
using StorageService.Infrastructure.Persistence;
using StorageService.Infrastructure.Storage;

namespace StorageService.API.Application.Commands.MultipartUpload;

/// <summary>
/// EN: Handler for InitiateMultipartUploadCommand.
/// VI: Handler cho InitiateMultipartUploadCommand.
/// </summary>
public class InitiateMultipartUploadCommandHandler 
    : IRequestHandler<InitiateMultipartUploadCommand, InitiateMultipartUploadResult>
{
    private const int DefaultChunkSizeBytes = 5 * 1024 * 1024; // 5MB
    private const int MinChunkSizeBytes = 5 * 1024 * 1024; // 5MB (MinIO minimum)
    private const int MaxChunkSizeBytes = 100 * 1024 * 1024; // 100MB
    
    private readonly IQuotaRepository _quotaRepository;
    private readonly StorageServiceContext _context;
    private readonly IStorageProviderFactory _storageProviderFactory;
    private readonly StorageSettings _settings;
    private readonly ILogger<InitiateMultipartUploadCommandHandler> _logger;

    public InitiateMultipartUploadCommandHandler(
        IQuotaRepository quotaRepository,
        StorageServiceContext context,
        IStorageProviderFactory storageProviderFactory,
        IOptions<StorageSettings> settings,
        ILogger<InitiateMultipartUploadCommandHandler> logger)
    {
        _quotaRepository = quotaRepository;
        _context = context;
        _storageProviderFactory = storageProviderFactory;
        _settings = settings.Value;
        _logger = logger;
    }

    public async Task<InitiateMultipartUploadResult> Handle(
        InitiateMultipartUploadCommand request, 
        CancellationToken cancellationToken)
    {
        try
        {
            _logger.LogInformation(
                "Initiating multipart upload for user {UserId}, file: {FileName}, size: {Size} bytes",
                request.UserId, request.FileName, request.FileSizeBytes);

            // EN: Validate file size / VI: Kiểm tra kích thước file
            if (request.FileSizeBytes <= 0)
            {
                return Fail("File size must be greater than 0");
            }

            // EN: Check user quota / VI: Kiểm tra quota của user
            var quota = await _quotaRepository.GetOrCreateAsync(request.UserId, cancellationToken);
            if (!quota.CanUpload(request.FileSizeBytes))
            {
                _logger.LogWarning(
                    "Quota exceeded for user {UserId}. Used: {Used}/{Max}",
                    request.UserId, quota.UsedStorageBytes, quota.MaxStorageBytes);
                    
                return Fail("Storage quota exceeded. Please upgrade your plan or delete some files.");
            }

            // EN: Determine chunk size / VI: Xác định chunk size
            var chunkSizeBytes = request.ChunkSizeBytes ?? DefaultChunkSizeBytes;
            if (chunkSizeBytes < MinChunkSizeBytes || chunkSizeBytes > MaxChunkSizeBytes)
            {
                chunkSizeBytes = DefaultChunkSizeBytes;
                _logger.LogWarning(
                    "Invalid chunk size {Size}, using default {Default}",
                    request.ChunkSizeBytes, DefaultChunkSizeBytes);
            }

            // EN: Generate object key / VI: Tạo object key
            var objectKey = GenerateObjectKey(request.UserId, request.FileName, request.AccessLevel);
            var bucketName = _settings.DefaultBucket;

            // EN: Initiate multipart upload with storage provider / VI: Khởi tạo multipart upload với storage provider
            var provider = _storageProviderFactory.GetProvider();
            await provider.EnsureBucketExistsAsync(bucketName, cancellationToken);
            
            var providerUploadId = await provider.InitiateMultipartUploadAsync(
                bucketName,
                objectKey,
                request.ContentType,
                cancellationToken);

            if (string.IsNullOrEmpty(providerUploadId))
            {
                _logger.LogError("Provider failed to return upload ID");
                return Fail("Failed to initiate multipart upload");
            }

            // EN: Create domain entity / VI: Tạo domain entity
            var multipartUpload = new Domain.AggregatesModel.MultipartUploadAggregate.MultipartUpload(
                userId: request.UserId,
                fileName: request.FileName,
                totalSizeBytes: request.FileSizeBytes,
                chunkSizeBytes: chunkSizeBytes,
                providerUploadId: providerUploadId,
                bucketName: bucketName,
                objectKey: objectKey,
                contentType: request.ContentType,
                expirationHours: 24
            );

            // EN: Save to database / VI: Lưu vào database
            await _context.MultipartUploads.AddAsync(multipartUpload, cancellationToken);
            await _context.SaveEntitiesAsync(cancellationToken);

            _logger.LogInformation(
                "Multipart upload initiated. UploadId: {UploadId}, ProviderUploadId: {ProviderUploadId}, TotalChunks: {TotalChunks}",
                multipartUpload.Id, providerUploadId, multipartUpload.TotalChunks);

            return new InitiateMultipartUploadResult(
                Success: true,
                UploadId: multipartUpload.Id,
                ProviderUploadId: providerUploadId,
                ObjectKey: objectKey,
                BucketName: bucketName,
                TotalChunks: multipartUpload.TotalChunks,
                ChunkSizeBytes: chunkSizeBytes,
                ExpiresAt: multipartUpload.ExpiresAt,
                Error: null
            );
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error initiating multipart upload for user {UserId}", request.UserId);
            return Fail("An error occurred while initiating multipart upload.");
        }
    }

    private static string GenerateObjectKey(string userId, string fileName, Domain.AggregatesModel.FileAggregate.FileAccessLevel accessLevel)
    {
        var prefix = accessLevel switch
        {
            Domain.AggregatesModel.FileAggregate.FileAccessLevel.Public => "public",
            Domain.AggregatesModel.FileAggregate.FileAccessLevel.Shared => "shared",
            _ => "private"
        };

        var date = DateTime.UtcNow.ToString("yyyyMMdd");
        var fileId = Guid.NewGuid().ToString("N")[..8];
        var sanitizedName = SanitizeFileName(fileName);

        return $"{prefix}/{userId}/{date}/{fileId}_{sanitizedName}";
    }

    private static string SanitizeFileName(string fileName)
    {
        var invalidChars = Path.GetInvalidFileNameChars();
        var sanitized = string.Join("_", fileName.Split(invalidChars, StringSplitOptions.RemoveEmptyEntries));
        
        if (sanitized.Length > 100)
        {
            var extension = Path.GetExtension(sanitized);
            var nameWithoutExt = Path.GetFileNameWithoutExtension(sanitized);
            sanitized = nameWithoutExt[..Math.Min(nameWithoutExt.Length, 90)] + extension;
        }

        return sanitized;
    }

    private static InitiateMultipartUploadResult Fail(string error) =>
        new(false, null, null, null, null, null, null, null, error);
}
