using MediatR;
using StorageService.Domain.AggregatesModel.FileAggregate;
using StorageService.Domain.AggregatesModel.QuotaAggregate;
using StorageService.Infrastructure.Storage;

namespace StorageService.API.Application.Queries;

/// <summary>
/// EN: Handler for GetFileQuery.
/// VI: Handler cho GetFileQuery.
/// </summary>
public class GetFileQueryHandler : IRequestHandler<GetFileQuery, FileDto?>
{
    private readonly IFileRepository _fileRepository;
    private readonly Infrastructure.Caching.IRedisCacheService _cache;
    private readonly ILogger<GetFileQueryHandler> _logger;

    // EN: Cache TTL for file metadata (10 minutes)
    // VI: TTL cache cho file metadata (10 phút)
    private static readonly TimeSpan FileCacheTtl = TimeSpan.FromMinutes(10);

    public GetFileQueryHandler(
        IFileRepository fileRepository,
        Infrastructure.Caching.IRedisCacheService cache,
        ILogger<GetFileQueryHandler> logger)
    {
        _fileRepository = fileRepository;
        _cache = cache;
        _logger = logger;
    }

    public async Task<FileDto?> Handle(GetFileQuery request, CancellationToken cancellationToken)
    {
        // EN: Try to get from cache first
        // VI: Thử lấy từ cache trước
        var cacheKey = Infrastructure.Caching.CacheKeys.FileMetadata(request.FileId);
        
        var cachedResult = await _cache.GetOrSetAsync(
            cacheKey,
            async () =>
            {
                _logger.LogDebug("Cache miss for file {FileId}, fetching from database", request.FileId);
                var file = await _fileRepository.GetByIdAsync(request.FileId, cancellationToken);
                return file?.ToDto();
            },
            FileCacheTtl,
            cancellationToken);

        // EN: Check ownership after cache retrieval
        // VI: Kiểm tra quyền sở hữu sau khi lấy từ cache
        if (cachedResult == null || cachedResult.UserId != request.UserId)
            return null;
            
        return cachedResult;
    }
}

/// <summary>
/// EN: Handler for GetUserFilesQuery.
/// VI: Handler cho GetUserFilesQuery.
/// </summary>
public class GetUserFilesQueryHandler : IRequestHandler<GetUserFilesQuery, UserFilesResult>
{
    private readonly IFileRepository _fileRepository;
    private readonly ILogger<GetUserFilesQueryHandler> _logger;

    public GetUserFilesQueryHandler(
        IFileRepository fileRepository,
        ILogger<GetUserFilesQueryHandler> logger)
    {
        _fileRepository = fileRepository;
        _logger = logger;
    }

    public async Task<UserFilesResult> Handle(GetUserFilesQuery request, CancellationToken cancellationToken)
    {
        // EN: Note: Not caching this query because search/pagination params vary too much
        // VI: Lưu ý: Không cache query này vì search/pagination params thay đổi quá nhiều
        _logger.LogDebug(
            "Fetching files for user {UserId}, skip={Skip}, take={Take}, search={SearchTerm}",
            request.UserId, request.Skip, request.Take, request.SearchTerm);

        IEnumerable<StorageFile> files;
        
        if (!string.IsNullOrWhiteSpace(request.SearchTerm))
        {
            files = await _fileRepository.SearchAsync(
                request.UserId, 
                request.SearchTerm, 
                request.Skip, 
                request.Take, 
                cancellationToken);
        }
        else
        {
            files = await _fileRepository.GetByUserIdAsync(
                request.UserId, 
                request.Skip, 
                request.Take, 
                cancellationToken);
        }

        var totalCount = await _fileRepository.GetFileCountByUserIdAsync(request.UserId, cancellationToken);
        
        return new UserFilesResult(
            files.Select(f => f.ToDto()).ToList(),
            totalCount);
    }
}

/// <summary>
/// EN: Handler for GetUserQuotaQuery.
/// VI: Handler cho GetUserQuotaQuery.
/// </summary>
public class GetUserQuotaQueryHandler : IRequestHandler<GetUserQuotaQuery, QuotaDto?>
{
    private readonly IQuotaRepository _quotaRepository;
    private readonly Infrastructure.Caching.IRedisCacheService _cache;

    public GetUserQuotaQueryHandler(
        IQuotaRepository quotaRepository,
        Infrastructure.Caching.IRedisCacheService cache)
    {
        _quotaRepository = quotaRepository;
        _cache = cache;
    }

    public async Task<QuotaDto?> Handle(GetUserQuotaQuery request, CancellationToken cancellationToken)
    {
        // EN: Use cache-aside pattern for quota
        // VI: Sử dụng cache-aside pattern cho quota
        if (Guid.TryParse(request.UserId, out var userId))
        {
            var cacheKey = Infrastructure.Caching.CacheKeys.UserQuota(userId);
            
            return await _cache.GetOrSetAsync(
                cacheKey,
                async () =>
                {
                    var quota = await _quotaRepository.GetOrCreateAsync(request.UserId, cancellationToken);
                    return new QuotaDto(
                        quota.UserId,
                        quota.MaxStorageBytes,
                        quota.UsedStorageBytes,
                        quota.RemainingStorageBytes,
                        quota.MaxFileCount,
                        quota.CurrentFileCount,
                        quota.RemainingFileCount,
                        quota.UsagePercentage,
                        quota.QuotaTier);
                },
                Infrastructure.Caching.CacheKeys.DefaultTtl,
                cancellationToken);
        }

        // EN: Fallback if UserId is not valid GUID
        // VI: Fallback nếu UserId không phải GUID hợp lệ
        var quotaFallback = await _quotaRepository.GetOrCreateAsync(request.UserId, cancellationToken);
        return new QuotaDto(
            quotaFallback.UserId,
            quotaFallback.MaxStorageBytes,
            quotaFallback.UsedStorageBytes,
            quotaFallback.RemainingStorageBytes,
            quotaFallback.MaxFileCount,
            quotaFallback.CurrentFileCount,
            quotaFallback.RemainingFileCount,
            quotaFallback.UsagePercentage,
            quotaFallback.QuotaTier);
    }
}

/// <summary>
/// EN: Handler for GetDownloadUrlQuery.
/// VI: Handler cho GetDownloadUrlQuery.
/// </summary>
public class GetDownloadUrlQueryHandler : IRequestHandler<GetDownloadUrlQuery, DownloadUrlResult>
{
    private readonly IFileRepository _fileRepository;
    private readonly IStorageProviderFactory _storageProviderFactory;
    private readonly ILogger<GetDownloadUrlQueryHandler> _logger;

    public GetDownloadUrlQueryHandler(
        IFileRepository fileRepository,
        IStorageProviderFactory storageProviderFactory,
        ILogger<GetDownloadUrlQueryHandler> logger)
    {
        _fileRepository = fileRepository;
        _storageProviderFactory = storageProviderFactory;
        _logger = logger;
    }

    public async Task<DownloadUrlResult> Handle(GetDownloadUrlQuery request, CancellationToken cancellationToken)
    {
        try
        {
            var file = await _fileRepository.GetByIdAsync(request.FileId, cancellationToken);
            
            if (file == null)
                return new DownloadUrlResult(false, null, null, "File not found.");
            
            // EN: Check access / VI: Kiểm tra quyền truy cập
            if (file.AccessLevel == FileAccessLevel.Private && file.UserId != request.UserId)
                return new DownloadUrlResult(false, null, null, "Access denied.");

            // EN: Mark as accessed / VI: Đánh dấu đã truy cập
            file.MarkAccessed();
            _fileRepository.Update(file);
            await _fileRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

            // EN: Generate pre-signed URL / VI: Tạo pre-signed URL
            var provider = _storageProviderFactory.GetProvider(file.Provider);
            var url = await provider.GetPreSignedDownloadUrlAsync(
                file.BucketName, 
                file.ObjectKey, 
                request.ExpirationSeconds, 
                cancellationToken);

            return new DownloadUrlResult(true, url, request.ExpirationSeconds, null);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error generating download URL for file {FileId}", request.FileId);
            return new DownloadUrlResult(false, null, null, "An error occurred.");
        }
    }
}
