namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Security;

/// <summary>
/// Validates OPC UA UserName tokens. The SDK already decrypts the token (using the server
/// application cert) and hands the cleartext username + password to this seam. Implementations
/// decide whether the credentials are valid and what roles to attach for downstream ACL checks.
///
/// Production implementation lives in the Host project (wraps <c>ILdapAuthService</c>); the
/// <see cref="NullOpcUaUserAuthenticator"/> default rejects every attempt so misconfigured
/// dev nodes don't silently accept credentials.
/// </summary>
public interface IOpcUaUserAuthenticator
{
    /// <summary>
    /// Resolves cleartext UserName credentials against the configured backing store. Must not
    /// throw — callers turn results into <c>ImpersonateEventArgs.IdentityValidationError</c>
    /// reject codes, and a thrown exception escapes into the OPC UA SDK's session-activation
    /// path where it surfaces as a generic <c>BadInternalError</c>.
    /// </summary>
    /// <param name="username">The username to authenticate.</param>
    /// <param name="password">The cleartext password to authenticate.</param>
    /// <param name="ct">The cancellation token.</param>
    /// <returns>An OpcUaUserAuthResult indicating success or failure.</returns>
    Task<OpcUaUserAuthResult> AuthenticateUserNameAsync(string username, string password, CancellationToken ct);
}

/// <summary>Outcome of a UserName authentication attempt. <see cref="Roles"/> populates the session identity's role set.</summary>
public sealed record OpcUaUserAuthResult(
    bool Success,
    string? DisplayName,
    IReadOnlyList<string> Roles,
    string? Error)
{
    /// <summary>Creates a successful authentication result with the specified display name and roles.</summary>
    /// <param name="displayName">The display name for the authenticated user.</param>
    /// <param name="roles">The roles to assign to the authenticated user.</param>
    /// <returns>A successful OpcUaUserAuthResult.</returns>
    public static OpcUaUserAuthResult Allow(string displayName, IReadOnlyList<string> roles) =>
        new(true, displayName, roles, null);

    /// <summary>Creates a failed authentication result with the specified error message.</summary>
    /// <param name="error">The error message describing why authentication failed.</param>
    /// <returns>A failed OpcUaUserAuthResult.</returns>
    public static OpcUaUserAuthResult Deny(string error) =>
        new(false, null, Array.Empty<string>(), error);
}

/// <summary>
/// Default deny-all authenticator. Wired by <c>OpcUaApplicationHost</c> when no production
/// authenticator is registered in DI — keeps the server safe-by-default rather than accepting
/// arbitrary UserName credentials. Production Host DI overrides this with the LDAP adapter.
/// </summary>
public sealed class NullOpcUaUserAuthenticator : IOpcUaUserAuthenticator
{
    public static readonly NullOpcUaUserAuthenticator Instance = new();
    private NullOpcUaUserAuthenticator() { }

    /// <inheritdoc />
    public Task<OpcUaUserAuthResult> AuthenticateUserNameAsync(string username, string password, CancellationToken ct) =>
        Task.FromResult(OpcUaUserAuthResult.Deny("No UserName authenticator is configured on this server."));
}
