using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using StorageService.Infrastructure.Persistence;
using StorageService.Infrastructure.Storage;

namespace StorageService.API.Application.Commands.MultipartUpload;

/// <summary>
/// EN: Handler for UploadPartCommand.
/// VI: Handler cho UploadPartCommand.
/// </summary>
public class UploadPartCommandHandler : IRequestHandler<UploadPartCommand, UploadPartResult>
{
    private readonly StorageServiceContext _context;
    private readonly IStorageProviderFactory _storageProviderFactory;
    private readonly ILogger<UploadPartCommandHandler> _logger;

    public UploadPartCommandHandler(
        StorageServiceContext context,
        IStorageProviderFactory storageProviderFactory,
        ILogger<UploadPartCommandHandler> logger)
    {
        _context = context;
        _storageProviderFactory = storageProviderFactory;
        _logger = logger;
    }

    public async Task<UploadPartResult> Handle(
        UploadPartCommand request, 
        CancellationToken cancellationToken)
    {
        try
        {
            _logger.LogInformation(
                "Uploading part {PartNumber} for upload {UploadId}",
                request.PartNumber, request.UploadId);

            // EN: Get multipart upload / VI: Lấy multipart upload
            var upload = await _context.MultipartUploads
                .Include(u => u.Parts)
                .FirstOrDefaultAsync(u => u.Id == request.UploadId, cancellationToken);

            if (upload == null)
            {
                _logger.LogWarning("Multipart upload not found: {UploadId}", request.UploadId);
                return UploadPartResult.Fail("Multipart upload not found");
            }

            // EN: Verify ownership / VI: Xác minh quyền sở hữu
            if (upload.UserId != request.UserId)
            {
                _logger.LogWarning(
                    "User {UserId} attempted to upload part for upload owned by {OwnerId}",
                    request.UserId, upload.UserId);
                return UploadPartResult.Fail("Unauthorized");
            }

            // EN: Check if upload is in progress / VI: Kiểm tra upload đang progress
            if (upload.Status != Domain.AggregatesModel.MultipartUploadAggregate.MultipartUploadStatus.InProgress)
            {
                _logger.LogWarning("Upload {UploadId} is not in progress: {Status}", request.UploadId, upload.Status);
                return UploadPartResult.Fail($"Upload is not in progress (status: {upload.Status})");
            }

            // EN: Check if expired / VI: Kiểm tra đã hết hạn
            if (upload.IsExpired())
            {
                _logger.LogWarning("Upload {UploadId} has expired", request.UploadId);
                upload.MarkFailed();
                await _context.SaveEntitiesAsync(cancellationToken);
                return UploadPartResult.Fail("Upload has expired");
            }

            // EN: Upload part to storage provider / VI: Upload part lên storage provider
            var provider = _storageProviderFactory.GetProvider();
            var etag = await provider.UploadPartAsync(
                upload.BucketName,
                upload.ObjectKey,
                upload.ProviderUploadId,
                request.PartNumber,
                request.DataStream,
                cancellationToken);

            // EN: Add part to upload / VI: Thêm part vào upload
            var partSize = request.DataStream.Length;
            upload.AddPart(request.PartNumber, etag, partSize);

            // EN: Save changes / VI: Lưu thay đổi
            await _context.SaveEntitiesAsync(cancellationToken);

            _logger.LogInformation(
                "Part {PartNumber} uploaded successfully. Progress: {Progress}%",
                request.PartNumber, upload.GetProgressPercentage());

            return UploadPartResult.Ok(etag);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error uploading part {PartNumber} for upload {UploadId}", 
                request.PartNumber, request.UploadId);
            return UploadPartResult.Fail("An error occurred while uploading part.");
        }
    }
}
