using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using AdsBillingService.Domain.AggregatesModel.BillingAccountAggregate;
using AdsBillingService.Domain.AggregatesModel.InvoiceAggregate;
using AdsBillingService.Domain.AggregatesModel.ChargeAggregate;
using AdsBillingService.Domain.SeedWork;

namespace AdsBillingService.Infrastructure;

public class AdsBillingServiceContext : DbContext, IUnitOfWork
{
    private readonly IMediator _mediator;
    private IDbContextTransaction? _currentTransaction;

    public DbSet<BillingAccount> BillingAccounts => Set<BillingAccount>();
    public DbSet<Invoice> Invoices => Set<Invoice>();
    public DbSet<AdCharge> AdCharges => Set<AdCharge>();

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

    public AdsBillingServiceContext(DbContextOptions<AdsBillingServiceContext> options) : base(options)
    {
        _mediator = null!;
    }

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

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // EN: Apply entity configurations / VI: Áp dụng entity configurations
        modelBuilder.ApplyConfiguration(new EntityConfigurations.BillingAccountEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new EntityConfigurations.InvoiceEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new EntityConfigurations.InvoiceLineItemEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new EntityConfigurations.AdChargeEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new EntityConfigurations.ClientRequestEntityTypeConfiguration());
    }

    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);
        }
    }
}
