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

/// <summary>
///     Parsed <c>ab://gateway[:port]/cip-path</c> host-address string used by the AbCip driver
///     as the <c>hostName</c> key across <see cref="Core.Abstractions.IHostConnectivityProbe"/>,
///     <see cref="Core.Abstractions.IPerCallHostResolver"/>, and the Polly per-host pipeline key
///     <c>(DriverInstanceId, hostName)</c>.
/// </summary>
/// <remarks>
///     <para>Format matches what libplctag's <c>gateway=...</c> + <c>path=...</c> attributes
///     consume, so no translation is needed at the wire layer — the parsed <see cref="CipPath"/>
///     is handed to the native library verbatim.</para>
///     <list type="bullet">
///         <item><c>ab://10.0.0.5/1,0</c> — single-chassis ControlLogix, CPU in slot 0.</item>
///         <item><c>ab://10.0.0.5/1,4</c> — CPU in slot 4.</item>
///         <item><c>ab://10.0.0.5/1,2,2,192.168.50.20,1,0</c> — bridged ControlLogix.</item>
///         <item><c>ab://10.0.0.5/</c> (empty path) — Micro800 / MicroLogix without backplane routing.</item>
///         <item><c>ab://10.0.0.5:44818/1,0</c> — explicit EIP port (default 44818).</item>
///     </list>
///     <para>Opaque to the rest of the stack: Admin UI, telemetry, and logs display the full
///     string so an incident ticket can be matched to the exact gateway + CIP route.</para>
/// </remarks>
public sealed record AbCipHostAddress(string Gateway, int Port, string CipPath)
{
    /// <summary>Default EtherNet/IP TCP port — spec-reserved.</summary>
    public const int DefaultEipPort = 44818;

    /// <inheritdoc />
    public override string ToString() => Port == DefaultEipPort
        ? $"ab://{Gateway}/{CipPath}"
        : $"ab://{Gateway}:{Port}/{CipPath}";

    /// <summary>
    ///     Parses an ab:// host address string. Returns <c>null</c> on any malformed input — callers
    ///     should treat a null return as a config-validation failure rather than catching.
    /// </summary>
    /// <param name="value">The ab:// URL string to parse.</param>
    /// <returns>A parsed host address, or null if the value is invalid.</returns>
    public static AbCipHostAddress? TryParse(string? value)
    {
        if (string.IsNullOrWhiteSpace(value)) return null;
        const string prefix = "ab://";
        if (!value.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return null;

        var remainder = value[prefix.Length..];
        var slashIdx = remainder.IndexOf('/');
        if (slashIdx < 0) return null;

        var authority = remainder[..slashIdx];
        var cipPath = remainder[(slashIdx + 1)..];
        if (string.IsNullOrEmpty(authority)) return null;

        var port = DefaultEipPort;
        var colonIdx = authority.LastIndexOf(':');
        string gateway;
        if (colonIdx >= 0)
        {
            gateway = authority[..colonIdx];
            if (!int.TryParse(authority[(colonIdx + 1)..], out port) || port <= 0 || port > 65535)
                return null;
        }
        else
        {
            gateway = authority;
        }
        if (string.IsNullOrEmpty(gateway)) return null;

        return new AbCipHostAddress(gateway, port, cipPath);
    }
}
