using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.Commons.Interfaces.Services;
using ScadaLink.ConfigurationDatabase.Repositories;
using ScadaLink.ConfigurationDatabase.Services;

namespace ScadaLink.ConfigurationDatabase;

public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Registers the ScadaLinkDbContext with the provided SQL Server connection string.
    /// </summary>
    public static IServiceCollection AddConfigurationDatabase(this IServiceCollection services, string connectionString)
    {
        // The DbContext is constructed via the (options, IDataProtectionProvider) overload so
        // secret-bearing configuration columns are encrypted at rest. AddDataProtection below
        // registers IDataProtectionProvider as a singleton; resolving it here does not recurse
        // because key-ring loading is lazy (first Protect/Unprotect), not triggered by
        // CreateProtector during model building.
        services.AddDbContext<ScadaLinkDbContext>((serviceProvider, options) =>
        {
            options.UseSqlServer(connectionString)
                .ConfigureWarnings(w => w.Ignore(
                    Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning));
        });

        // AddDbContext registers ScadaLinkDbContext via EF's activator, which only injects
        // DbContextOptions. Override that registration (last registration wins for resolution)
        // with a factory that also supplies the IDataProtectionProvider, so the encrypting
        // value converter for secret columns is always wired up at runtime.
        services.AddScoped(serviceProvider =>
        {
            var options = serviceProvider.GetRequiredService<DbContextOptions<ScadaLinkDbContext>>();
            var protectionProvider = serviceProvider.GetRequiredService<IDataProtectionProvider>();
            return new ScadaLinkDbContext(options, protectionProvider);
        });

        services.AddScoped<ISecurityRepository, SecurityRepository>();
        services.AddScoped<ICentralUiRepository, CentralUiRepository>();
        services.AddScoped<ITemplateEngineRepository, TemplateEngineRepository>();
        services.AddScoped<IDeploymentManagerRepository, DeploymentManagerRepository>();
        services.AddScoped<ISiteRepository, SiteRepository>();
        services.AddScoped<IExternalSystemRepository, ExternalSystemRepository>();
        services.AddScoped<INotificationRepository, NotificationRepository>();
        services.AddScoped<IInboundApiRepository, InboundApiRepository>();
        services.AddScoped<IAuditService, AuditService>();
        services.AddScoped<IInstanceLocator, InstanceLocator>();

        services.AddDataProtection()
            .PersistKeysToDbContext<ScadaLinkDbContext>();

        return services;
    }

    /// <summary>
    /// Obsolete parameterless overload. This previously registered nothing, which meant a
    /// central node wired up with it failed late and opaquely — the first repository
    /// resolution threw a DI exception far from the actual misconfiguration. Use
    /// <see cref="AddConfigurationDatabase(IServiceCollection, string)"/> and pass the
    /// configured connection string.
    /// </summary>
    /// <exception cref="InvalidOperationException">
    /// Always thrown. The connection string is required; there is no valid no-op registration.
    /// </exception>
    [Obsolete(
        "AddConfigurationDatabase() with no connection string registers nothing and is not a " +
        "valid configuration. Call AddConfigurationDatabase(connectionString) instead.",
        error: true)]
    public static IServiceCollection AddConfigurationDatabase(this IServiceCollection services)
    {
        // Defence-in-depth: even if a caller suppresses the compile-time obsolete error,
        // fail fast at wire-up time rather than silently registering nothing and surfacing
        // an opaque DI resolution failure much later.
        throw new InvalidOperationException(
            "AddConfigurationDatabase() requires a connection string. Call " +
            "AddConfigurationDatabase(connectionString) with the configured " +
            "'ScadaLink:Database:ConfigurationDb' value.");
    }
}
