using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using FnbEngine.Domain.AggregatesModel.BaristaAggregate;
using FnbEngine.Domain.AggregatesModel.TableAggregate;
using FnbEngine.Domain.AggregatesModel.SessionAggregate;
using FnbEngine.Domain.AggregatesModel.KitchenAggregate;
using FnbEngine.Domain.AggregatesModel.RecipeAggregate;
using FnbEngine.Domain.AggregatesModel.ReservationAggregate;
using FnbEngine.Domain.SeedWork;
using FnbEngine.Infrastructure.EntityConfigurations;

namespace FnbEngine.Infrastructure;

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

public class FnbContext : DbContext, IUnitOfWork
{
    private readonly IMediator _mediator;
    private readonly IFnbTenantProvider? _tenantProvider;
    private IDbContextTransaction? _currentTransaction;

    public DbSet<Table> Tables => Set<Table>();
    public DbSet<Session> Sessions => Set<Session>();
    public DbSet<KitchenTicket> KitchenTickets => Set<KitchenTicket>();
    public DbSet<Recipe> Recipes => Set<Recipe>();
    public DbSet<RecipeIngredient> RecipeIngredients => Set<RecipeIngredient>();
    public DbSet<Reservation> Reservations => Set<Reservation>();
    public DbSet<BaristaQueueItem> BaristaQueueItems => Set<BaristaQueueItem>();
    public IDbContextTransaction? CurrentTransaction => _currentTransaction;
    public bool HasActiveTransaction => _currentTransaction != null;

    public FnbContext(DbContextOptions<FnbContext> 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 FnbContext(
        DbContextOptions<FnbContext> options,
        IMediator mediator,
        IFnbTenantProvider tenantProvider) : base(options)
    {
        _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
        _tenantProvider = tenantProvider;
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new TableEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new TableStatusEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new SessionEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new KitchenTicketEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new RecipeEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new RecipeIngredientEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new ReservationEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new BaristaQueueItemEntityTypeConfiguration());

        // EN: Global query filters for tenant isolation (shop-level).
        //     Tables, Sessions, and Reservations have shop_id.
        //     KitchenTickets are accessed via Sessions (indirect isolation).
        //     Recipes are shared across shops (no tenant filter).
        // VI: Global query filters cho cách ly tenant (cấp shop).
        //     Tables, Sessions, và Reservations có shop_id.
        //     KitchenTickets được truy cập qua Sessions (cách ly gián tiếp).
        //     Recipes được chia sẻ giữa các shop (không có tenant filter).
        modelBuilder.Entity<Table>().HasQueryFilter(t =>
            _tenantProvider == null
            || _tenantProvider.ShouldBypassTenantFilter()
            || _tenantProvider.GetCurrentShopId() == null
            || t.ShopId == _tenantProvider.GetCurrentShopId());

        modelBuilder.Entity<Session>().HasQueryFilter(s =>
            _tenantProvider == null
            || _tenantProvider.ShouldBypassTenantFilter()
            || _tenantProvider.GetCurrentShopId() == null
            || s.ShopId == _tenantProvider.GetCurrentShopId());

        modelBuilder.Entity<Reservation>().HasQueryFilter(r =>
            _tenantProvider == null
            || _tenantProvider.ShouldBypassTenantFilter()
            || _tenantProvider.GetCurrentShopId() == null
            || r.ShopId == _tenantProvider.GetCurrentShopId());

        modelBuilder.Entity<BaristaQueueItem>().HasQueryFilter(bq =>
            _tenantProvider == null
            || _tenantProvider.ShouldBypassTenantFilter()
            || _tenantProvider.GetCurrentShopId() == null
            || bq.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);
    }
}
