using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.ValueGeneration;
using Modules.User.Database.Database.Entities;

namespace Modules.User.Database.Database;

public class UserDbContext : DbContext
{
    public DbSet<Account> Accounts { get; set; }
    public DbSet<Entities.User> Users { get; set; }
    public DbSet<Session> Sessions { get; set; }
    
    public DbSet<Role> Roles { get; set; }
    public DbSet<AccountRole> AccountRoles { get; set; }

    public DbSet<Permission> Permissions { get; set; }
    public DbSet<AccountPermission> AccountPermissions { get; set; }

    public UserDbContext(DbContextOptions<UserDbContext> options) : base(options)
    {

    }

    protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
    {
        configurationBuilder.Properties<Enum>().HaveConversion<string>();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Entities.User>(q =>
        {
            q.ToTable("Users");
            
            q.HasKey(x => x.Id);
            q.Property(x => x.NickName).IsRequired().HasMaxLength(32);
            q.Property(x => x.FirstName).HasMaxLength(32);
            q.Property(x => x.Patronymic).HasMaxLength(64);
            q.Property(x => x.LastName).HasMaxLength(128);
            q.Property(x => x.AvatarId).HasMaxLength(128);
            
            q.HasOne(x => x.Account)
                .WithOne(x => x.User)
                .HasForeignKey<Account>(x => x.UserId);
            
            q.Property(e => e.Xmin).HasColumnName("xmin").IsRowVersion();
            
            q.OwnsOne(x => x.RegionalSettings);
        });
        modelBuilder.Entity<Entities.User>()
            .Navigation(x => x.RegionalSettings).IsRequired(); //fix optional dependent

        modelBuilder.Entity<Account>(q =>
        {
            q.ToTable("Accounts");
            
            q.HasKey(x => x.Id);
            q.Property(x => x.Email).IsRequired().HasMaxLength(128);
            q.Property(x => x.HashedPassword).IsRequired().HasMaxLength(256);

            q.HasMany(x => x.Sessions)
                .WithOne()
                .HasForeignKey(x => x.AccountId)
                .OnDelete(DeleteBehavior.Cascade);
            
            q.Property(x => x.CreationDate)
                .HasDefaultValueSql("now()").ValueGeneratedOnAdd().HasValueGenerator(typeof(DatetimeNowValueGenerator));

            q.Property(e => e.Xmin).HasColumnName("xmin").IsRowVersion();
        });

        modelBuilder.Entity<Session>(q =>
        {
            //q.Property(q => q.DeviceInfo).HasMaxLength(128);
            //q.Property(q => q.UserAgent).HasMaxLength(128);
            q.OwnsOne(x => x.ClientInfo, x =>
            {
                x.Property(z => z.UserAgent).HasMaxLength(256);
                x.Property(z => z.Country).HasMaxLength(96);
                x.Property(z => z.Region).HasMaxLength(128);
            });
            q.Property(x => x.RefreshToken).IsRequired().HasMaxLength(172); //128 bytes to base64
            // q.Property(x => x.ExpiredDate).HasDefaultValueSql("'0001-01-01 00:00:00+00'").ValueGeneratedOnAdd();
            q.Property(x => x.ExpiredDate).HasDefaultValueSql("now()").ValueGeneratedOnAdd();
            q.Property(x => x.LastUpdate).HasDefaultValueSql("now()").ValueGeneratedOnAdd();
        });
        
        modelBuilder.Entity<Permission>(q =>
        {
            q.ToTable("Permissions");
            q.HasKey(x => x.Id);

            q.Property(x => x.Code).IsRequired().HasMaxLength(128);
            q.HasIndex(x => x.Code).IsUnique();

            q.Property(x => x.Name).IsRequired().HasMaxLength(64);
            q.Property(x => x.Description).HasMaxLength(512);
            
            q.Property(x => x.CreationDate)
                .HasDefaultValueSql("now()").ValueGeneratedOnAdd().HasValueGenerator(typeof(DatetimeNowValueGenerator));

            q.Property(e => e.Xmin).HasColumnName("xmin").IsRowVersion();
        });
        
        modelBuilder.Entity<Role>(q =>
        {
            q.ToTable("Roles");
            q.HasKey(x => x.Id);

            q.Property(x => x.Code).IsRequired().HasMaxLength(64);
            q.HasIndex(x => x.Code).IsUnique();

            q.Property(x => x.Name).IsRequired().HasMaxLength(32);
            q.Property(x => x.Description).HasMaxLength(512);

            q.Property(x => x.CreationDate)
                .HasDefaultValueSql("now()").ValueGeneratedOnAdd().HasValueGenerator(typeof(DatetimeNowValueGenerator));
            
            q.Property(e => e.Xmin).HasColumnName("xmin").IsRowVersion();  

            q.HasMany(x => x.Permissions)
                .WithMany(x => x.Roles)
                .UsingEntity<Dictionary<string, object>>("RolePermissions",
                    x => x.HasOne<Permission>()
                        .WithMany()
                        .HasForeignKey("PermissionId")
                        .OnDelete(DeleteBehavior.Restrict)
                    , x => x.HasOne<Role>()
                        .WithMany()
                        .HasForeignKey("RoleId")
                        .OnDelete(DeleteBehavior.Restrict),
                    x =>
                    {
                        x.ToTable("RolePermissions");
                        x.HasKey("RoleId", "PermissionId");
                    });
        });
        
        modelBuilder.Entity<AccountRole>(q =>
        {
            q.ToTable("AccountRoles");
            q.HasKey(x => x.Id);

            q.Property(x => x.GrantedAtUtc)
                .HasDefaultValueSql("now()").ValueGeneratedOnAdd().HasValueGenerator(typeof(DatetimeNowValueGenerator));
            
            q.Property(x => x.IssuerId).IsRequired();
            q.Property(x => x.RevokedAtUtc).IsRequired(false);
            q.Property(x => x.GrantReason).IsRequired(false).HasMaxLength(128);
            q.Property(x => x.RevokeReason).IsRequired(false).HasMaxLength(128);

            q.Property(e => e.Xmin).HasColumnName("xmin").IsRowVersion();

            q.HasOne(x => x.Account)
                .WithMany(a => a.Roles)
                .HasForeignKey(x => x.AccountId)
                .OnDelete(DeleteBehavior.Restrict);
            
            q.HasOne(x => x.Role)
                .WithMany()
                .HasForeignKey(x => x.RoleId)
                .OnDelete(DeleteBehavior.Restrict);
            
            q.HasOne(x => x.Issuer)
                .WithMany()
                .HasForeignKey(x => x.IssuerId)
                .OnDelete(DeleteBehavior.Restrict);

            q.HasOne(x => x.Revoker)
                .WithMany()
                .HasForeignKey(x => x.RevokerId)
                .OnDelete(DeleteBehavior.Restrict)
                .IsRequired(false);
            
            q.HasIndex(x => new { x.AccountId, x.RoleId })
                .HasDatabaseName("UX_AccountRoles_Active")
                .IsUnique()
                .HasFilter("\"RevokedAtUtc\" IS NULL");
            
            q.HasIndex(x => new { x.AccountId, x.GrantedAtUtc });
            q.HasIndex(x => new { x.IssuerId, x.GrantedAtUtc });
        });
        
        modelBuilder.Entity<AccountPermission>(q =>
        {
            q.ToTable("AccountPermissions");
            q.HasKey(x => x.Id);

            q.Property(x => x.GrantedAtUtc)
                .HasDefaultValueSql("now()").ValueGeneratedOnAdd().HasValueGenerator(typeof(DatetimeNowValueGenerator));

            q.Property(x => x.IssuerId).IsRequired();
            q.Property(x => x.RevokedAtUtc).IsRequired(false);
            q.Property(x => x.GrantReason).IsRequired(false).HasMaxLength(128);
            q.Property(x => x.RevokeReason).IsRequired(false).HasMaxLength(128);

            q.Property(e => e.Xmin).HasColumnName("xmin").IsRowVersion();

            q.HasOne(x => x.Account)
                .WithMany(a => a.Permissions)
                .HasForeignKey(x => x.AccountId)
                .OnDelete(DeleteBehavior.Restrict);
            
            q.HasOne(x => x.Permission)
                .WithMany()
                .HasForeignKey(x => x.PermissionId)
                .OnDelete(DeleteBehavior.Restrict);
            
            q.HasOne(x => x.Issuer)
                .WithMany()
                .HasForeignKey(x => x.IssuerId)
                .OnDelete(DeleteBehavior.Restrict);

            q.HasOne(x => x.Revoker)
                .WithMany()
                .HasForeignKey(x => x.RevokerId)
                .OnDelete(DeleteBehavior.Restrict)
                .IsRequired(false);
            
            q.HasIndex(x => new { x.AccountId, x.PermissionId })
                .HasDatabaseName("UX_AccountPermissions_Active")
                .IsUnique()
                .HasFilter("\"RevokedAtUtc\" IS NULL");
            
            q.HasIndex(x => new { x.AccountId, x.GrantedAtUtc });
            q.HasIndex(x => new { x.IssuerId, x.GrantedAtUtc });
        });
        
        modelBuilder.Entity<AccountBan>(q =>
        {
            q.ToTable("AccountBans");
            q.HasKey(x => x.Id);

            q.Property(x => x.BanDate)
                .HasDefaultValueSql("now()").ValueGeneratedOnAdd().HasValueGenerator(typeof(DatetimeNowValueGenerator));

            q.Property(x => x.IssuerId).IsRequired();
            q.Property(x => x.Reason).IsRequired().HasMaxLength(128);
            q.Property(x => x.UnbanReason).IsRequired(false).HasMaxLength(128);
            
            q.Property(e => e.Xmin).HasColumnName("xmin").IsRowVersion();

            q.HasOne(x => x.Account)
                .WithMany(a => a.Bans)
                .HasForeignKey(x => x.AccountId)
                .OnDelete(DeleteBehavior.Restrict);
            
            q.HasIndex(x => new { x.AccountId, x.BanDate });
            // q.HasIndex(x => new { x.AccountId, x.ReleaseDate })
            //     .HasDatabaseName("IX_AccountBans_Active")
            //     .HasFilter("\"Deleted\" = false AND (\"ReleaseDate\" IS NULL OR \"ReleaseDate\" > now())");
        });

        RolesAndPermissionSeedList.SeedPermissions(modelBuilder.Entity<Permission>());
        RolesAndPermissionSeedList.SeedRoles(modelBuilder.Entity<Role>(), modelBuilder);
        
        modelBuilder.Entity<Account>().HasQueryFilter(x => !x.Deleted);
        modelBuilder.Entity<AccountPermission>().HasQueryFilter(x => !x.Account.Deleted);
        modelBuilder.Entity<AccountRole>().HasQueryFilter(x => !x.Account.Deleted);
        
        modelBuilder.Entity<AccountBan>().HasQueryFilter(x => !x.Deleted);
        modelBuilder.Entity<Role>().HasQueryFilter(x => !x.Deleted);
        modelBuilder.Entity<Permission>().HasQueryFilter(x => !x.Deleted);

    }

    private class DatetimeNowValueGenerator : ValueGenerator<DateTime>
    {
        public override bool GeneratesTemporaryValues => false;

        public override DateTime Next(EntityEntry entry)
        {
            return DateTime.UtcNow;
        }
    }
}