namespace MyOffice.DbContext;

using Data.Models.Accounts;
using Data.Models.Currencies;
using Data.Models.Items;
using Microsoft.EntityFrameworkCore;
using Data.Models.Users;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MyOffice.Data.Models.Verifications;
using MyOffice.Data.Models.Notifications;

public enum AppDbContextProvidersEnum
{
	sqlite,
	mssql,
	npgsql
}

public class AppDbContext : DbContext
{
	private readonly AppDbContextProvidersEnum _provider;

	private readonly Dictionary<AppDbContextProvidersEnum, string> _noCaseCollation = new()
	{
		{ AppDbContextProvidersEnum.sqlite, "NOCASE" },
		{ AppDbContextProvidersEnum.npgsql, "my_ci_collation" }
	};

	public AppDbContext(AppDbContextProvidersEnum provider, DbContextOptions<AppDbContext> options) : base(options)
	{
		_provider = provider;
		ConfigureChangeTracker();
	}

	public AppDbContext(DbContextOptions<AppDbContext> options, ConnectionConfiguration connection) : base(options)
	{
		_provider = Enum.Parse<AppDbContextProvidersEnum>(connection.Provider, ignoreCase: true);
		ConfigureChangeTracker();
	}

	private void ConfigureChangeTracker()
	{
		// Match historical repository behavior (manual EntityState updates).
		ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
		ChangeTracker.AutoDetectChangesEnabled = false;
	}

	public DbSet<User> Users { get; set; } = null!;
	public DbSet<UserExternal> UserClaims { get; set; } = null!;

	public DbSet<CurrencyGlobal> CurrencyGlobals { get; set; } = null!;
	public DbSet<Currency> Currencies { get; set; } = null!;
	public DbSet<CurrencyRate> CurrencyRates { get; set; } = null!;
	public DbSet<AccountCategory> AccountCategories { get; set; } = null!;
	public DbSet<Account> Accounts { get; set; } = null!;
	public DbSet<AccountAccountCategory> AccountAccountCategories { get; set; } = null!;
	public DbSet<AccountAccess> AccountAccesses { get; set; } = null!;
	public DbSet<AccountAccessInvite> AccountAccessInvites { get; set; } = null!;

	public DbSet<ItemCategory> ItemCategories { get; set; } = null!;
	public DbSet<ItemGlobal> ItemGlobals { get; set; } = null!;
	public DbSet<Item> Items { get; set; } = null!;
	public DbSet<Motion> Motions { get; set; } = null!;

	public DbSet<VerificationCode> Verifications { get; set; } = null!;
	public DbSet<EmailTemplate> EmailTemplates { get; set; } = null!;

	protected override void OnModelCreating(ModelBuilder modelBuilder)
	{
		var noCaseCollation = _noCaseCollation[_provider];

		if (_provider == AppDbContextProvidersEnum.npgsql)
		{
			modelBuilder.HasCollation("my_ci_collation", "en-u-ks-primary", "icu", false);
		}

		/* NOCASE PROPERTIES */
		modelBuilder.Entity<User>()
			.Property(x => x.UserName)
			.UseCollation(noCaseCollation);

		modelBuilder.Entity<User>()
			.Property(x => x.Email)
			.UseCollation(noCaseCollation);

		modelBuilder.Entity<UserExternal>()
			.Property(x => x.Provider)
			.UseCollation(noCaseCollation);

		modelBuilder.Entity<UserExternal>()
			.Property(x => x.Email)
			.UseCollation(noCaseCollation);


		modelBuilder.Entity<UserExternal>()
			.HasOne(x => x.User)
			.WithMany(x => x.UserClaims)
			.HasForeignKey(x => x.UserId);

		UserCreating(modelBuilder);
		CurrencyCreating(modelBuilder);
		AccountCreating(modelBuilder);
		MotionsCreating(modelBuilder);
		VerificationsCreating(modelBuilder);
		EmailTemplateCreating(modelBuilder);

		modelBuilder.UseOpenIddict();

		base.OnModelCreating(modelBuilder);
	}

	private void VerificationsCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<VerificationCode>()
			.HasKey(x => x.Id);

		modelBuilder.Entity<VerificationCode>()
			.HasOne(x => x.User)
			.WithMany()
			.HasForeignKey(x => x.UserId);
	}

	private void EmailTemplateCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<EmailTemplate>()
			.HasKey(x => x.Id);
	}

	private void UserCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<User>()
			.HasOne(x => x.Currency)
			.WithMany()
			.HasForeignKey(x => x.CurrencyId);
	}

	private void CurrencyCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<CurrencyGlobal>()
			.HasKey(x => x.Id);

		modelBuilder.Entity<Currency>()
			.HasKey(x => x.Id);

		modelBuilder.Entity<CurrencyRate>()
			.HasKey(x => x.Id);

		modelBuilder.Entity<Currency>()
			.HasOne(x => x.CurrencyGlobal)
			.WithMany(x => x.Currencies)
			.HasForeignKey(x => x.CurrencyGlobalId);

		modelBuilder.Entity<Currency>()
			.HasOne(x => x.User)
			.WithMany(x => x.Currencies)
			.HasForeignKey(x => x.UserId);

		modelBuilder.Entity<CurrencyRate>()
			.HasOne(x => x.Currency)
			.WithMany(x => x.Rates)
			.HasForeignKey(x => x.CurrencyId);

		modelBuilder.Entity<Currency>()
			.HasOne(x => x.CurrentRate)
			.WithMany(x => x.Currencies)
			.HasForeignKey(x => x.CurrentRateId);
	}

	private void AccountCreating(ModelBuilder modelBuilder)
	{
		#region Account

		modelBuilder.Entity<Account>()
			.HasKey(x => x.Id);

		modelBuilder.Entity<Account>()
			.HasOne(x => x.CurrencyGlobal)
			.WithMany(x => x.Accounts)
			.HasForeignKey(x => x.CurrencyGlobalId);

		modelBuilder.Entity<Account>()
			.HasOne(x => x.Owner)
			.WithMany(x => x.Accounts)
			.HasForeignKey(x => x.OwnerId);

		#endregion Account

		#region AccountAccess

		modelBuilder.Entity<AccountAccess>()
			.HasKey(x => x.Id);

		modelBuilder.Entity<AccountAccess>()
			.HasOne(x => x.Account)
			.WithMany(x => x.AccessRights)
			.HasForeignKey(x => x.AccountId);

		modelBuilder.Entity<AccountAccess>()
			.HasOne(x => x.User)
			.WithMany(x => x.AccountAccess)
			.HasForeignKey(x => x.UserId);

		modelBuilder.Entity<AccountAccess>()
			.HasOne(x => x.Owner)
			.WithMany(x => x.AccountAccessOwners)
			.HasForeignKey(x => x.OwnerId);

		modelBuilder
			.Entity<AccountAccess>()
			.Property(d => d.Type)
			.HasConversion(new EnumToStringConverter<AccountAccessTypeEnum>());

		modelBuilder.Entity<AccountAccess>()
			.HasIndex(p => new { p.AccountId, p.UserId })
			.IsUnique();

		modelBuilder.Entity<AccountAccess>()
			.HasIndex(p => new { p.AccountId, p.OwnerId })
			.IsUnique();

		#endregion AccountAccess

		#region AccountAccessInvite

		modelBuilder.Entity<AccountAccessInvite>()
			.HasKey(x => x.Id);

		modelBuilder.Entity<AccountAccessInvite>()
			.HasOne(x => x.User)
			.WithMany(x => x.AccountAccessInvites)
			.HasForeignKey(x => x.UserId);

		modelBuilder.Entity<AccountAccessInvite>()
			.HasOne(x => x.Account)
			.WithMany(x => x.Invites)
			.HasForeignKey(x => x.AccountId);
		
		#endregion AccountAccessInvite

		#region Motion

		modelBuilder.Entity<Motion>()
			.HasKey(x => x.Id);

		modelBuilder.Entity<Motion>()
			.HasOne(x => x.Account)
			.WithMany(x => x.Motions)
			.HasForeignKey(x => x.AccountId);
		
		#endregion Motion

		#region AccountCategory

		modelBuilder.Entity<AccountCategory>()
			.HasKey(x => x.Id);

		modelBuilder.Entity<AccountCategory>()
			.HasOne(x => x.User)
			.WithMany(x => x.AccountCategories)
			.HasForeignKey(x => x.UserId);
		
		#endregion AccountCategory

		#region AccountAccountCategory

		modelBuilder.Entity<AccountAccountCategory>()
			.HasKey(x => x.Id);

		modelBuilder.Entity<AccountAccountCategory>()
			.HasOne(x => x.Account)
			.WithMany(x => x.Categories)
			.HasForeignKey(x => x.AccountId);

		modelBuilder.Entity<AccountAccountCategory>()
			.HasOne(x => x.Category)
			.WithMany(x => x.Accounts)
			.HasForeignKey(x => x.CategoryId);

		modelBuilder.Entity<AccountAccountCategory>()
			.HasIndex(p => new { p.AccountId, p.CategoryId })
			.IsUnique();

		modelBuilder.Entity<AccountAccountCategory>()
			.HasOne(x => x.Category)
			.WithMany(x => x.Accounts)
			.HasForeignKey(x => x.CategoryId);
		
		#endregion AccountAccountCategory
	}

	private void MotionsCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<ItemGlobal>()
			.HasKey(x => x.Id);
		modelBuilder.Entity<ItemCategory>()
			.HasKey(x => x.Id);
		modelBuilder.Entity<Item>()
			.HasKey(x => x.Id);
		modelBuilder.Entity<Motion>()
			.HasKey(x => x.Id);

		modelBuilder.Entity<ItemCategory>()
			.HasOne(x => x.User)
			.WithMany(x => x.ItemCategories)
			.HasForeignKey(x => x.UserId);

		modelBuilder.Entity<Item>()
			.HasOne(x => x.Category)
			.WithMany(x => x.Items)
			.HasForeignKey(x => x.CategoryId);

		modelBuilder.Entity<Item>()
			.HasOne(x => x.ItemGlobal)
			.WithMany(x => x.Items)
			.HasForeignKey(x => x.ItemGlobalId);

		modelBuilder.Entity<Motion>()
			.HasOne(x => x.Item)
			.WithMany(x => x.Motions)
			.HasForeignKey(x => x.ItemId);

		modelBuilder.Entity<Motion>()
			.HasOne(x => x.Account)
			.WithMany(x => x.Motions)
			.HasForeignKey(x => x.AccountId);

		modelBuilder.Entity<Motion>()
			.Property(x => x.AmountMinus)
			.HasPrecision(18, 6);

		modelBuilder.Entity<Motion>()
			.Property(x => x.AmountPlus)
			.HasPrecision(18, 6);
	}
}