namespace WalletService.Infrastructure;

using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using WalletService.Domain.AggregatesModel.PaymentAggregate;
using WalletService.Domain.AggregatesModel.PointAccountAggregate;
using WalletService.Domain.AggregatesModel.WalletAggregate;
using WalletService.Domain.SeedWork;

/// <summary>
/// EN: Tenant provider interface for WalletServiceContext global query filters.
///     Wallet service uses user_id as primary tenant key (wallets are per-user).
/// VI: Interface tenant provider cho global query filters của WalletServiceContext.
///     Wallet service sử dụng user_id làm tenant key chính (ví là per-user).
/// </summary>
public interface IWalletTenantProvider
{
    Guid? GetCurrentUserId();
    bool ShouldBypassTenantFilter();
}

/// <summary>
/// EN: Database context for Wallet Service with Unit of Work pattern and multi-tenant filtering.
/// VI: Database context cho Wallet Service với pattern Unit of Work và filtering đa tenant.
/// </summary>
public class WalletServiceContext : DbContext, IUnitOfWork
{
    private readonly IMediator _mediator;
    private readonly IWalletTenantProvider? _tenantProvider;
    private IDbContextTransaction? _currentTransaction;

    public DbSet<Wallet> Wallets { get; set; } = null!;
    public DbSet<WalletItem> WalletItems { get; set; } = null!;
    public DbSet<WalletTransaction> WalletTransactions { get; set; } = null!;
    public DbSet<HoldItem> WalletHolds { get; set; } = null!;
    public DbSet<Payment> Payments { get; set; } = null!;
    public DbSet<PointAccount> PointAccounts { get; set; } = null!;
    public DbSet<PointTransaction> PointTransactions { get; set; } = null!;

    public bool HasActiveTransaction => _currentTransaction != null;

    public WalletServiceContext(DbContextOptions<WalletServiceContext> options, IMediator mediator)
        : base(options)
    {
        _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
    }

    /// <summary>
    /// EN: Constructor with tenant provider for multi-tenant filtering.
    /// VI: Constructor với tenant provider cho filtering đa tenant.
    /// </summary>
    public WalletServiceContext(
        DbContextOptions<WalletServiceContext> options,
        IMediator mediator,
        IWalletTenantProvider tenantProvider)
        : base(options)
    {
        _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
        _tenantProvider = tenantProvider;
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // EN: Apply all entity configurations
        // VI: Áp dụng tất cả các cấu hình entity
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(WalletServiceContext).Assembly);

        // EN: Global query filter for tenant isolation on Wallets (user-level).
        //     Wallets belong to a user, not a shop. Each user can only see their own wallet.
        // VI: Global query filter cho cách ly tenant trên Wallets (cấp user).
        //     Wallets thuộc về user, không phải shop. Mỗi user chỉ thấy ví của mình.
        modelBuilder.Entity<Wallet>().HasQueryFilter(w =>
            _tenantProvider == null
            || _tenantProvider.ShouldBypassTenantFilter()
            || _tenantProvider.GetCurrentUserId() == null
            || w.UserId == _tenantProvider.GetCurrentUserId());
    }

    /// <summary>
    /// EN: Save changes and dispatch domain events
    /// VI: Lưu thay đổi và dispatch domain events
    /// </summary>
    public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
    {
        // EN: Dispatch domain events after saving
        // VI: Dispatch domain events sau khi lưu
        await DispatchDomainEventsAsync();

        // EN: Save changes to database
        // VI: Lưu thay đổi vào database
        var result = await base.SaveChangesAsync(cancellationToken);
        return result > 0;
    }

    /// <summary>
    /// EN: Begin a new transaction
    /// VI: Bắt đầu transaction mới
    /// </summary>
    public async Task<IDbContextTransaction?> BeginTransactionAsync()
    {
        if (_currentTransaction != null) return null;

        _currentTransaction = await Database.BeginTransactionAsync();
        return _currentTransaction;
    }

    /// <summary>
    /// EN: Commit the current transaction
    /// VI: Commit transaction hiện tại
    /// </summary>
    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;
            }
        }
    }

    /// <summary>
    /// EN: Rollback the current transaction
    /// VI: Rollback transaction hiện tại
    /// </summary>
    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);
        }
    }
}
