using StorageService.Domain.SeedWork;
using StorageService.Domain.Events;

namespace StorageService.Domain.AggregatesModel.MultipartUploadAggregate;

/// <summary>
/// EN: Represents a multipart upload in progress.
/// VI: Đại diện cho quá trình multipart upload.
/// </summary>
public class MultipartUpload : Entity, IAggregateRoot
{
    private readonly List<MultipartUploadPart> _parts = new();

    /// <summary>EN: User ID / VI: ID người dùng</summary>
    public string UserId { get; private set; } = string.Empty;
    
    /// <summary>EN: Original file name / VI: Tên file gốc</summary>
    public string FileName { get; private set; } = string.Empty;
    
    /// <summary>EN: Total file size in bytes / VI: Tổng kích thước file (bytes)</summary>
    public long TotalSizeBytes { get; private set; }
    
    /// <summary>EN: Chunk size in bytes / VI: Kích thước chunk (bytes)</summary>
    public int ChunkSizeBytes { get; private set; }
    
    /// <summary>EN: Total number of chunks / VI: Tổng số chunk</summary>
    public int TotalChunks { get; private set; }
    
    /// <summary>EN: Number of uploaded chunks / VI: Số chunk đã upload</summary>
    public int UploadedChunks { get; private set; }
    
    /// <summary>EN: Upload ID from storage provider / VI: Upload ID từ storage provider</summary>
    public string ProviderUploadId { get; private set; } = string.Empty;
    
    /// <summary>EN: Bucket name / VI: Tên bucket</summary>
    public string BucketName { get; private set; } = string.Empty;
    
    /// <summary>EN: Object key / VI: Object key</summary>
    public string ObjectKey { get; private set; } = string.Empty;
    
    /// <summary>EN: Content type / VI: Content type</summary>
    public string ContentType { get; private set; } = string.Empty;
    
    /// <summary>EN: Upload status / VI: Trạng thái upload</summary>
    public MultipartUploadStatus Status { get; private set; }
    
    /// <summary>EN: Creation timestamp / VI: Thời gian tạo</summary>
    public DateTime CreatedAt { get; private set; }
    
    /// <summary>EN: Completion timestamp / VI: Thời gian hoàn thành</summary>
    public DateTime? CompletedAt { get; private set; }
    
    /// <summary>EN: Expiration timestamp / VI: Thời gian hết hạn</summary>
    public DateTime ExpiresAt { get; private set; }
    
    /// <summary>EN: Upload parts / VI: Các part đã upload</summary>
    public IReadOnlyCollection<MultipartUploadPart> Parts => _parts.AsReadOnly();

    // EN: EF Core requires parameterless constructor / VI: EF Core cần constructor không tham số
    protected MultipartUpload() { }

    /// <summary>
    /// EN: Create a new multipart upload.
    /// VI: Tạo một multipart upload mới.
    /// </summary>
    public MultipartUpload(
        string userId,
        string fileName,
        long totalSizeBytes,
        int chunkSizeBytes,
        string providerUploadId,
        string bucketName,
        string objectKey,
        string contentType,
        int expirationHours = 24)
    {
        if (string.IsNullOrWhiteSpace(userId))
            throw new ArgumentNullException(nameof(userId));
        if (string.IsNullOrWhiteSpace(fileName))
            throw new ArgumentNullException(nameof(fileName));
        if (totalSizeBytes <= 0)
            throw new ArgumentException("Total size must be > 0", nameof(totalSizeBytes));
        if (chunkSizeBytes <= 0)
            throw new ArgumentException("Chunk size must be > 0", nameof(chunkSizeBytes));
        if (string.IsNullOrWhiteSpace(providerUploadId))
            throw new ArgumentNullException(nameof(providerUploadId));
        if (string.IsNullOrWhiteSpace(bucketName))
            throw new ArgumentNullException(nameof(bucketName));
        if (string.IsNullOrWhiteSpace(objectKey))
            throw new ArgumentNullException(nameof(objectKey));

        Id = Guid.NewGuid();
        UserId = userId;
        FileName = fileName;
        TotalSizeBytes = totalSizeBytes;
        ChunkSizeBytes = chunkSizeBytes;
        TotalChunks = (int)Math.Ceiling((double)totalSizeBytes / chunkSizeBytes);
        UploadedChunks = 0;
        ProviderUploadId = providerUploadId;
        BucketName = bucketName;
        ObjectKey = objectKey;
        ContentType = contentType ?? "application/octet-stream";
        Status = MultipartUploadStatus.InProgress;
        CreatedAt = DateTime.UtcNow;
        ExpiresAt = DateTime.UtcNow.AddHours(expirationHours);

        // EN: Raise domain event / VI: Phát domain event
        AddDomainEvent(new MultipartUploadInitiatedDomainEvent(Id, fileName, userId, totalSizeBytes));
    }

    /// <summary>
    /// EN: Add a part to the upload.
    /// VI: Thêm một part đã upload.
    /// </summary>
    public void AddPart(int partNumber, string etag, long sizeBytes)
    {
        if (Status != MultipartUploadStatus.InProgress)
            throw new InvalidOperationException($"Cannot add part to upload with status {Status}");

        if (_parts.Any(p => p.PartNumber == partNumber))
            throw new InvalidOperationException($"Part {partNumber} already uploaded");

        var part = new MultipartUploadPart(Id, partNumber, etag, sizeBytes);
        _parts.Add(part);
        UploadedChunks = _parts.Count;
    }

    /// <summary>
    /// EN: Complete the multipart upload.
    /// VI: Hoàn thành multipart upload.
    /// </summary>
    public void Complete()
    {
        if (Status != MultipartUploadStatus.InProgress)
            throw new InvalidOperationException($"Cannot complete upload with status {Status}");

        if (UploadedChunks != TotalChunks)
            throw new InvalidOperationException($"Cannot complete: {UploadedChunks}/{TotalChunks} parts uploaded");

        Status = MultipartUploadStatus.Completed;
        CompletedAt = DateTime.UtcNow;

        // EN: Raise domain event / VI: Phát domain event
        AddDomainEvent(new MultipartUploadCompletedDomainEvent(Id, FileName, UserId, TotalSizeBytes, ObjectKey));
    }

    /// <summary>
    /// EN: Abort the multipart upload.
    /// VI: Hủy multipart upload.
    /// </summary>
    public void Abort()
    {
        if (Status == MultipartUploadStatus.Completed)
            throw new InvalidOperationException("Cannot abort completed upload");

        Status = MultipartUploadStatus.Aborted;
        CompletedAt = DateTime.UtcNow;

        // EN: Raise domain event / VI: Phát domain event
        AddDomainEvent(new MultipartUploadAbortedDomainEvent(Id, UserId));
    }

    /// <summary>
    /// EN: Mark upload as failed.
    /// VI: Đánh dấu upload thất bại.
    /// </summary>
    public void MarkFailed()
    {
        Status = MultipartUploadStatus.Failed;
        CompletedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Check if upload has expired.
    /// VI: Kiểm tra xem upload đã hết hạn chưa.
    /// </summary>
    public bool IsExpired() => DateTime.UtcNow > ExpiresAt;

    /// <summary>
    /// EN: Get progress percentage.
    /// VI: Lấy phần trăm tiến độ.
    /// </summary>
    public double GetProgressPercentage()
    {
        if (TotalChunks == 0) return 0;
        return (double)UploadedChunks / TotalChunks * 100;
    }
}
