using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Knot.Modules.Relations.Domain;
using Knot.Shared.Kernel;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Knot.Modules.Relations.Application.Abstractions;

namespace Knot.Modules.Relations.Infrastructure.Persistence;

public sealed class RelationsDbContext : DbContext, IContactsDbContext
{
    private readonly IMediator _mediator;

    public RelationsDbContext(DbContextOptions<RelationsDbContext> options, IMediator mediator)
        : base(options)
    {
        _mediator = mediator;
    }

    public DbSet<Contact> Contacts => Set<Contact>();
    public DbSet<UserReplica> UserReplicas => Set<UserReplica>();
    public DbSet<Friendship> Friendships => Set<Friendship>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.HasDefaultSchema("relations");

        modelBuilder.Entity<Contact>(builder =>
        {
            builder.ToTable("Contacts");
            builder.HasKey(c => c.Id);
            builder.Property(c => c.Status).HasConversion<string>();
            builder.HasIndex(c => new { c.UserId, c.ContactId }).IsUnique();
        });

        modelBuilder.Entity<UserReplica>(builder =>
        {
            builder.ToTable("UserReplicas");
            builder.HasKey(r => r.Id);
        });

        modelBuilder.Entity<Friendship>(builder =>
        {
            builder.ToTable("Friendships");
            builder.HasKey(f => f.Id);
            builder.Property(f => f.Status).HasConversion<string>();
            builder.HasIndex(f => new { f.UserId, f.FriendId }).IsUnique();
        });
    }

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        var domainEvents = ChangeTracker
            .Entries<IAggregateRoot>()
            .SelectMany(x =>
            {
                if (x.Entity is AggregateRoot<Guid> root)
                {
                    var events = root.GetDomainEvents().ToList();
                    root.ClearDomainEvents();
                    return events;
                }
                return Enumerable.Empty<IDomainEvent>();
            })
            .ToList();

        int result = await base.SaveChangesAsync(cancellationToken);

        foreach (var domainEvent in domainEvents)
        {
            await _mediator.Publish(domainEvent, cancellationToken);
        }

        return result;
    }
}
