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

namespace StorageService.API.Application.Commands;

/// <summary>
/// EN: Handler for SignUploadCommand - generates pre-signed URL for direct upload.
/// VI: Handler cho SignUploadCommand - tạo pre-signed URL cho upload trực tiếp.
/// </summary>
/// <remarks>
/// EN: This handler validates quota, generates object key with path prefix based on access level,
///     and returns a pre-signed PUT URL for client to upload directly to MinIO.
/// VI: Handler này kiểm tra quota, tạo object key với path prefix theo access level,
///     và trả về pre-signed PUT URL để client upload trực tiếp lên MinIO.
/// </remarks>
public class SignUploadCommandHandler : IRequestHandler<SignUploadCommand, SignUploadResult>
{
    private readonly IQuotaRepository _quotaRepository;
    private readonly IStorageProviderFactory _storageProviderFactory;
    private readonly StorageSettings _settings;
    private readonly ILogger<SignUploadCommandHandler> _logger;

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

    public async Task<SignUploadResult> Handle(SignUploadCommand request, CancellationToken cancellationToken)
    {
        try
        {
            _logger.LogInformation(
                "Sign upload request 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 > _settings.MaxFileSizeBytes)
            {
                _logger.LogWarning(
                    "File size {Size} exceeds maximum {Max} for user {UserId}",
                    request.FileSizeBytes, _settings.MaxFileSizeBytes, request.UserId);
                    
                return SignUploadResult.Fail(
                    $"File size exceeds maximum allowed ({_settings.MaxFileSizeBytes / 1024 / 1024}MB)");
            }

            // 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 SignUploadResult.Fail(
                    "Storage quota exceeded. Please upgrade your plan or delete some files.");
            }

            // EN: Generate object key with path prefix based on access level
            // VI: Tạo object key với path prefix theo access level
            var objectKey = GenerateObjectKey(request.UserId, request.FileName, request.AccessLevel);
            var bucketName = _settings.DefaultBucket;
            var expirationSeconds = _settings.PreSignedUrlExpirationSeconds;

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

            // EN: Generate pre-signed PUT URL / VI: Tạo pre-signed PUT URL
            var uploadUrl = await provider.GetPreSignedUploadUrlAsync(
                bucketName,
                objectKey,
                expirationSeconds,
                cancellationToken);

            var expiresAt = DateTime.UtcNow.AddSeconds(expirationSeconds);

            _logger.LogInformation(
                "Generated pre-signed URL for user {UserId}, objectKey: {ObjectKey}, expires: {ExpiresAt}",
                request.UserId, objectKey, expiresAt);

            return SignUploadResult.Ok(uploadUrl, objectKey, expiresAt);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error generating pre-signed URL for user {UserId}", request.UserId);
            return SignUploadResult.Fail("An error occurred while generating upload URL.");
        }
    }

    /// <summary>
    /// EN: Generate object key with path prefix based on access level.
    /// VI: Tạo object key với path prefix theo access level.
    /// </summary>
    /// <remarks>
    /// EN: Path structure:
    ///   - public/{userId}/{date}/{fileId}_{filename}   → Publicly accessible
    ///   - private/{userId}/{date}/{fileId}_{filename}  → Requires pre-signed URL
    ///   - shared/{userId}/{date}/{fileId}_{filename}   → Access controlled by sharing rules
    /// VI: Cấu trúc path:
    ///   - public/{userId}/{date}/{fileId}_{filename}   → Truy cập công khai
    ///   - private/{userId}/{date}/{fileId}_{filename}  → Yêu cầu pre-signed URL
    ///   - shared/{userId}/{date}/{fileId}_{filename}   → Kiểm soát bởi quy tắc chia sẻ
    /// </remarks>
    private static string GenerateObjectKey(string userId, string fileName, FileAccessLevel accessLevel)
    {
        var prefix = accessLevel switch
        {
            FileAccessLevel.Public => "public",
            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}";
    }

    /// <summary>
    /// EN: Sanitize file name to remove invalid characters.
    /// VI: Làm sạch tên file để loại bỏ ký tự không hợp lệ.
    /// </summary>
    private static string SanitizeFileName(string fileName)
    {
        var invalidChars = Path.GetInvalidFileNameChars();
        var sanitized = string.Join("_", fileName.Split(invalidChars, StringSplitOptions.RemoveEmptyEntries));
        
        // EN: Limit file name length / VI: Giới hạn độ dài tên file
        if (sanitized.Length > 100)
        {
            var extension = Path.GetExtension(sanitized);
            var nameWithoutExt = Path.GetFileNameWithoutExtension(sanitized);
            sanitized = nameWithoutExt[..Math.Min(nameWithoutExt.Length, 90)] + extension;
        }

        return sanitized;
    }
}
