using Microsoft.Extensions.Options;

namespace DodoSSH.Api.Setup;

/// <summary>Binds and validates strongly-typed options.</summary>
/// <remarks>
/// Every section uses <c>ValidateOnStart</c>. A self-hosted server that boots half-configured and
/// fails later, per-request, is far harder to diagnose than one that refuses to start and says
/// which setting is wrong.
/// </remarks>
internal static class Configuration
{
    internal static IServiceCollection AddDodoOptions(this IServiceCollection services)
    {
        services.AddOptions<ServerOptions>()
            .BindConfiguration(ServerOptions.SectionName)
            .ValidateDataAnnotations()
            .ValidateOnStart();

        services.AddOptions<DatabaseOptions>()
            .BindConfiguration(DatabaseOptions.SectionName)
            .ValidateDataAnnotations()
            .ValidateOnStart();

        services.AddOptions<OidcOptions>()
            .BindConfiguration(OidcOptions.SectionName)
            .ValidateDataAnnotations()
            .ValidateOnStart();

        services.AddOptions<SyncOptions>()
            .BindConfiguration(SyncOptions.SectionName)
            .ValidateDataAnnotations()
            .Validate(
                options => options.MaxItemPayloadBytes <= options.MaxPayloadBytes,
                "Sync:MaxItemPayloadBytes must not exceed Sync:MaxPayloadBytes.")
            .Validate(
                options => options.DefaultPullLimit <= options.MaxPullLimit,
                "Sync:DefaultPullLimit must not exceed Sync:MaxPullLimit.")
            .ValidateOnStart();

        services.AddOptions<EventsOptions>()
            .BindConfiguration(EventsOptions.SectionName)
            .ValidateDataAnnotations()
            .Validate(
                options => options.HeartbeatInterval > TimeSpan.Zero,
                "Events:HeartbeatInterval must be greater than zero.")
            .Validate(
                options => options.AccessRefreshInterval > TimeSpan.Zero,
                "Events:AccessRefreshInterval must be greater than zero.")
            .Validate(
                options => options.MaxConnectionDuration > options.AccessRefreshInterval,
                "Events:MaxConnectionDuration must exceed Events:AccessRefreshInterval; a connection "
                + "that never lives long enough to re-read its own access has none of the bound that "
                + "setting exists to provide.")
            .ValidateOnStart();

        services.AddOptions<RelayOptions>()
            .BindConfiguration(RelayOptions.SectionName)
            .ValidateDataAnnotations()
            .Validate(
                options => !options.Enabled || !string.IsNullOrWhiteSpace(options.WebSocketUrl),
                "Relay:WebSocketUrl is required when Relay:Enabled is true.")
            .Validate(
                options => options.IdleTimeout < options.MaxSessionDuration,
                "Relay:IdleTimeout must be shorter than Relay:MaxSessionDuration.")
            .ValidateOnStart();

        return services;
    }

    /// <summary>
    /// Warns about configuration that is individually valid but dangerous in combination.
    /// </summary>
    /// <remarks>
    /// These cannot be hard failures — each is legitimate in some deployment — but silently
    /// accepting them produces subtly broken security properties that nobody notices.
    /// </remarks>
    internal static WebApplication WarnOnRiskyConfiguration(this WebApplication app)
    {
        var logger = app.Services.GetRequiredService<ILoggerFactory>().CreateLogger("DodoSSH.Startup");
        var oidc = app.Services.GetRequiredService<IOptions<OidcOptions>>().Value;

        if (!oidc.RequireHttpsMetadata && !app.Environment.IsDevelopment())
        {
            StartupLog.InsecureMetadataOutsideDevelopment(logger);
        }

        if (oidc.AllowEmailLinking)
        {
            StartupLog.EmailLinkingEnabled(logger);
        }

        return app;
    }
}
