using Femto.Common.Domain;
using Femto.Common.Infrastructure.Outbox;
using Femto.Modules.Auth.Models;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.Logging;

namespace Femto.Modules.Auth.Data;

internal class AuthContext(DbContextOptions<AuthContext> options) : DbContext(options), IOutboxContext
{
    public virtual DbSet<UserIdentity> Users { get; set; }
    public virtual DbSet<SignupCode> SignupCodes { get; set; }
    public virtual DbSet<LongTermSession> LongTermSessions { get; set; }
    public virtual DbSet<OutboxEntry> Outbox { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        builder.HasDefaultSchema("authn");
        builder.ApplyConfigurationsFromAssembly(typeof(AuthContext).Assembly);
    }

    public override int SaveChanges()
    {
        throw new InvalidOperationException("Use SaveChangesAsync instead");
    }

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        await EmitDomainEvents(cancellationToken);
        
        return await base.SaveChangesAsync(cancellationToken);
    }

    private async Task EmitDomainEvents(CancellationToken cancellationToken)
    {
        var logger = this.GetService<ILogger<AuthContext>>();
        var publisher = this.GetService<IPublisher>();
        var domainEvents = this
            .ChangeTracker.Entries<Entity>()
            .SelectMany(e =>
            {
                var events = e.Entity.DomainEvents;
                e.Entity.ClearDomainEvents();
                return events;
            })
            .ToList();

        logger.LogTrace("loaded {Count} domain events", domainEvents.Count);

        foreach (var domainEvent in domainEvents)
        {
            logger.LogTrace(
                "publishing {Type} domain event {Id}",
                domainEvent.GetType().Name,
                domainEvent.EventId
            );
            await publisher.Publish(domainEvent, cancellationToken);
        }
    }
}