using Opc.Ua;
using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;

namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests.Adapters;

/// <summary>
/// Covers <see cref="RealOpcUaClient.RewriteEndpointHostForReachability"/> — the fix for OPC UA
/// servers (e.g. OtOpcUa v3) that advertise an unroutable base address (a 0.0.0.0 wildcard bind or
/// an internal container/NAT hostname). The session must dial the host the operator reached, not
/// the advertised one, while preserving the advertised scheme + path.
/// </summary>
public class RealOpcUaClientEndpointRewriteTests
{
    private static string? Rewrite(string advertised, string discovery)
    {
        var ep = new EndpointDescription(advertised);
        RealOpcUaClient.RewriteEndpointHostForReachability(ep, discovery);
        return ep.EndpointUrl;
    }

    [Fact]
    public void Rewrites_0_0_0_0_advertisement_to_the_reachable_host_preserving_path()
    {
        Assert.Equal(
            "opc.tcp://otopcua-dev-site-a-1-1:4840/OtOpcUa",
            Rewrite("opc.tcp://0.0.0.0:4840/OtOpcUa", "opc.tcp://otopcua-dev-site-a-1-1:4840/OtOpcUa"));
    }

    [Fact]
    public void Rewrites_internal_nat_hostname_to_the_reachable_host()
    {
        Assert.Equal(
            "opc.tcp://plc.example.com:4840/OtOpcUa",
            Rewrite("opc.tcp://be5667fab9da:4840/OtOpcUa", "opc.tcp://plc.example.com:4840/OtOpcUa"));
    }

    [Fact]
    public void Rewrites_port_too_when_the_advertised_port_differs()
    {
        Assert.Equal(
            "opc.tcp://gateway:51210/OtOpcUa",
            Rewrite("opc.tcp://0.0.0.0:4840/OtOpcUa", "opc.tcp://gateway:51210/OtOpcUa"));
    }

    [Fact]
    public void Leaves_an_already_reachable_advertisement_untouched()
    {
        // opc-plc advertises its real container name — must not be rewritten.
        Assert.Equal(
            "opc.tcp://scadabridge-opcua:50000",
            Rewrite("opc.tcp://scadabridge-opcua:50000", "opc.tcp://scadabridge-opcua:50000"));
    }

    [Fact]
    public void Handles_authority_only_advertisement_without_appending_a_trailing_slash()
    {
        Assert.Equal(
            "opc.tcp://host-b:4840",
            Rewrite("opc.tcp://0.0.0.0:4840", "opc.tcp://host-b:4840"));
    }

    [Fact]
    public void Re_wraps_an_ipv6_reachable_host_in_brackets()
    {
        Assert.Equal(
            "opc.tcp://[::1]:4840/OtOpcUa",
            Rewrite("opc.tcp://0.0.0.0:4840/OtOpcUa", "opc.tcp://[::1]:4840/OtOpcUa"));
    }

    [Fact]
    public void Emits_no_port_when_neither_url_carries_one()
    {
        // opc.tcp has no default port, so Uri.Port is -1 on both — must not emit ":-1".
        Assert.Equal(
            "opc.tcp://myhost/UaServer",
            Rewrite("opc.tcp://0.0.0.0/UaServer", "opc.tcp://myhost/UaServer"));
    }

    [Fact]
    public void Uses_the_reachable_port_when_the_advertisement_omits_one()
    {
        Assert.Equal(
            "opc.tcp://myhost:4840/UaServer",
            Rewrite("opc.tcp://0.0.0.0/UaServer", "opc.tcp://myhost:4840/UaServer"));
    }

    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData("not-a-uri")]
    public void Leaves_endpoint_unchanged_when_discovery_url_is_unusable(string discovery)
    {
        Assert.Equal("opc.tcp://0.0.0.0:4840/OtOpcUa", Rewrite("opc.tcp://0.0.0.0:4840/OtOpcUa", discovery));
    }

    [Fact]
    public void Null_endpoint_is_a_no_op()
    {
        // Must not throw.
        RealOpcUaClient.RewriteEndpointHostForReachability(null, "opc.tcp://host:4840");
    }
}
