using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using ScadaLink.Commons.Entities.Audit;
using ScadaLink.Commons.Entities.Deployment;
using ScadaLink.Commons.Entities.ExternalSystems;
using ScadaLink.Commons.Entities.InboundApi;
using ScadaLink.Commons.Entities.Instances;
using ScadaLink.Commons.Entities.Notifications;
using ScadaLink.Commons.Entities.Scripts;
using ScadaLink.Commons.Entities.Security;
using ScadaLink.Commons.Entities.Sites;
using ScadaLink.Commons.Entities.Templates;

namespace ScadaLink.ConfigurationDatabase;

public class ScadaLinkDbContext : DbContext, IDataProtectionKeyContext
{
    private readonly IDataProtectionProvider? _dataProtectionProvider;

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

    /// <summary>
    /// Creates a context with an explicit Data Protection provider used to encrypt
    /// secret-bearing configuration columns at rest. The runtime resolves this overload
    /// via DI; design-time tooling uses the single-argument overload.
    /// </summary>
    public ScadaLinkDbContext(DbContextOptions<ScadaLinkDbContext> options, IDataProtectionProvider dataProtectionProvider)
        : base(options)
    {
        _dataProtectionProvider = dataProtectionProvider
            ?? throw new ArgumentNullException(nameof(dataProtectionProvider));
    }

    // Templates
    public DbSet<Template> Templates => Set<Template>();
    public DbSet<TemplateAttribute> TemplateAttributes => Set<TemplateAttribute>();
    public DbSet<TemplateAlarm> TemplateAlarms => Set<TemplateAlarm>();
    public DbSet<TemplateScript> TemplateScripts => Set<TemplateScript>();
    public DbSet<TemplateComposition> TemplateCompositions => Set<TemplateComposition>();
    public DbSet<TemplateFolder> TemplateFolders => Set<TemplateFolder>();

    // Instances
    public DbSet<Instance> Instances => Set<Instance>();
    public DbSet<InstanceAttributeOverride> InstanceAttributeOverrides => Set<InstanceAttributeOverride>();
    public DbSet<InstanceAlarmOverride> InstanceAlarmOverrides => Set<InstanceAlarmOverride>();
    public DbSet<InstanceConnectionBinding> InstanceConnectionBindings => Set<InstanceConnectionBinding>();
    public DbSet<Area> Areas => Set<Area>();

    // Sites
    public DbSet<Site> Sites => Set<Site>();
    public DbSet<DataConnection> DataConnections => Set<DataConnection>();

    // Deployment
    public DbSet<DeploymentRecord> DeploymentRecords => Set<DeploymentRecord>();
    public DbSet<SystemArtifactDeploymentRecord> SystemArtifactDeploymentRecords => Set<SystemArtifactDeploymentRecord>();
    public DbSet<DeployedConfigSnapshot> DeployedConfigSnapshots => Set<DeployedConfigSnapshot>();

    // External Systems
    public DbSet<ExternalSystemDefinition> ExternalSystemDefinitions => Set<ExternalSystemDefinition>();
    public DbSet<ExternalSystemMethod> ExternalSystemMethods => Set<ExternalSystemMethod>();
    public DbSet<DatabaseConnectionDefinition> DatabaseConnectionDefinitions => Set<DatabaseConnectionDefinition>();

    // Notifications
    public DbSet<NotificationList> NotificationLists => Set<NotificationList>();
    public DbSet<NotificationRecipient> NotificationRecipients => Set<NotificationRecipient>();
    public DbSet<SmtpConfiguration> SmtpConfigurations => Set<SmtpConfiguration>();

    // Scripts
    public DbSet<SharedScript> SharedScripts => Set<SharedScript>();

    // Security
    public DbSet<LdapGroupMapping> LdapGroupMappings => Set<LdapGroupMapping>();
    public DbSet<SiteScopeRule> SiteScopeRules => Set<SiteScopeRule>();

    // Inbound API
    public DbSet<ApiKey> ApiKeys => Set<ApiKey>();
    public DbSet<ApiMethod> ApiMethods => Set<ApiMethod>();

    // Audit
    public DbSet<AuditLogEntry> AuditLogEntries => Set<AuditLogEntry>();

    // Data Protection Keys (for shared ASP.NET Data Protection across nodes)
    public DbSet<DataProtectionKey> DataProtectionKeys => Set<DataProtectionKey>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(ScadaLinkDbContext).Assembly);

        ApplySecretColumnEncryption(modelBuilder);
    }

    /// <summary>
    /// Applies encryption-at-rest to columns that hold authentication secrets
    /// (SMTP credentials, external-system auth config, database connection strings)
    /// so they are never persisted as plaintext.
    /// </summary>
    /// <remarks>
    /// When no Data Protection provider is supplied (design-time <c>dotnet ef</c> tooling,
    /// which only emits schema and never reads or writes secret data), an ephemeral provider
    /// is used. The encrypted-column type is <c>nvarchar</c> either way, so the generated
    /// schema is identical regardless of which provider is in effect. The runtime path always
    /// receives the DI-registered provider whose keys are persisted to this database.
    /// </remarks>
    private void ApplySecretColumnEncryption(ModelBuilder modelBuilder)
    {
        IDataProtectionProvider provider = _dataProtectionProvider ?? new EphemeralDataProtectionProvider();
        var converter = new EncryptedStringConverter(
            provider.CreateProtector(EncryptedStringConverter.ProtectorPurpose));

        modelBuilder.Entity<SmtpConfiguration>()
            .Property(s => s.Credentials)
            .HasConversion(converter);

        modelBuilder.Entity<ExternalSystemDefinition>()
            .Property(e => e.AuthConfiguration)
            .HasConversion(converter);

        modelBuilder.Entity<DatabaseConnectionDefinition>()
            .Property(d => d.ConnectionString)
            .HasConversion((Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter)converter);
    }
}
