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

/// <summary>
///     LDAP settings for the OPC UA server's UserName token validator. Bound from
///     <c>appsettings.json</c> <c>OpcUaServer:Ldap</c>. Defaults target the GLAuth dev instance
///     (localhost:3893, <c>dc=lmxopcua,dc=local</c>) for the stock inner-loop setup. Production
///     deployments are expected to point at Active Directory; see <see cref="UserNameAttribute"/>
///     and the per-field xml-docs for the AD-specific overrides.
/// </summary>
/// <remarks>
///     <para><b>Active Directory cheat-sheet</b>:</para>
///     <list type="bullet">
///         <item><see cref="Server"/>: one of the domain controllers, or the domain FQDN (will round-robin DCs).</item>
///         <item><see cref="Port"/>: <c>389</c> (LDAP) or <c>636</c> (LDAPS); use 636 + <see cref="UseTls"/> in production.</item>
///         <item><see cref="UseTls"/>: <c>true</c>. AD increasingly rejects plain-LDAP bind under LDAP-signing enforcement.</item>
///         <item><see cref="AllowInsecureLdap"/>: <c>false</c>. Dev escape hatch only.</item>
///         <item><see cref="SearchBase"/>: <c>DC=corp,DC=example,DC=com</c> — your domain's base DN.</item>
///         <item><see cref="ServiceAccountDn"/>: a dedicated service principal with read access to user + group entries
///         (e.g. <c>CN=OpcUaSvc,OU=Service Accounts,DC=corp,DC=example,DC=com</c>). Never a privileged admin.</item>
///         <item><see cref="UserNameAttribute"/>: <c>sAMAccountName</c> (classic login name) or <c>userPrincipalName</c>
///         (user@domain form). Default is <c>uid</c> which AD does <b>not</b> populate, so this override is required.</item>
///         <item><see cref="DisplayNameAttribute"/>: <c>displayName</c> gives the human name; <c>cn</c> works too but is less rich.</item>
///         <item><see cref="GroupAttribute"/>: <c>memberOf</c> — matches AD's default. Values are full DNs
///         (<c>CN=&lt;Group&gt;,OU=...,DC=...</c>); the authenticator strips the leading <c>CN=</c> RDN value and uses
///         that as the lookup key in <see cref="GroupToRole"/>.</item>
///         <item><see cref="GroupToRole"/>: maps your AD group common-names to OPC UA roles — e.g.
///         <c>{"OPCUA-Operators" : "WriteOperate", "OPCUA-Engineers" : "WriteConfigure"}</c>.</item>
///     </list>
///     <para>
///         Nested groups are <b>not</b> expanded — AD's <c>tokenGroups</c> / <c>LDAP_MATCHING_RULE_IN_CHAIN</c>
///         membership-chain filter isn't used. Assign users directly to the role-mapped groups, or pre-flatten
///         membership in your directory. If nested expansion becomes a requirement, it's an authenticator
///         enhancement (not a config change).
///     </para>
/// </remarks>
public sealed class LdapOptions
{
    public bool Enabled { get; init; } = false;
    public string Server { get; init; } = "localhost";
    public int Port { get; init; } = 3893;
    public bool UseTls { get; init; } = false;

    /// <summary>Dev-only escape hatch — must be false in production.</summary>
    public bool AllowInsecureLdap { get; init; } = true;

    public string SearchBase { get; init; } = "dc=lmxopcua,dc=local";
    public string ServiceAccountDn { get; init; } = string.Empty;
    public string ServiceAccountPassword { get; init; } = string.Empty;
    public string DisplayNameAttribute { get; init; } = "cn";
    public string GroupAttribute { get; init; } = "memberOf";

    /// <summary>
    ///     LDAP attribute used to match a login name against user entries in the directory.
    ///     Defaults to <c>uid</c> (RFC 2307). Common overrides:
    ///     <list type="bullet">
    ///         <item><c>sAMAccountName</c> — Active Directory, classic NT-style login names (e.g. <c>jdoe</c>).</item>
    ///         <item><c>userPrincipalName</c> — Active Directory, email-style (e.g. <c>jdoe@corp.example.com</c>).</item>
    ///         <item><c>cn</c> — GLAuth + some OpenLDAP deployments where users are keyed by common-name.</item>
    ///     </list>
    ///     Used only when <see cref="ServiceAccountDn"/> is non-empty (search-then-bind path) —
    ///     direct-bind fallback constructs the DN as <c>cn=&lt;name&gt;,&lt;SearchBase&gt;</c>
    ///     regardless of this setting and is not a production-grade path against AD.
    /// </summary>
    public string UserNameAttribute { get; init; } = "uid";

    /// <summary>
    ///     LDAP group → OPC UA role. Each authenticated user gets every role whose source group
    ///     is in their membership list. Recognized role names (CLAUDE.md): <c>ReadOnly</c> (browse
    ///     + read), <c>WriteOperate</c>, <c>WriteTune</c>, <c>WriteConfigure</c>, <c>AlarmAck</c>.
    /// </summary>
    public Dictionary<string, string> GroupToRole { get; init; } = new(StringComparer.OrdinalIgnoreCase);
}
