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

namespace StorageService.API.Application.Commands;

/// <summary>
/// EN: Handler for UploadFileCommand.
/// VI: Handler cho UploadFileCommand.
/// </summary>
public class UploadFileCommandHandler : IRequestHandler<UploadFileCommand, UploadFileResult>
{
    private readonly IFileRepository _fileRepository;
    private readonly IQuotaRepository _quotaRepository;
    private readonly IStorageProviderFactory _storageProviderFactory;
    private readonly StorageSettings _settings;
    private readonly ILogger<UploadFileCommandHandler> _logger;

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

    public async Task<UploadFileResult> Handle(UploadFileCommand request, CancellationToken cancellationToken)
    {
        try
        {
            // EN: Check file size limit / VI: Kiểm tra giới hạn kích thước file
            if (request.FileSizeBytes > _settings.MaxFileSizeBytes)
            {
                return new UploadFileResult(false, null, null, 
                    $"File size exceeds maximum allowed ({_settings.MaxFileSizeBytes} bytes)");
            }

            // EN: Check user quota / VI: Kiểm tra quota user
            var quota = await _quotaRepository.GetOrCreateAsync(request.UserId, cancellationToken);
            if (!quota.CanUpload(request.FileSizeBytes))
            {
                return new UploadFileResult(false, null, null, 
                    "Quota exceeded. Please upgrade your plan or delete some files.");
            }

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

            // EN: Upload to storage provider / VI: Upload lên storage provider
            var provider = _storageProviderFactory.GetProvider();
            var uploadResult = await provider.UploadAsync(
                bucketName, 
                objectKey, 
                request.FileStream, 
                request.ContentType, 
                cancellationToken);

            if (!uploadResult.Success)
            {
                _logger.LogError("Failed to upload file to storage: {Error}", uploadResult.Error);
                return new UploadFileResult(false, null, null, uploadResult.Error);
            }

            // EN: Save file metadata / VI: Lưu metadata file
            var storageFile = new StorageFile(
                request.FileName,
                bucketName,
                objectKey,
                request.ContentType,
                request.FileSizeBytes,
                request.UserId,
                provider.ProviderType,
                request.AccessLevel,
                request.TenantId,
                uploadResult.Checksum);

            await _fileRepository.AddAsync(storageFile, cancellationToken);

            // EN: Update quota / VI: Cập nhật quota
            quota.AddUsage(request.FileSizeBytes);
            _quotaRepository.Update(quota);

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

            _logger.LogInformation("File uploaded successfully: {FileId}, {ObjectKey}", 
                storageFile.Id, objectKey);

            return new UploadFileResult(true, storageFile.Id, objectKey, null);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error uploading file for user {UserId}", request.UserId);
            return new UploadFileResult(false, null, null, "An error occurred while uploading the file.");
        }
    }

    /// <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 timestamp = DateTime.UtcNow.ToString("yyyyMMdd");
        var uniqueId = Guid.NewGuid().ToString("N")[..8];
        var safeFileName = SanitizeFileName(fileName);
        return $"{prefix}/{userId}/{timestamp}/{uniqueId}_{safeFileName}";
    }

    private static string SanitizeFileName(string fileName)
    {
        var invalidChars = Path.GetInvalidFileNameChars();
        return string.Join("_", fileName.Split(invalidChars, StringSplitOptions.RemoveEmptyEntries));
    }
}

