using System.Net.Security;

namespace ZB.MOM.WW.Auth.Abstractions.Ldap;

public enum LdapTransport { Ldaps, StartTls, None }

public sealed record LdapOptions
{
    public bool Enabled { get; init; } = true;
    public string Server { get; init; } = "localhost";
    public int Port { get; init; } = 3893;
    public LdapTransport Transport { get; init; } = LdapTransport.Ldaps;
    public bool AllowInsecure { get; init; }
    public string SearchBase { get; init; } = "";
    public string ServiceAccountDn { get; init; } = "";
    public string ServiceAccountPassword { get; init; } = "";
    public string UserNameAttribute { get; init; } = "cn";
    public string DisplayNameAttribute { get; init; } = "cn";
    public string GroupAttribute { get; init; } = "memberOf";
    public int ConnectionTimeoutMs { get; init; } = 10_000;

    /// <summary>
    /// Optional hook to harden (or, in dev, relax) TLS server-certificate validation for the
    /// <see cref="LdapTransport.Ldaps"/> / <see cref="LdapTransport.StartTls"/> transports. When
    /// <see langword="null"/> (the default) the LDAP client validates the server certificate against
    /// the OS trust store — it does <em>not</em> blind-accept. Supply a callback to pin a CA, validate
    /// the SAN against <see cref="Server"/>, or otherwise tighten validation. This is a code-only seam
    /// (not bound from configuration) and takes precedence over <see cref="AllowInsecure"/>.
    /// </summary>
    public RemoteCertificateValidationCallback? ServerCertificateValidationCallback { get; init; }
}

public enum LdapAuthFailure { BadCredentials, UserNotFound, AmbiguousUser, GroupLookupFailed, ServiceAccountBindFailed, Disabled }

public sealed record LdapAuthResult(bool Succeeded, string Username, string DisplayName, IReadOnlyList<string> Groups, LdapAuthFailure? Failure)
{
    public static LdapAuthResult Success(string username, string displayName, IReadOnlyList<string> groups) => new(true, username, displayName, groups, null);
    public static LdapAuthResult Fail(LdapAuthFailure failure) => new(false, "", "", Array.Empty<string>(), failure);
}

public interface ILdapAuthService
{
    /// <summary>
    /// Authenticates <paramref name="username"/> against the directory by bind-then-search and
    /// returns the outcome, including the resolved display name and group memberships on success.
    /// </summary>
    /// <param name="username">The login name to authenticate.</param>
    /// <param name="password">The credential to bind with.</param>
    /// <param name="ct">A token to request cancellation of the operation.</param>
    /// <returns>The authentication result.</returns>
    /// <remarks>
    /// The cancellation token is observed at entry only. Implementations backed by synchronous
    /// LDAP clients cannot abort an in-flight bind or search once it has been dispatched, so full
    /// cooperative cancellation is not guaranteed mid-call: a request that has already reached the
    /// directory will run to completion (subject to the configured connection timeout) even if the
    /// token is cancelled.
    /// </remarks>
    Task<LdapAuthResult> AuthenticateAsync(string username, string password, CancellationToken ct);
}
