using System.ComponentModel.DataAnnotations;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

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

/// <summary>
///     OPC UA Client (gateway) driver configuration. Bound from <c>DriverConfig</c> JSON at
///     driver-host registration time. Models the settings documented in
///     <c>docs/v2/driver-specs.md</c> §8.
/// </summary>
/// <remarks>
///     This driver connects to a REMOTE OPC UA server and re-exposes its address space
///     through the local OtOpcUa server — the opposite direction from the usual "server
///     exposes PLC data" flow. Tier A (pure managed, OPC Foundation reference SDK); universal
///     protections cover it.
/// </remarks>
// A record (not a plain class) so G-2b's OpcUaClientSecretResolution can produce a
// credential-resolved copy with a `with` expression — every property keeps its init setter.
public sealed record OpcUaClientDriverOptions
{
    /// <summary>
    ///     Remote OPC UA endpoint URL, e.g. <c>opc.tcp://plc.internal:4840</c>. Convenience
    ///     shortcut for a single-endpoint deployment — equivalent to setting
    ///     <see cref="EndpointUrls"/> to a list with this one URL. When both are provided,
    ///     the list wins and <see cref="EndpointUrl"/> is ignored.
    /// </summary>
    public string EndpointUrl { get; init; } = "opc.tcp://localhost:4840";

    /// <summary>
    ///     v3 authored raw tags for this driver instance — the deploy artifact (Wave C) populates this
    ///     with the cluster's raw OPC UA Client tags. Each <see cref="RawTagEntry"/> carries the tag's
    ///     <b>RawPath</b> identity plus a driver <c>TagConfig</c> blob whose <c>nodeId</c> key holds the
    ///     upstream OPC UA node id string the driver dials. The driver builds a <c>RawPath → nodeId</c>
    ///     resolution table (via <see cref="NamespaceMap"/>) from this list at connect time; a
    ///     read/write/subscribe/history reference is always a RawPath resolved through that table.
    /// </summary>
    public IReadOnlyList<RawTagEntry> RawTags { get; init; } = [];

    /// <summary>
    ///     Ordered list of candidate endpoint URLs for failover. The driver tries each in
    ///     order at <c>OpcUaClientDriver.InitializeAsync</c> and on session drop;
    ///     the first URL that successfully connects wins. Typical use-case: an OPC UA server
    ///     pair running in hot-standby (primary 4840 + backup 4841) where either can serve
    ///     the same address space. Leave unset (or empty) to use <see cref="EndpointUrl"/>
    ///     as a single-URL shortcut.
    /// </summary>
    public IReadOnlyList<string> EndpointUrls { get; init; } = [];

    /// <summary>
    ///     Per-endpoint connect-attempt timeout during the failover sweep. Short enough that
    ///     cycling through several dead servers doesn't blow the overall init budget, long
    ///     enough to tolerate a slow TLS handshake on a healthy server. Applied independently
    ///     of <see cref="Timeout"/> which governs steady-state operations.
    /// </summary>
    public TimeSpan PerEndpointConnectTimeout { get; init; } = TimeSpan.FromSeconds(3);

    /// <summary>
    ///     Security policy to require when selecting an endpoint. Either a
    ///     <see cref="OpcUaSecurityPolicy"/> enum constant or a free-form string (for
    ///     forward-compatibility with future OPC UA policies not yet in the enum).
    ///     Matched against <c>EndpointDescription.SecurityPolicyUri</c> suffix — the driver
    ///     connects to the first endpoint whose policy name matches AND whose mode matches
    ///     <see cref="SecurityMode"/>. When set to <see cref="OpcUaSecurityPolicy.None"/>
    ///     the driver picks any unsecured endpoint regardless of policy string.
    /// </summary>
    public OpcUaSecurityPolicy SecurityPolicy { get; init; } = OpcUaSecurityPolicy.None;

    /// <summary>Security mode.</summary>
    public OpcUaSecurityMode SecurityMode { get; init; } = OpcUaSecurityMode.None;

    /// <summary>Authentication type.</summary>
    public OpcUaAuthType AuthType { get; init; } = OpcUaAuthType.Anonymous;

    /// <summary>User name (required only for <see cref="OpcUaAuthType.Username"/>).</summary>
    public string? Username { get; init; }

    /// <summary>Password (required only for <see cref="OpcUaAuthType.Username"/>).</summary>
    public string? Password { get; init; }

    /// <summary>
    ///     Filesystem path to the user-identity certificate (PFX/PEM). Required when
    ///     <see cref="AuthType"/> is <see cref="OpcUaAuthType.Certificate"/>. The driver
    ///     loads the cert + private key, which the remote server validates against its
    ///     <c>TrustedUserCertificates</c> store to authenticate the session's user token.
    ///     Leave unset to use the driver's application-instance certificate as the user
    ///     token (not typical — most deployments have a separate user cert).
    /// </summary>
    public string? UserCertificatePath { get; init; }

    /// <summary>
    ///     Optional password that unlocks <see cref="UserCertificatePath"/> when the PFX is
    ///     protected. PEM files generally have their password on the adjacent key file; this
    ///     knob only applies to password-locked PFX.
    /// </summary>
    public string? UserCertificatePassword { get; init; }

    /// <summary>Server-negotiated session timeout. Default 120s per driver-specs.md §8.</summary>
    public TimeSpan SessionTimeout { get; init; } = TimeSpan.FromSeconds(120);

    /// <summary>Client-side keep-alive interval.</summary>
    public TimeSpan KeepAliveInterval { get; init; } = TimeSpan.FromSeconds(5);

    /// <summary>Initial reconnect delay after a session drop.</summary>
    public TimeSpan ReconnectPeriod { get; init; } = TimeSpan.FromSeconds(5);

    /// <summary>
    ///     When <c>true</c>, the driver accepts any self-signed / untrusted server certificate.
    ///     Dev-only — must be <c>false</c> in production so MITM attacks against the opc.tcp
    ///     channel fail closed.
    /// </summary>
    public bool AutoAcceptCertificates { get; init; } = false;

    /// <summary>
    ///     Application URI the driver reports during session creation. Must match the
    ///     subject-alt-name on the client certificate if one is used, which is why it's a
    ///     config knob rather than hard-coded.
    /// </summary>
    public string ApplicationUri { get; init; } = "urn:localhost:OtOpcUa:GatewayClient";

    /// <summary>
    ///     Friendly name sent to the remote server for diagnostics. Shows up in the remote
    ///     server's session-list so operators can identify which gateway instance is calling.
    /// </summary>
    public string SessionName { get; init; } = "OtOpcUa-Gateway";

    /// <summary>Connect + per-operation timeout.</summary>
    public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(10);

    /// <summary>
    ///     Root NodeId to mirror. Default <c>null</c> = <c>ObjectsFolder</c> (i=85). Set to
    ///     a scoped root to restrict the address space the driver exposes locally — useful
    ///     when the remote server has tens of thousands of nodes and only a subset is
    ///     needed downstream.
    /// </summary>
    public string? BrowseRoot { get; init; }

    /// <summary>
    ///     Cap on total nodes discovered during <c>DiscoverAsync</c>. Default 10_000 —
    ///     bounds memory on runaway remote servers without being so low that normal
    ///     deployments hit it. When the cap is reached discovery stops and a warning is
    ///     written to the driver health surface; the partially-discovered tree is still
    ///     projected into the local address space.
    /// </summary>
    [Range(1, int.MaxValue)]
    public int MaxDiscoveredNodes { get; init; } = 10_000;

    /// <summary>
    ///     Max hierarchical depth of the browse. Default 10 — deep enough for realistic
    ///     OPC UA information models, shallow enough that cyclic graphs can't spin the
    ///     browse forever.
    /// </summary>
    [Range(1, int.MaxValue)]
    public int MaxBrowseDepth { get; init; } = 10;

    /// <summary>
    ///     The namespace kind this driver instance gateways into. Per
    ///     <c>docs/v2/driver-specs.md</c> §8 "Namespace Assignment", the OPC UA Client driver
    ///     is the only driver that supports <b>either</b> kind, decided per instance:
    ///     <list type="bullet">
    ///         <item><see cref="OpcUaTargetNamespaceKind.Equipment"/> — gatewaying a remote
    ///         server that exposes raw equipment data; the driver remaps remote browse paths
    ///         to UNS.</item>
    ///         <item><see cref="OpcUaTargetNamespaceKind.SystemPlatform"/> — gatewaying a
    ///         remote server that exposes processed/derived data; hierarchy is preserved via
    ///         the remote browse path with no UNS conversion.</item>
    ///     </list>
    ///     The driver enforces the choice at startup — see
    ///     <c>OpcUaClientDriver.ValidateNamespaceKind</c> — so a misconfiguration fails
    ///     draft validation rather than surfacing as a runtime surprise.
    /// </summary>
    public OpcUaTargetNamespaceKind TargetNamespaceKind { get; init; } = OpcUaTargetNamespaceKind.Equipment;

    /// <summary>
    ///     Optional config-driven remote-to-UNS mapping table. Required when
    ///     <see cref="TargetNamespaceKind"/> is <see cref="OpcUaTargetNamespaceKind.Equipment"/>:
    ///     §8 mandates that a remote server gatewayed as Equipment carry a mapping table
    ///     because remote nodes do not conform to UNS by default. Keys are remote browse-path
    ///     prefixes; values are the UNS Area/Line/Name path the matching subtree maps onto.
    ///     For <see cref="OpcUaTargetNamespaceKind.SystemPlatform"/> the table must be empty —
    ///     processed data preserves its own hierarchy and a mapping table would be ambiguous.
    /// </summary>
    public IReadOnlyDictionary<string, string> UnsMappingTable { get; init; }
        = new Dictionary<string, string>();

    /// <summary>
    ///     Timeout for the AdminUI Test Connect probe, in seconds. The AdminUI clamps to a
    ///     60s server-side maximum; this default is what the form pre-fills for new instances.
    /// </summary>
    [Range(1, 60)]
    public int ProbeTimeoutSeconds { get; init; } = 15;
}

/// <summary>
///     The namespace kind an OPC UA Client driver instance gateways its remote server into.
///     See <c>docs/v2/driver-specs.md</c> §8 "Namespace Assignment".
/// </summary>
public enum OpcUaTargetNamespaceKind
{
    /// <summary>
    ///     Remote server exposes raw equipment data; remote browse paths are remapped to UNS
    ///     via <see cref="OpcUaClientDriverOptions.UnsMappingTable"/>.
    /// </summary>
    Equipment,

    /// <summary>
    ///     Remote server exposes processed/derived data; hierarchy is preserved via the
    ///     remote browse path with no UNS conversion.
    /// </summary>
    SystemPlatform,
}

/// <summary>OPC UA message security mode.</summary>
public enum OpcUaSecurityMode
{
    None,
    Sign,
    SignAndEncrypt,
}

/// <summary>
///     OPC UA security policies recognized by the driver. Maps to the standard
///     <c>http://opcfoundation.org/UA/SecurityPolicy#</c> URI suffixes the SDK uses for
///     endpoint matching.
/// </summary>
/// <remarks>
///     <see cref="Basic128Rsa15"/> and <see cref="Basic256"/> are <b>deprecated</b> per OPC UA
///     spec v1.04 — they remain in the enum only for brownfield interop with older servers.
///     Prefer <see cref="Basic256Sha256"/>, <see cref="Aes128_Sha256_RsaOaep"/>, or
///     <see cref="Aes256_Sha256_RsaPss"/> for new deployments.
/// </remarks>
public enum OpcUaSecurityPolicy
{
    /// <summary>No security. Unsigned, unencrypted wire.</summary>
    None,
    /// <summary>Deprecated (OPC UA 1.04). Retained for legacy server interop.</summary>
    Basic128Rsa15,
    /// <summary>Deprecated (OPC UA 1.04). Retained for legacy server interop.</summary>
    Basic256,
    /// <summary>Recommended baseline for current deployments.</summary>
    Basic256Sha256,
    /// <summary>Current OPC UA policy; AES-128 + SHA-256 + RSA-OAEP.</summary>
    Aes128_Sha256_RsaOaep,
    /// <summary>Current OPC UA policy; AES-256 + SHA-256 + RSA-PSS.</summary>
    Aes256_Sha256_RsaPss,
}

/// <summary>User authentication type sent to the remote server.</summary>
public enum OpcUaAuthType
{
    Anonymous,
    Username,
    Certificate,
}
