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

/// <summary>
///     Parsed TwinCAT AMS address — six-octet AMS Net ID + port. Canonical form
///     <c>ads://{netId}:{port}</c> where <c>netId</c> is five-dot-separated octets (six of them)
///     and <c>port</c> is the AMS service port (851 = TC3 PLC runtime 1, 852 = runtime 2, 801 /
///     811 / 821 = TC2 PLC runtimes, 10000 = system service, etc.).
/// </summary>
/// <remarks>
///     Format examples:
///     <list type="bullet">
///         <item><c>ads://5.23.91.23.1.1:851</c> — remote TC3 runtime</item>
///         <item><c>ads://5.23.91.23.1.1</c> — defaults to port 851 (TC3 PLC runtime 1)</item>
///         <item><c>ads://127.0.0.1.1.1:851</c> — local loopback (when the router is local)</item>
///     </list>
///     <para>AMS Net ID is NOT an IP — it's a Beckhoff-specific identifier that the router
///     translates to an IP route. Typically the first four octets match the host's IPv4 and
///     the last two are <c>.1.1</c>, but the router can be configured otherwise.</para>
/// </remarks>
public sealed record TwinCATAmsAddress(string NetId, int Port)
{
    /// <summary>Default AMS port — TC3 PLC runtime 1.</summary>
    public const int DefaultPlcPort = 851;

    /// <inheritdoc />
    public override string ToString() => Port == DefaultPlcPort
        ? $"ads://{NetId}"
        : $"ads://{NetId}:{Port}";

    /// <summary>Parses a TwinCAT AMS address from a string.</summary>
    /// <param name="value">The address string to parse.</param>
    /// <returns>The parsed AMS address, or null if parsing fails.</returns>
    public static TwinCATAmsAddress? TryParse(string? value)
    {
        if (string.IsNullOrWhiteSpace(value)) return null;
        const string prefix = "ads://";
        if (!value.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return null;

        var body = value[prefix.Length..];
        if (string.IsNullOrEmpty(body)) return null;

        var colonIdx = body.LastIndexOf(':');
        string netId;
        var port = DefaultPlcPort;
        if (colonIdx >= 0)
        {
            netId = body[..colonIdx];
            if (!int.TryParse(body[(colonIdx + 1)..], out port) || port is <= 0 or > 65535)
                return null;
        }
        else
        {
            netId = body;
        }

        if (!IsValidNetId(netId)) return null;
        return new TwinCATAmsAddress(netId, port);
    }

    private static bool IsValidNetId(string netId)
    {
        var parts = netId.Split('.');
        if (parts.Length != 6) return false;
        foreach (var p in parts)
            if (!byte.TryParse(p, out _)) return false;
        return true;
    }
}
