namespace ZB.MOM.WW.OtOpcUa.Driver.Sql;

/// <summary>
///     Resolves a <c>Sql</c> driver config's <c>connectionStringRef</c> — a NAME — into the connection string
///     it stands for (design §8.2). The deployed artifact carries only the name, so database credentials never
///     ride in a config blob, never land in the central config DB, and never appear in a deployment diff.
///     <para><b>The environment is read directly, on purpose.</b> A driver factory is invoked through
///     <c>DriverFactoryRegistry</c>'s <c>(driverInstanceId, driverConfigJson)</c> closure — there is no
///     <c>IConfiguration</c>, no <c>IServiceProvider</c> and no ambient scope at that seam, and widening the
///     registry's signature for one driver would change every driver in the tree. The
///     <c>Sql__ConnectionStrings__&lt;ref&gt;</c> spelling is exactly the key .NET's environment-variable
///     configuration provider would bind to <c>Sql:ConnectionStrings:&lt;ref&gt;</c>, so an operator can set it
///     the same way they set every other secret and a later move onto <c>IConfiguration</c> needs no
///     re-provisioning.</para>
///     <para><b>Nothing here ever emits the resolved value.</b> Messages name the environment
///     <em>variable</em>, which is not a secret and is the operator's next action; the value itself is returned
///     to exactly one caller and is otherwise unmentioned.</para>
/// </summary>
public static class SqlConnectionStringResolver
{
    /// <summary>
    ///     The environment-variable prefix a <c>connectionStringRef</c> is appended to. The double underscore
    ///     is .NET's configuration hierarchy separator, so this is the environment spelling of
    ///     <c>Sql:ConnectionStrings:&lt;ref&gt;</c>.
    /// </summary>
    public const string EnvironmentVariablePrefix = "Sql__ConnectionStrings__";

    /// <summary>The environment variable a given <c>connectionStringRef</c> is read from.</summary>
    /// <param name="connectionStringRef">The authored reference name.</param>
    /// <returns>The full environment-variable name.</returns>
    /// <exception cref="ArgumentException"><paramref name="connectionStringRef"/> is null, empty or whitespace.</exception>
    public static string EnvironmentVariableFor(string connectionStringRef)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(connectionStringRef);
        return string.Concat(EnvironmentVariablePrefix, connectionStringRef);
    }

    /// <summary>
    ///     Resolves <paramref name="connectionStringRef"/> to its connection string.
    ///     <para>An absent <b>or blank</b> variable is a half-provisioned deployment and throws: handing an
    ///     empty string to the provider would surface as an unintelligible ADO.NET error at the first poll,
    ///     far from the missing secret that actually caused it.</para>
    /// </summary>
    /// <param name="connectionStringRef">The authored reference name.</param>
    /// <returns>The resolved connection string. <b>Treat as a secret</b> — never log it.</returns>
    /// <exception cref="ArgumentException"><paramref name="connectionStringRef"/> is null, empty or whitespace.</exception>
    /// <exception cref="InvalidOperationException">The environment variable is absent or blank.</exception>
    public static string Resolve(string connectionStringRef)
    {
        var variable = EnvironmentVariableFor(connectionStringRef);
        var value = Environment.GetEnvironmentVariable(variable);
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new InvalidOperationException(
                $"Sql connection string '{connectionStringRef}' is not provisioned: set the environment " +
                $"variable '{variable}' on every node that runs this driver. The connection string is " +
                $"deliberately not carried in the deployed configuration.");
        }

        return value;
    }
}
