using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Knot.Contracts.Conversations.Application.Abstractions;
using Knot.Contracts.Conversations.Domain;
using Knot.Contracts.Conversations.Infrastructure.Persistence;
using Knot.Shared.Kernel;
using Knot.Shared.Kernel.Security;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using DomainChat = Knot.Contracts.Conversations.Domain.Chat;

namespace Knot.Modules.Conversations.Infrastructure.Persistence;

public sealed class ChatsDbContext : DbContext, Knot.Contracts.Conversations.Application.Abstractions.IChatsUnitOfWork, Knot.Contracts.Conversations.Infrastructure.Persistence.IChatsDbContext
{
    private readonly IMediator? _mediator;
    private readonly IEncryptionService? _encryptionService;

    public ChatsDbContext(DbContextOptions<ChatsDbContext> options)
        : base(options)
    {
    }

    public ChatsDbContext(DbContextOptions<ChatsDbContext> options, IMediator mediator, IEncryptionService encryptionService)
        : this(options)
    {
        _mediator = mediator;
        _encryptionService = encryptionService;
    }

    public DbSet<DomainChat> Chats => Set<DomainChat>();
    public DbSet<Folder> Folders => Set<Folder>();
    public DbSet<UserChatSettings> UserChatSettings => Set<UserChatSettings>();

    IQueryable<Knot.Contracts.Conversations.Infrastructure.Persistence.Chat> Knot.Contracts.Conversations.Infrastructure.Persistence.IChatsDbContext.Chats =>
            Set<DomainChat>().Select(c => new Knot.Contracts.Conversations.Infrastructure.Persistence.Chat { Id = c.Id, Avatar = c.Avatar });

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning));
    }

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

        modelBuilder.Entity<DomainChat>(builder =>
                {
                    builder.ToTable("Chats");
                    builder.HasKey(c => c.Id);
                    builder.Property(c => c.IsImporting);
                    builder.Property(c => c.ImportJobId);
                    builder.Property(c => c.Type).HasConversion<string>();

                    builder.OwnsMany(c => c.Members, mb =>
                    {
                        mb.ToTable("ChatMembers");
                        mb.HasKey(m => m.Id);
                        mb.WithOwner().HasForeignKey(m => m.ChatId);
                        mb.HasIndex(m => new { m.ChatId, m.UserId }).IsUnique();
                    }).Navigation(c => c.Members).UsePropertyAccessMode(PropertyAccessMode.Field);
                });

        modelBuilder.Entity<Folder>(builder =>
        {
            builder.ToTable("Folders");
            builder.HasKey(f => f.Id);
            builder.Property(f => f.Type).HasConversion<string>();
        });

        modelBuilder.Entity<UserChatSettings>(builder =>
        {
            builder.ToTable("UserChatSettings");
            builder.HasKey(s => s.Id);
            builder.HasIndex(s => new { s.UserId, s.ChatId }).IsUnique();

            builder.Property(s => s.FolderIds)
                .HasConversion(
                    v => string.Join(',', v),
                    v => v.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(Guid.Parse).ToList()
                );
        });
    }

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

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

        return result;
    }
}
