using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

namespace Baya.Web.Api.Configuration;

/// <summary>
/// Fail-fast validation that no load-bearing secret is missing or left at its committed placeholder.
/// A database connection is required in every real environment; the JWE + field-encryption keys are
/// required only in <b>deployed</b> environments (Development keeps working dev-only defaults in
/// <c>appsettings.Development.json</c>, and the "Testing" environment runs on in-memory SQLite with
/// test-injected keys). The effect: a fresh clone with no user-secrets stops at boot with a clear
/// message instead of silently connecting somewhere unintended, and a deployment can never fall back
/// to a committed placeholder key.
/// </summary>
public static class StartupSecretsGuard
{
    // Substrings that mark a value as a committed placeholder, never a real secret. Any configured value
    // containing one of these is treated as "not provided".
    private static readonly string[] PlaceholderMarkers =
    [
        "SET_VIA_USER_SECRETS_OR_ENV",
        "not-for-production",
        "change-me",
        "ShouldBe-LongerThan-16Char-SecretKey",
        "16CharEncryptKey"
    ];

    public static void ValidateRequiredSecrets(this WebApplicationBuilder builder)
    {
        // Integration tests boot as "Testing" over in-memory SQLite and inject their own crypto keys.
        if (builder.Environment.IsEnvironment("Testing"))
            return;

        var config = builder.Configuration;
        var errors = new List<string>();

        RequireReal(errors, "ConnectionStrings:SqlServer", config.GetConnectionString("SqlServer"));
        RequireReal(errors, "ConnectionStrings:logDb", config.GetConnectionString("logDb"));

        // Development supplies working dev-only keys via appsettings.Development.json; only deployed
        // environments must inject real per-environment secrets (env vars / Key Vault / KMS).
        if (!builder.Environment.IsDevelopment())
        {
            RequireReal(errors, "IdentitySettings:SecretKey", config["IdentitySettings:SecretKey"]);
            RequireReal(errors, "IdentitySettings:Encryptkey", config["IdentitySettings:Encryptkey"]);
            RequireReal(errors, "Seams:FieldEncryption:Key", config["Seams:FieldEncryption:Key"]);
            RequireReal(errors, "Seams:FieldEncryption:HashKey", config["Seams:FieldEncryption:HashKey"]);
        }

        if (errors.Count == 0)
            return;

        throw new InvalidOperationException(
            "Refusing to start: required secret configuration is missing or still a committed placeholder. " +
            "Provide real values via user-secrets (Development) or environment variables (deployed) — see " +
            "dev/post-phase/refinement/RUNBOOK.md.\n  - " + string.Join("\n  - ", errors));
    }

    private static void RequireReal(List<string> errors, string key, string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
            errors.Add($"{key} is not set.");
        else if (PlaceholderMarkers.Any(marker => value.Contains(marker, StringComparison.OrdinalIgnoreCase)))
            errors.Add($"{key} is still a committed placeholder.");
    }
}
