using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;

namespace EntKube.Web.Data;

/// <summary>
/// The main database context for EntKube, holding Identity tables and all
/// application entities. This is the base context — provider-specific
/// subclasses exist for PostgreSQL and SQL Server so EF Core can generate
/// provider-appropriate migrations independently.
/// </summary>
public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext<ApplicationUser>(options)
{
    public DbSet<Tenant> Tenants => Set<Tenant>();
    public DbSet<TenantRole> TenantRoles => Set<TenantRole>();
    public DbSet<TenantMembership> TenantMemberships => Set<TenantMembership>();
    public DbSet<Group> Groups => Set<Group>();
    public DbSet<GroupMembership> GroupMemberships => Set<GroupMembership>();
    public DbSet<Environment> Environments => Set<Environment>();
    public DbSet<Customer> Customers => Set<Customer>();
    public DbSet<App> Apps => Set<App>();
    public DbSet<AppEnvironment> AppEnvironments => Set<AppEnvironment>();
    public DbSet<KubernetesCluster> KubernetesClusters => Set<KubernetesCluster>();
    public DbSet<SecretVault> SecretVaults => Set<SecretVault>();
    public DbSet<VaultSecret> VaultSecrets => Set<VaultSecret>();
    public DbSet<ClusterComponent> ClusterComponents => Set<ClusterComponent>();
    public DbSet<ExternalRoute> ExternalRoutes => Set<ExternalRoute>();
    public DbSet<StorageLink> StorageLinks => Set<StorageLink>();
    public DbSet<OpenStackConnection> OpenStackConnections => Set<OpenStackConnection>();
    public DbSet<StorageBinding> StorageBindings => Set<StorageBinding>();
    public DbSet<AppDeployment> AppDeployments => Set<AppDeployment>();
    public DbSet<DeploymentManifest> DeploymentManifests => Set<DeploymentManifest>();
    public DbSet<DeploymentResource> DeploymentResources => Set<DeploymentResource>();
    public DbSet<CustomerAccess> CustomerAccesses => Set<CustomerAccess>();
    public DbSet<CnpgCluster> CnpgClusters => Set<CnpgCluster>();
    public DbSet<CnpgDatabase> CnpgDatabases => Set<CnpgDatabase>();
    public DbSet<CnpgBackup> CnpgBackups => Set<CnpgBackup>();
    public DbSet<MongoCluster> MongoClusters => Set<MongoCluster>();
    public DbSet<MongoDatabase> MongoDatabases => Set<MongoDatabase>();
    public DbSet<DatabaseBinding> DatabaseBindings => Set<DatabaseBinding>();
    public DbSet<MongoBackup> MongoBackups => Set<MongoBackup>();
    public DbSet<KeycloakComponentConfig> KeycloakComponentConfigs => Set<KeycloakComponentConfig>();
    public DbSet<KeycloakRealm> KeycloakRealms => Set<KeycloakRealm>();
    public DbSet<KeycloakBackup> KeycloakBackups => Set<KeycloakBackup>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // Suppress the PendingModelChangesWarning during development.
        // The model may temporarily drift from the snapshot while iterating
        // on entity design — this prevents the app from failing to start.

        optionsBuilder.ConfigureWarnings(w =>
            w.Ignore(RelationalEventId.PendingModelChangesWarning));
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        // Tenant — the slug must be unique across all tenants since it's
        // used as the URL-safe identifier in routes and API calls.

        builder.Entity<Tenant>(entity =>
        {
            entity.HasKey(t => t.Id);
            entity.HasIndex(t => t.Slug).IsUnique();
            entity.Property(t => t.Name).HasMaxLength(200).IsRequired();
            entity.Property(t => t.Slug).HasMaxLength(100).IsRequired();
        });

        // TenantRole — each role name must be unique within its tenant.
        // A tenant owns its roles; deleting a tenant cascades to its roles.

        builder.Entity<TenantRole>(entity =>
        {
            entity.HasKey(r => r.Id);
            entity.HasIndex(r => new { r.TenantId, r.Name }).IsUnique();
            entity.Property(r => r.Name).HasMaxLength(100).IsRequired();

            entity.HasOne(r => r.Tenant)
                .WithMany(t => t.Roles)
                .HasForeignKey(r => r.TenantId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // TenantMembership — composite key on (UserId, TenantId) ensures
        // a user can only have one membership per tenant. The role FK tells
        // us what they can do in that tenant.

        builder.Entity<TenantMembership>(entity =>
        {
            entity.HasKey(m => new { m.UserId, m.TenantId });

            entity.HasOne(m => m.User)
                .WithMany()
                .HasForeignKey(m => m.UserId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(m => m.Tenant)
                .WithMany(t => t.Memberships)
                .HasForeignKey(m => m.TenantId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(m => m.Role)
                .WithMany(r => r.Memberships)
                .HasForeignKey(m => m.RoleId)
                .OnDelete(DeleteBehavior.Restrict);
        });

        // Group — belongs to a tenant. Name should be unique within a tenant.

        builder.Entity<Group>(entity =>
        {
            entity.HasKey(g => g.Id);
            entity.HasIndex(g => new { g.TenantId, g.Name }).IsUnique();
            entity.Property(g => g.Name).HasMaxLength(200).IsRequired();

            entity.HasOne(g => g.Tenant)
                .WithMany(t => t.Groups)
                .HasForeignKey(g => g.TenantId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // GroupMembership — composite key on (UserId, GroupId) prevents
        // a user from being added to the same group twice.

        builder.Entity<GroupMembership>(entity =>
        {
            entity.HasKey(gm => new { gm.UserId, gm.GroupId });

            entity.HasOne(gm => gm.User)
                .WithMany()
                .HasForeignKey(gm => gm.UserId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(gm => gm.Group)
                .WithMany(g => g.Memberships)
                .HasForeignKey(gm => gm.GroupId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // Environment — belongs to a tenant. Name must be unique within a tenant.

        builder.Entity<Environment>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.HasIndex(e => new { e.TenantId, e.Name }).IsUnique();
            entity.Property(e => e.Name).HasMaxLength(100).IsRequired();

            entity.HasOne(e => e.Tenant)
                .WithMany(t => t.Environments)
                .HasForeignKey(e => e.TenantId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // Customer — belongs to a tenant. Name must be unique within a tenant.

        builder.Entity<Customer>(entity =>
        {
            entity.HasKey(c => c.Id);
            entity.HasIndex(c => new { c.TenantId, c.Name }).IsUnique();
            entity.Property(c => c.Name).HasMaxLength(200).IsRequired();

            entity.HasOne(c => c.Tenant)
                .WithMany(t => t.Customers)
                .HasForeignKey(c => c.TenantId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // App — belongs to a customer. Name must be unique within a customer.

        builder.Entity<App>(entity =>
        {
            entity.HasKey(a => a.Id);
            entity.HasIndex(a => new { a.CustomerId, a.Name }).IsUnique();
            entity.Property(a => a.Name).HasMaxLength(200).IsRequired();

            entity.HasOne(a => a.Customer)
                .WithMany(c => c.Apps)
                .HasForeignKey(a => a.CustomerId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // AppEnvironment — many-to-many join between App and Environment.
        // Composite key prevents duplicate links.

        builder.Entity<AppEnvironment>(entity =>
        {
            entity.HasKey(ae => new { ae.AppId, ae.EnvironmentId });

            entity.HasOne(ae => ae.App)
                .WithMany(a => a.AppEnvironments)
                .HasForeignKey(ae => ae.AppId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(ae => ae.Environment)
                .WithMany(e => e.AppEnvironments)
                .HasForeignKey(ae => ae.EnvironmentId)
                .OnDelete(DeleteBehavior.Restrict);
        });

        // KubernetesCluster — belongs to a tenant and an environment.
        // Name must be unique within a tenant.

        builder.Entity<KubernetesCluster>(entity =>
        {
            entity.HasKey(c => c.Id);
            entity.HasIndex(c => new { c.TenantId, c.Name }).IsUnique();
            entity.Property(c => c.Name).HasMaxLength(200).IsRequired();
            entity.Property(c => c.ApiServerUrl).HasMaxLength(500).IsRequired();

            entity.HasOne(c => c.Tenant)
                .WithMany(t => t.KubernetesClusters)
                .HasForeignKey(c => c.TenantId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(c => c.Environment)
                .WithMany(e => e.KubernetesClusters)
                .HasForeignKey(c => c.EnvironmentId)
                .OnDelete(DeleteBehavior.Restrict);
        });

        // SecretVault — one vault per tenant. Stores the sealed Data Encryption Key.
        // A tenant can have at most one vault (1:1 relationship).

        builder.Entity<SecretVault>(entity =>
        {
            entity.HasKey(v => v.Id);
            entity.HasIndex(v => v.TenantId).IsUnique();

            entity.HasOne(v => v.Tenant)
                .WithOne(t => t.Vault)
                .HasForeignKey<SecretVault>(v => v.TenantId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // VaultSecret — an individual encrypted secret. Scoped to either an App
        // or a ClusterComponent. Name must be unique within its scope.

        builder.Entity<VaultSecret>(entity =>
        {
            entity.HasKey(s => s.Id);
            entity.Property(s => s.Name).HasMaxLength(200).IsRequired();
            entity.Property(s => s.KubernetesSecretName).HasMaxLength(253);
            entity.Property(s => s.KubernetesNamespace).HasMaxLength(63);

            entity.HasIndex(s => new { s.VaultId, s.AppId, s.Name })
                .IsUnique()
                .HasFilter(null);

            entity.HasIndex(s => new { s.VaultId, s.ComponentId, s.Name })
                .IsUnique()
                .HasFilter(null);

            entity.HasOne(s => s.Vault)
                .WithMany(v => v.Secrets)
                .HasForeignKey(s => s.VaultId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(s => s.App)
                .WithMany(a => a.Secrets)
                .HasForeignKey(s => s.AppId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(s => s.Component)
                .WithMany(c => c.Secrets)
                .HasForeignKey(s => s.ComponentId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(s => s.CnpgDatabase)
                .WithMany()
                .HasForeignKey(s => s.CnpgDatabaseId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(s => s.MongoDatabase)
                .WithMany()
                .HasForeignKey(s => s.MongoDatabaseId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(s => s.MongoCluster)
                .WithMany()
                .HasForeignKey(s => s.MongoClusterId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // ClusterComponent — a deployable unit (Helm chart, operator, etc.) on a cluster.
        // Name must be unique within a cluster.

        builder.Entity<ClusterComponent>(entity =>
        {
            entity.HasKey(c => c.Id);
            entity.HasIndex(c => new { c.ClusterId, c.Name }).IsUnique();
            entity.Property(c => c.Name).HasMaxLength(200).IsRequired();
            entity.Property(c => c.ComponentType).HasMaxLength(50).IsRequired();

            entity.HasOne(c => c.Cluster)
                .WithMany(k => k.Components)
                .HasForeignKey(c => c.ClusterId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // ExternalRoute — exposes a component via Gateway API HTTPRoute.
        // Hostname must be unique within a cluster (enforced via component→cluster).

        builder.Entity<ExternalRoute>(entity =>
        {
            entity.HasKey(r => r.Id);
            entity.Property(r => r.Hostname).HasMaxLength(253).IsRequired();
            entity.Property(r => r.ServiceName).HasMaxLength(200);
            entity.Property(r => r.PathPrefix).HasMaxLength(200);
            entity.Property(r => r.ClusterIssuerName).HasMaxLength(200);
            entity.Property(r => r.GatewayName).HasMaxLength(200);
            entity.Property(r => r.GatewayNamespace).HasMaxLength(63);
            entity.Property(r => r.TlsMode).HasConversion<string>().HasMaxLength(20);

            entity.HasOne(r => r.Component)
                .WithMany(c => c.ExternalRoutes)
                .HasForeignKey(r => r.ComponentId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // AppDeployment — a deployable unit targeting a specific cluster and namespace.
        // Name must be unique within an app. Stores sync/health status like ArgoCD.

        builder.Entity<AppDeployment>(entity =>
        {
            entity.HasKey(d => d.Id);
            entity.HasIndex(d => new { d.AppId, d.Name }).IsUnique();
            entity.Property(d => d.Name).HasMaxLength(200).IsRequired();
            entity.Property(d => d.Namespace).HasMaxLength(63).IsRequired();
            entity.Property(d => d.Type).HasConversion<string>().HasMaxLength(20);
            entity.Property(d => d.SyncStatus).HasConversion<string>().HasMaxLength(20);
            entity.Property(d => d.HealthStatus).HasConversion<string>().HasMaxLength(20);
            entity.Property(d => d.HelmRepoUrl).HasMaxLength(500);
            entity.Property(d => d.HelmChartName).HasMaxLength(200);
            entity.Property(d => d.HelmChartVersion).HasMaxLength(50);

            entity.HasOne(d => d.App)
                .WithMany(a => a.Deployments)
                .HasForeignKey(d => d.AppId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(d => d.Environment)
                .WithMany()
                .HasForeignKey(d => d.EnvironmentId)
                .OnDelete(DeleteBehavior.Restrict);

            entity.HasOne(d => d.Cluster)
                .WithMany()
                .HasForeignKey(d => d.ClusterId)
                .OnDelete(DeleteBehavior.Restrict);
        });

        // DeploymentManifest — an individual K8s YAML document within a deployment.
        // Applied in SortOrder sequence. Name is informational, not necessarily unique.

        builder.Entity<DeploymentManifest>(entity =>
        {
            entity.HasKey(m => m.Id);
            entity.Property(m => m.Kind).HasMaxLength(100).IsRequired();
            entity.Property(m => m.Name).HasMaxLength(253).IsRequired();

            entity.HasOne(m => m.Deployment)
                .WithMany(d => d.Manifests)
                .HasForeignKey(m => m.DeploymentId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // DeploymentResource — a tracked live resource in the cluster (ArgoCD-style
        // resource tree). Resources form a parent-child hierarchy for tree rendering.

        builder.Entity<DeploymentResource>(entity =>
        {
            entity.HasKey(r => r.Id);
            entity.Property(r => r.Group).HasMaxLength(100).IsRequired();
            entity.Property(r => r.Version).HasMaxLength(20).IsRequired();
            entity.Property(r => r.Kind).HasMaxLength(100).IsRequired();
            entity.Property(r => r.Name).HasMaxLength(253).IsRequired();
            entity.Property(r => r.Namespace).HasMaxLength(63);
            entity.Property(r => r.SyncStatus).HasConversion<string>().HasMaxLength(20);
            entity.Property(r => r.HealthStatus).HasConversion<string>().HasMaxLength(20);

            entity.HasOne(r => r.Deployment)
                .WithMany(d => d.Resources)
                .HasForeignKey(r => r.DeploymentId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(r => r.ParentResource)
                .WithMany(r => r.ChildResources)
                .HasForeignKey(r => r.ParentResourceId)
                .OnDelete(DeleteBehavior.Restrict);
        });

        // CustomerAccess — composite key on (UserId, CustomerId) ensures
        // a user gets exactly one access entry per customer. The Role enum
        // controls what they can do (Viewer, Operator, Admin).

        builder.Entity<CustomerAccess>(entity =>
        {
            entity.HasKey(ca => new { ca.UserId, ca.CustomerId });

            entity.Property(ca => ca.Role)
                .HasConversion<string>()
                .HasMaxLength(20);

            entity.HasOne(ca => ca.User)
                .WithMany()
                .HasForeignKey(ca => ca.UserId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(ca => ca.Customer)
                .WithMany()
                .HasForeignKey(ca => ca.CustomerId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // StorageBinding — connects a StorageLink to a workload (AppDeployment or
        // ClusterComponent). The platform syncs storage credentials to a K8s Secret
        // in the workload's namespace so pods can consume them.

        builder.Entity<StorageBinding>(entity =>
        {
            entity.HasKey(b => b.Id);
            entity.Property(b => b.KubernetesSecretName).HasMaxLength(253).IsRequired();

            entity.HasOne(b => b.StorageLink)
                .WithMany(s => s.StorageBindings)
                .HasForeignKey(b => b.StorageLinkId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(b => b.AppDeployment)
                .WithMany(d => d.StorageBindings)
                .HasForeignKey(b => b.AppDeploymentId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(b => b.Component)
                .WithMany(c => c.StorageBindings)
                .HasForeignKey(b => b.ComponentId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        builder.Entity<CnpgCluster>(entity =>
        {
            entity.HasKey(c => c.Id);
            entity.Property(c => c.Name).HasMaxLength(63).IsRequired();
            entity.Property(c => c.Namespace).HasMaxLength(63).IsRequired();
            entity.Property(c => c.PostgresVersion).HasMaxLength(10).IsRequired();
            entity.Property(c => c.StorageSize).HasMaxLength(20).IsRequired();
            entity.Property(c => c.BackupSchedule).HasMaxLength(100);

            entity.HasIndex(c => new { c.KubernetesClusterId, c.Name, c.Namespace }).IsUnique();

            entity.HasOne(c => c.Tenant)
                .WithMany()
                .HasForeignKey(c => c.TenantId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(c => c.KubernetesCluster)
                .WithMany()
                .HasForeignKey(c => c.KubernetesClusterId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(c => c.StorageLink)
                .WithMany()
                .HasForeignKey(c => c.StorageLinkId)
                .OnDelete(DeleteBehavior.SetNull);
        });

        builder.Entity<CnpgDatabase>(entity =>
        {
            entity.HasKey(d => d.Id);
            entity.Property(d => d.Name).HasMaxLength(63).IsRequired();
            entity.Property(d => d.Owner).HasMaxLength(63).IsRequired();

            entity.HasIndex(d => new { d.CnpgClusterId, d.Name }).IsUnique();

            entity.HasOne(d => d.CnpgCluster)
                .WithMany(c => c.Databases)
                .HasForeignKey(d => d.CnpgClusterId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        builder.Entity<CnpgBackup>(entity =>
        {
            entity.HasKey(b => b.Id);
            entity.Property(b => b.Name).HasMaxLength(253).IsRequired();

            entity.HasIndex(b => new { b.CnpgClusterId, b.Name }).IsUnique();

            entity.HasOne(b => b.CnpgCluster)
                .WithMany(c => c.Backups)
                .HasForeignKey(b => b.CnpgClusterId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        builder.Entity<MongoCluster>(entity =>
        {
            entity.HasKey(c => c.Id);
            entity.Property(c => c.Name).HasMaxLength(63).IsRequired();
            entity.Property(c => c.Namespace).HasMaxLength(63).IsRequired();
            entity.Property(c => c.MongoVersion).HasMaxLength(20).IsRequired();
            entity.Property(c => c.StorageSize).HasMaxLength(20).IsRequired();
            entity.Property(c => c.BackupSchedule).HasMaxLength(100);

            entity.HasIndex(c => new { c.KubernetesClusterId, c.Name, c.Namespace }).IsUnique();

            entity.HasOne(c => c.Tenant)
                .WithMany()
                .HasForeignKey(c => c.TenantId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(c => c.KubernetesCluster)
                .WithMany()
                .HasForeignKey(c => c.KubernetesClusterId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(c => c.StorageLink)
                .WithMany()
                .HasForeignKey(c => c.StorageLinkId)
                .OnDelete(DeleteBehavior.SetNull);
        });

        builder.Entity<MongoDatabase>(entity =>
        {
            entity.HasKey(d => d.Id);
            entity.Property(d => d.Name).HasMaxLength(63).IsRequired();

            entity.HasIndex(d => new { d.MongoClusterId, d.Name }).IsUnique();

            entity.HasOne(d => d.MongoCluster)
                .WithMany(c => c.Databases)
                .HasForeignKey(d => d.MongoClusterId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        builder.Entity<MongoBackup>(entity =>
        {
            entity.HasKey(b => b.Id);
            entity.Property(b => b.Name).HasMaxLength(253).IsRequired();

            entity.HasIndex(b => new { b.MongoClusterId, b.Name }).IsUnique();

            entity.HasOne(b => b.MongoCluster)
                .WithMany(c => c.Backups)
                .HasForeignKey(b => b.MongoClusterId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // KeycloakComponentConfig — links a detected Keycloak ClusterComponent to its
        // backing CNPG database. DB credentials and the admin password are stored as
        // component vault secrets (ComponentId = ClusterComponentId) and synced to K8s.

        builder.Entity<KeycloakComponentConfig>(entity =>
        {
            entity.HasKey(c => c.Id);
            entity.Property(c => c.AdminUsername).HasMaxLength(100).IsRequired();
            entity.Property(c => c.AdminUrl).HasMaxLength(500);

            entity.HasOne(c => c.Tenant)
                .WithMany()
                .HasForeignKey(c => c.TenantId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(c => c.ClusterComponent)
                .WithMany()
                .HasForeignKey(c => c.ClusterComponentId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(c => c.CnpgDatabase)
                .WithMany()
                .HasForeignKey(c => c.CnpgDatabaseId)
                .OnDelete(DeleteBehavior.SetNull);
        });

        // KeycloakRealm — a realm managed via a component config. RealmName must be
        // unique within a config (Keycloak enforces this globally per server).

        builder.Entity<KeycloakRealm>(entity =>
        {
            entity.HasKey(r => r.Id);
            entity.Property(r => r.RealmName).HasMaxLength(100).IsRequired();
            entity.Property(r => r.DisplayName).HasMaxLength(200).IsRequired();

            entity.HasIndex(r => new { r.KeycloakComponentConfigId, r.RealmName }).IsUnique();

            entity.HasOne(r => r.ComponentConfig)
                .WithMany(c => c.Realms)
                .HasForeignKey(r => r.KeycloakComponentConfigId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(r => r.LinkedApp)
                .WithMany()
                .HasForeignKey(r => r.LinkedAppId)
                .OnDelete(DeleteBehavior.SetNull);
        });

        // KeycloakBackup — realm JSON snapshots stored in S3.

        builder.Entity<KeycloakBackup>(entity =>
        {
            entity.HasKey(b => b.Id);
            entity.Property(b => b.ObjectKey).HasMaxLength(1024).IsRequired();
            entity.Property(b => b.RealmName).HasMaxLength(100).IsRequired();

            entity.HasOne(b => b.Realm)
                .WithMany(r => r.Backups)
                .HasForeignKey(b => b.KeycloakRealmId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(b => b.StorageLink)
                .WithMany()
                .HasForeignKey(b => b.StorageLinkId)
                .OnDelete(DeleteBehavior.Restrict);
        });

        // DatabaseBinding — connects a managed database (CNPG or MongoDB) to an
        // AppDeployment. The platform syncs credentials into the app's namespace
        // automatically, including after password rotations.

        builder.Entity<DatabaseBinding>(entity =>
        {
            entity.HasKey(b => b.Id);
            entity.Property(b => b.KubernetesSecretName).HasMaxLength(253).IsRequired();

            entity.HasOne(b => b.CnpgDatabase)
                .WithMany(d => d.DatabaseBindings)
                .HasForeignKey(b => b.CnpgDatabaseId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(b => b.MongoDatabase)
                .WithMany(d => d.DatabaseBindings)
                .HasForeignKey(b => b.MongoDatabaseId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(b => b.AppDeployment)
                .WithMany(d => d.DatabaseBindings)
                .HasForeignKey(b => b.AppDeploymentId)
                .OnDelete(DeleteBehavior.Cascade);
        });
    }
}

/// <summary>
/// PostgreSQL-specific context. Used only for generating and applying
/// PostgreSQL migrations. Shares the same model as the base context.
/// </summary>
public class PostgresApplicationDbContext(DbContextOptions<PostgresApplicationDbContext> options)
    : ApplicationDbContext(options)
{
}

/// <summary>
/// SQL Server-specific context. Used only for generating and applying
/// SQL Server migrations. Shares the same model as the base context.
/// </summary>
public class SqlServerApplicationDbContext(DbContextOptions<SqlServerApplicationDbContext> options)
    : ApplicationDbContext(options)
{
}
