using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using InventoryService.Domain.AggregatesModel.InventoryAggregate;
using InventoryService.Domain.SeedWork;
using InventoryService.Infrastructure.EntityConfigurations;
using InventoryService.Infrastructure.Idempotency;

namespace InventoryService.Infrastructure;

/// <summary>
/// EN: Tenant provider interface for InventoryContext global query filters.
/// VI: Interface tenant provider cho global query filters của InventoryContext.
/// </summary>
public interface IInventoryTenantProvider
{
    Guid? GetCurrentShopId();
    bool ShouldBypassTenantFilter();
}

/// <summary>
/// EN: EF Core DbContext for InventoryService with multi-tenant global query filters.
/// VI: EF Core DbContext cho InventoryService với global query filters đa tenant.
/// </summary>
public class InventoryContext : DbContext, IUnitOfWork
{
    private readonly IMediator _mediator;
    private readonly IInventoryTenantProvider? _tenantProvider;
    private IDbContextTransaction? _currentTransaction;

    /// <summary>
    /// EN: Inventory items table.
    /// VI: Bảng Inventory items.
    /// </summary>
    public DbSet<InventoryItem> InventoryItems => Set<InventoryItem>();
    public DbSet<ClientRequest> ClientRequests => Set<ClientRequest>();

    /// <summary>
    /// EN: Read-only access to current transaction.
    /// VI: Truy cập chỉ đọc đến transaction hiện tại.
    /// </summary>
    public IDbContextTransaction? CurrentTransaction => _currentTransaction;

    /// <summary>
    /// EN: Check if there is an active transaction.
    /// VI: Kiểm tra xem có transaction đang hoạt động không.
    /// </summary>
    public bool HasActiveTransaction => _currentTransaction != null;

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

    /// <summary>
    /// EN: Constructor with tenant provider for multi-tenant filtering.
    /// VI: Constructor với tenant provider cho filtering đa tenant.
    /// </summary>
    public InventoryContext(
        DbContextOptions<InventoryContext> options,
        IMediator mediator,
        IInventoryTenantProvider tenantProvider) : base(options)
    {
        _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
        _tenantProvider = tenantProvider;
        System.Diagnostics.Debug.WriteLine("InventoryContext::ctor (tenant) - " + GetHashCode());
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new InventoryItemEntityTypeConfiguration());

        // EN: Configure ClientRequest for idempotency tracking.
        // VI: Cấu hình ClientRequest cho theo dõi idempotency.
        modelBuilder.Entity<ClientRequest>(b =>
        {
            b.ToTable("client_requests");
            b.HasKey(cr => cr.Id);
            b.Property(cr => cr.Id).HasColumnName("id");
            b.Property(cr => cr.Name).HasColumnName("name").HasMaxLength(200).IsRequired();
            b.Property(cr => cr.Time).HasColumnName("time").IsRequired();
        });

        // EN: Ignore Enumeration types so EF Core does NOT auto-discover TypeId as a FK.
        //     TransactionType and ItemType are DDD Enumerations resolved in-memory.
        // VI: Ignore các Enumeration type để EF Core KHÔNG tự phát hiện TypeId là FK.
        //     TransactionType và ItemType là DDD Enumerations xử lý trong bộ nhớ.
        modelBuilder.Ignore<TransactionType>();
        modelBuilder.Ignore<ItemType>();

        // EN: Global query filter for tenant isolation on InventoryItems (shop-level).
        // VI: Global query filter cho cách ly tenant trên InventoryItems (cấp shop).
        modelBuilder.Entity<InventoryItem>().HasQueryFilter(i =>
            _tenantProvider == null
            || _tenantProvider.ShouldBypassTenantFilter()
            || _tenantProvider.GetCurrentShopId() == null
            || i.ShopId == _tenantProvider.GetCurrentShopId());
    }

    public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
    {
        await DispatchDomainEventsAsync();
        await base.SaveChangesAsync(cancellationToken);
        return true;
    }

    public async Task<IDbContextTransaction?> BeginTransactionAsync()
    {
        if (_currentTransaction != null) return null;
        _currentTransaction = await Database.BeginTransactionAsync(System.Data.IsolationLevel.ReadCommitted);
        return _currentTransaction;
    }

    public async Task CommitTransactionAsync(IDbContextTransaction transaction)
    {
        ArgumentNullException.ThrowIfNull(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.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);
        }
    }
}
