using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using BookingService.Domain.AggregatesModel.AppointmentAggregate;
using BookingService.Domain.AggregatesModel.ResourceAggregate;
using BookingService.Domain.AggregatesModel.StaffAggregate;
using BookingService.Domain.SeedWork;
using BookingService.Infrastructure.EntityConfigurations;

namespace BookingService.Infrastructure;

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

    public DbSet<Appointment> Appointments => Set<Appointment>();
    public DbSet<Resource> Resources => Set<Resource>();
    public DbSet<StaffSchedule> StaffSchedules => Set<StaffSchedule>();
    public IDbContextTransaction? CurrentTransaction => _currentTransaction;
    public bool HasActiveTransaction => _currentTransaction != null;

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

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new AppointmentEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new AppointmentStatusEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new ResourceEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new StaffScheduleEntityTypeConfiguration());
    }

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