using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using StorageService.Domain.AggregatesModel.FileAggregate;
using StorageService.Domain.AggregatesModel.QuotaAggregate;
using StorageService.Domain.AggregatesModel.MultipartUploadAggregate;
using StorageService.Domain.AggregatesModel.FileShareAggregate;
using StorageService.Domain.AggregatesModel.FileVersionAggregate;
using StorageService.Domain.AggregatesModel.FolderAggregate;
using StorageService.Domain.SeedWork;
using System.Data;

// EN: Alias to avoid collision with System.IO.FileShare
// VI: Alias để tránh xung đột với System.IO.FileShare
using DomainFileShare = StorageService.Domain.AggregatesModel.FileShareAggregate.FileShare;

namespace StorageService.Infrastructure.Persistence;

/// <summary>
/// EN: Storage Service DbContext.
/// VI: DbContext cho Storage Service.
/// </summary>
public class StorageServiceContext : DbContext, IUnitOfWork
{
    private readonly IMediator _mediator;
    private IDbContextTransaction? _currentTransaction;

    public DbSet<StorageFile> StorageFiles => Set<StorageFile>();
    public DbSet<UserStorageQuota> UserStorageQuotas => Set<UserStorageQuota>();
    public DbSet<MultipartUpload> MultipartUploads => Set<MultipartUpload>();
    public DbSet<MultipartUploadPart> MultipartUploadParts => Set<MultipartUploadPart>();
    public DbSet<DomainFileShare> FileShares => Set<DomainFileShare>();
    public DbSet<FileVersion> FileVersions => Set<FileVersion>();
    public DbSet<Folder> Folders => Set<Folder>();

    public IDbContextTransaction? CurrentTransaction => _currentTransaction;
    public bool HasActiveTransaction => _currentTransaction != null;

    public StorageServiceContext(DbContextOptions<StorageServiceContext> options, IMediator mediator)
        : base(options)
    {
        _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
        System.Diagnostics.Debug.WriteLine("StorageServiceContext::ctor - " + GetHashCode());
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // EN: Configure StorageFile entity / VI: Cấu hình entity StorageFile
        modelBuilder.Entity<StorageFile>(entity =>
        {
            entity.ToTable("storage_files");
            
            entity.HasKey(e => e.Id);
            
            entity.Property(e => e.Id)
                .HasColumnName("id");
            
            entity.Property(e => e.FileName)
                .HasColumnName("file_name")
                .HasMaxLength(255)
                .IsRequired();
            
            entity.Property(e => e.BucketName)
                .HasColumnName("bucket_name")
                .HasMaxLength(100)
                .IsRequired();
            
            entity.Property(e => e.ObjectKey)
                .HasColumnName("object_key")
                .HasMaxLength(500)
                .IsRequired();
            
            entity.Property(e => e.ContentType)
                .HasColumnName("content_type")
                .HasMaxLength(100)
                .IsRequired();
            
            entity.Property(e => e.FileSizeBytes)
                .HasColumnName("file_size_bytes")
                .IsRequired();
            
            entity.Property(e => e.UserId)
                .HasColumnName("user_id")
                .HasMaxLength(100)
                .IsRequired();
            
            entity.Property(e => e.TenantId)
                .HasColumnName("tenant_id")
                .HasMaxLength(100);
            
            entity.Property(e => e.Provider)
                .HasColumnName("provider")
                .HasConversion<string>()
                .IsRequired();
            
            entity.Property(e => e.AccessLevel)
                .HasColumnName("access_level")
                .HasConversion<string>()
                .IsRequired();
            
            entity.Property(e => e.UploadedAt)
                .HasColumnName("uploaded_at")
                .IsRequired();
            
            entity.Property(e => e.LastAccessedAt)
                .HasColumnName("last_accessed_at");
            
            entity.Property(e => e.ExpiresAt)
                .HasColumnName("expires_at");
            
            entity.Property(e => e.Checksum)
                .HasColumnName("checksum")
                .HasMaxLength(100);
            
            entity.Property(e => e.IsDeleted)
                .HasColumnName("is_deleted")
                .HasDefaultValue(false);
            
            entity.Property(e => e.DeletedAt)
                .HasColumnName("deleted_at");

            // EN: Indexes / VI: Indexes
            entity.HasIndex(e => e.ObjectKey).IsUnique();
            entity.HasIndex(e => e.UserId);
            entity.HasIndex(e => e.TenantId);
            entity.HasIndex(e => e.UploadedAt);
            entity.HasIndex(e => e.IsDeleted);
            
            // EN: Filter for soft delete / VI: Filter cho soft delete
            entity.HasQueryFilter(e => !e.IsDeleted);
            
            // EN: Ignore domain events / VI: Bỏ qua domain events
            entity.Ignore(e => e.DomainEvents);
        });

        // EN: Configure UserStorageQuota entity / VI: Cấu hình entity UserStorageQuota
        modelBuilder.Entity<UserStorageQuota>(entity =>
        {
            entity.ToTable("user_storage_quotas");
            
            entity.HasKey(e => e.Id);
            
            entity.Property(e => e.Id)
                .HasColumnName("id");
            
            entity.Property(e => e.UserId)
                .HasColumnName("user_id")
                .HasMaxLength(100)
                .IsRequired();
            
            entity.Property(e => e.MaxStorageBytes)
                .HasColumnName("max_storage_bytes")
                .IsRequired();
            
            entity.Property(e => e.UsedStorageBytes)
                .HasColumnName("used_storage_bytes")
                .IsRequired();
            
            entity.Property(e => e.MaxFileCount)
                .HasColumnName("max_file_count")
                .IsRequired();
            
            entity.Property(e => e.CurrentFileCount)
                .HasColumnName("current_file_count")
                .IsRequired();
            
            entity.Property(e => e.QuotaTier)
                .HasColumnName("quota_tier")
                .HasMaxLength(50);
            
            entity.Property(e => e.CreatedAt)
                .HasColumnName("created_at")
                .IsRequired();
            
            entity.Property(e => e.LastUpdatedAt)
                .HasColumnName("last_updated_at")
                .IsRequired();

            // EN: Indexes / VI: Indexes
            entity.HasIndex(e => e.UserId).IsUnique();
            
            // EN: Ignore domain events / VI: Bỏ qua domain events
            entity.Ignore(e => e.DomainEvents);
        });

        // EN: Configure MultipartUpload entity / VI: Cấu hình entity MultipartUpload
        modelBuilder.Entity<MultipartUpload>(entity =>
        {
            entity.ToTable("multipart_uploads");
            
            entity.HasKey(e => e.Id);
            
            entity.Property(e => e.Id)
                .HasColumnName("id");
            
            entity.Property(e => e.UserId)
                .HasColumnName("user_id")
                .HasMaxLength(255)
                .IsRequired();
            
            entity.Property(e => e.FileName)
                .HasColumnName("file_name")
                .HasMaxLength(255)
                .IsRequired();
            
            entity.Property(e => e.TotalSizeBytes)
                .HasColumnName("total_size_bytes")
                .IsRequired();
            
            entity.Property(e => e.ChunkSizeBytes)
                .HasColumnName("chunk_size_bytes")
                .IsRequired();
            
            entity.Property(e => e.TotalChunks)
                .HasColumnName("total_chunks")
                .IsRequired();
            
            entity.Property(e => e.UploadedChunks)
                .HasColumnName("uploaded_chunks")
                .IsRequired();
            
            entity.Property(e => e.ProviderUploadId)
                .HasColumnName("upload_id")
                .HasMaxLength(255)
                .IsRequired();
            
            entity.Property(e => e.BucketName)
                .HasColumnName("bucket_name")
                .HasMaxLength(255)
                .IsRequired();
            
            entity.Property(e => e.ObjectKey)
                .HasColumnName("object_key")
                .HasMaxLength(500)
                .IsRequired();
            
            entity.Property(e => e.ContentType)
                .HasColumnName("content_type")
                .HasMaxLength(100)
                .IsRequired();
            
            entity.Property(e => e.Status)
                .HasColumnName("status")
                .HasConversion<string>()
                .HasMaxLength(50)
                .IsRequired();
            
            entity.Property(e => e.CreatedAt)
                .HasColumnName("created_at")
                .IsRequired();
            
            entity.Property(e => e.CompletedAt)
                .HasColumnName("completed_at");
            
            entity.Property(e => e.ExpiresAt)
                .HasColumnName("expires_at")
                .IsRequired();

            // EN: Indexes / VI: Indexes
            entity.HasIndex(e => new { e.UserId, e.Status });
            entity.HasIndex(e => e.ProviderUploadId);
            entity.HasIndex(e => e.CreatedAt);
            
            // EN: Configure relationship with parts / VI: Cấu hình mối quan hệ với parts
            entity.HasMany(e => e.Parts)
                .WithOne()
                .HasForeignKey(p => p.MultipartUploadId)
                .OnDelete(DeleteBehavior.Cascade);
            
            // EN: Ignore domain events / VI: Bỏ qua domain events
            entity.Ignore(e => e.DomainEvents);
        });

        // EN: Configure MultipartUploadPart entity / VI: Cấu hình entity MultipartUploadPart
        modelBuilder.Entity<MultipartUploadPart>(entity =>
        {
            entity.ToTable("multipart_upload_parts");
            
            entity.HasKey(e => e.Id);
            
            entity.Property(e => e.Id)
                .HasColumnName("id");
            
            entity.Property(e => e.MultipartUploadId)
                .HasColumnName("multipart_upload_id")
                .IsRequired();
            
            entity.Property(e => e.PartNumber)
                .HasColumnName("part_number")
                .IsRequired();
            
            entity.Property(e => e.ETag)
                .HasColumnName("etag")
                .HasMaxLength(255)
                .IsRequired();
            
            entity.Property(e => e.SizeBytes)
                .HasColumnName("size_bytes")
                .IsRequired();
            
            entity.Property(e => e.UploadedAt)
                .HasColumnName("uploaded_at")
                .IsRequired();

            // EN: Indexes / VI: Indexes
            entity.HasIndex(e => new { e.MultipartUploadId, e.PartNumber })
                .IsUnique();
            
            // EN: Ignore domain events / VI: Bỏ qua domain events
            entity.Ignore(e => e.DomainEvents);
        });

        // EN: Configure FileShare entity / VI: Cấu hình entity FileShare
        modelBuilder.Entity<DomainFileShare>(entity =>
        {
            entity.ToTable("file_shares");
            
            entity.HasKey(e => e.Id);
            
            entity.Property(e => e.Id)
                .HasColumnName("id");
            
            entity.Property(e => e.FileId)
                .HasColumnName("file_id")
                .IsRequired();
            
            entity.Property(e => e.SharedBy)
                .HasColumnName("shared_by")
                .HasMaxLength(255)
                .IsRequired();
            
            entity.Property(e => e.SharedWith)
                .HasColumnName("shared_with")
                .HasMaxLength(255);
            
            entity.Property(e => e.Permission)
                .HasColumnName("permission")
                .HasConversion<string>()
                .HasMaxLength(50)
                .IsRequired();
            
            entity.Property(e => e.ShareToken)
                .HasColumnName("share_token")
                .HasMaxLength(255)
                .IsRequired();
            
            entity.Property(e => e.PasswordHash)
                .HasColumnName("password_hash")
                .HasMaxLength(255);
            
            entity.Property(e => e.ExpiresAt)
                .HasColumnName("expires_at");
            
            entity.Property(e => e.MaxDownloads)
                .HasColumnName("max_downloads");
            
            entity.Property(e => e.DownloadCount)
                .HasColumnName("download_count")
                .HasDefaultValue(0);
            
            entity.Property(e => e.Status)
                .HasColumnName("status")
                .HasConversion<string>()
                .HasMaxLength(50)
                .IsRequired();
            
            entity.Property(e => e.CreatedAt)
                .HasColumnName("created_at")
                .IsRequired();
            
            entity.Property(e => e.RevokedAt)
                .HasColumnName("revoked_at");

            // EN: Indexes / VI: Indexes
            entity.HasIndex(e => e.FileId);
            entity.HasIndex(e => e.ShareToken).IsUnique();
            entity.HasIndex(e => e.SharedWith);
            entity.HasIndex(e => e.SharedBy);
            
            // EN: Relationship with StorageFile / VI: Mối quan hệ với StorageFile
            entity.HasOne<StorageFile>()
                .WithMany()
                .HasForeignKey(e => e.FileId)
                .OnDelete(DeleteBehavior.Cascade);
            
            // EN: Ignore domain events / VI: Bỏ qua domain events
            entity.Ignore(e => e.DomainEvents);
        });

        // EN: Configure FileVersion entity / VI: Cấu hình entity FileVersion
        modelBuilder.Entity<FileVersion>(entity =>
        {
            entity.ToTable("file_versions");
            
            entity.HasKey(e => e.Id);
            
            entity.Property(e => e.Id)
                .HasColumnName("id");
            
            entity.Property(e => e.FileId)
                .HasColumnName("file_id")
                .IsRequired();
            
            entity.Property(e => e.VersionNumber)
                .HasColumnName("version_number")
                .IsRequired();
            
            entity.Property(e => e.ObjectKey)
                .HasColumnName("object_key")
                .HasMaxLength(500)
                .IsRequired();
            
            entity.Property(e => e.SizeBytes)
                .HasColumnName("size_bytes")
                .IsRequired();
            
            entity.Property(e => e.ContentType)
                .HasColumnName("content_type")
                .HasMaxLength(100)
                .IsRequired();
            
            entity.Property(e => e.Checksum)
                .HasColumnName("checksum")
                .HasMaxLength(100);
            
            entity.Property(e => e.CreatedAt)
                .HasColumnName("created_at")
                .IsRequired();
            
            entity.Property(e => e.CreatedBy)
                .HasColumnName("created_by")
                .HasMaxLength(255)
                .IsRequired();
            
            entity.Property(e => e.Comment)
                .HasColumnName("comment")
                .HasMaxLength(500);
            
            entity.Property(e => e.IsCurrent)
                .HasColumnName("is_current")
                .HasDefaultValue(false);

            // EN: Indexes / VI: Indexes
            entity.HasIndex(e => e.FileId);
            entity.HasIndex(e => new { e.FileId, e.VersionNumber }).IsUnique();
            entity.HasIndex(e => new { e.FileId, e.IsCurrent });
            
            // EN: Relationship with StorageFile / VI: Mối quan hệ với StorageFile
            entity.HasOne<StorageFile>()
                .WithMany()
                .HasForeignKey(e => e.FileId)
                .OnDelete(DeleteBehavior.Cascade);
            
            // EN: Ignore domain events / VI: Bỏ qua domain events
            entity.Ignore(e => e.DomainEvents);
        });

        // EN: Configure Folder entity / VI: Cấu hình entity Folder
        modelBuilder.Entity<Folder>(entity =>
        {
            entity.ToTable("folders");
            
            entity.HasKey(e => e.Id);
            
            entity.Property(e => e.Id)
                .HasColumnName("id");
            
            entity.Property(e => e.UserId)
                .HasColumnName("user_id")
                .HasMaxLength(255)
                .IsRequired();
            
            entity.Property(e => e.ParentId)
                .HasColumnName("parent_id");
            
            entity.Property(e => e.Name)
                .HasColumnName("name")
                .HasMaxLength(255)
                .IsRequired();
            
            entity.Property(e => e.Path)
                .HasColumnName("path")
                .HasMaxLength(1000)
                .IsRequired();
            
            entity.Property(e => e.Level)
                .HasColumnName("level")
                .IsRequired();
            
            entity.Property(e => e.CreatedAt)
                .HasColumnName("created_at")
                .IsRequired();
            
            entity.Property(e => e.UpdatedAt)
                .HasColumnName("updated_at")
                .IsRequired();
            
            entity.Property(e => e.IsDeleted)
                .HasColumnName("is_deleted")
                .HasDefaultValue(false);
            
            entity.Property(e => e.DeletedAt)
                .HasColumnName("deleted_at");

            // EN: Indexes / VI: Indexes
            entity.HasIndex(e => e.UserId);
            entity.HasIndex(e => e.ParentId);
            entity.HasIndex(e => new { e.UserId, e.ParentId, e.Name }).IsUnique().HasFilter("is_deleted = false");
            entity.HasIndex(e => e.Path);
            
            // EN: Self-referencing relationship / VI: Quan hệ tự tham chiếu
            entity.HasOne<Folder>()
                .WithMany()
                .HasForeignKey(e => e.ParentId)
                .OnDelete(DeleteBehavior.Restrict);
            
            // EN: Ignore domain events / VI: Bỏ qua domain events
            entity.Ignore(e => e.DomainEvents);
            
            // EN: Filter for soft delete / VI: Filter cho soft delete
            entity.HasQueryFilter(e => !e.IsDeleted);
        });
    }

    /// <inheritdoc />
    public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
    {
        // EN: Dispatch domain events before saving / VI: Dispatch domain events trước khi lưu
        await DispatchDomainEventsAsync();

        await base.SaveChangesAsync(cancellationToken);
        return true;
    }

    public async Task<IDbContextTransaction?> BeginTransactionAsync()
    {
        if (_currentTransaction != null) return null;

        _currentTransaction = await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted);

        return _currentTransaction;
    }

    public async Task CommitTransactionAsync(IDbContextTransaction transaction)
    {
        if (transaction == null) throw new ArgumentNullException(nameof(transaction));
        if (transaction != _currentTransaction) throw new InvalidOperationException($"Transaction {transaction.TransactionId} is not current");

        try
        {
            await SaveChangesAsync();
            await transaction.CommitAsync();
        }
        catch
        {
            RollbackTransaction();
            throw;
        }
        finally
        {
            if (_currentTransaction != null)
            {
                _currentTransaction.Dispose();
                _currentTransaction = null;
            }
        }
    }

    public void RollbackTransaction()
    {
        try
        {
            _currentTransaction?.Rollback();
        }
        finally
        {
            if (_currentTransaction != null)
            {
                _currentTransaction.Dispose();
                _currentTransaction = null;
            }
        }
    }

    private async Task DispatchDomainEventsAsync()
    {
        var domainEntities = ChangeTracker
            .Entries<Entity>()
            .Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any())
            .ToList();

        var domainEvents = domainEntities
            .SelectMany(x => x.Entity.DomainEvents)
            .ToList();

        domainEntities.ForEach(entity => entity.Entity.ClearDomainEvents());

        foreach (var domainEvent in domainEvents)
        {
            await _mediator.Publish(domainEvent);
        }
    }
}
