using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

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

/// <summary>
///     Scaffold-level tests for <see cref="OpcUaClientDriver"/> that don't require a live
///     remote OPC UA server. PR 67+ adds IReadable/IWritable/ITagDiscovery/ISubscribable
///     tests against a local in-process OPC UA server fixture.
/// </summary>
[Trait("Category", "Unit")]
public sealed class OpcUaClientDriverScaffoldTests
{
    /// <summary>Verifies that default options target the standard OPC UA port with anonymous auth.</summary>
    [Fact]
    public void Default_options_target_standard_opcua_port_and_anonymous_auth()
    {
        var opts = new OpcUaClientDriverOptions();
        opts.EndpointUrl.ShouldBe("opc.tcp://localhost:4840", "4840 is the IANA-assigned OPC UA port");
        opts.SecurityMode.ShouldBe(OpcUaSecurityMode.None);
        opts.SecurityPolicy.ShouldBe(OpcUaSecurityPolicy.None);
        opts.AuthType.ShouldBe(OpcUaAuthType.Anonymous);
        opts.AutoAcceptCertificates.ShouldBeFalse("production default must reject untrusted server certs");
    }

    /// <summary>Verifies that default timeouts match driver specification section 8.</summary>
    [Fact]
    public void Default_timeouts_match_driver_specs_section_8()
    {
        var opts = new OpcUaClientDriverOptions();
        opts.SessionTimeout.ShouldBe(TimeSpan.FromSeconds(120));
        opts.KeepAliveInterval.ShouldBe(TimeSpan.FromSeconds(5));
        opts.ReconnectPeriod.ShouldBe(TimeSpan.FromSeconds(5));
    }

    /// <summary>Verifies that driver reports type and ID before connecting.</summary>
    [Fact]
    public void Driver_reports_type_and_id_before_connect()
    {
        using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-test");
        drv.DriverType.ShouldBe("OpcUaClient");
        drv.DriverInstanceId.ShouldBe("opcua-test");
        drv.GetHealth().State.ShouldBe(DriverState.Unknown);
    }

    /// <summary>Verifies that Initialize against unreachable endpoint transitions to Faulted and throws.</summary>
    [Fact]
    public async Task Initialize_against_unreachable_endpoint_transitions_to_Faulted_and_throws()
    {
        // RFC 5737 reserved-for-documentation IP; won't route anywhere. Pick opc.tcp:// so
        // endpoint selection hits the transport-layer connection rather than a DNS lookup.
        var opts = new OpcUaClientDriverOptions
        {
            // Port 1 on loopback is effectively guaranteed to be closed — the OS responds
            // with TCP RST immediately instead of hanging on connect, which keeps the
            // unreachable-host tests snappy. Don't use an RFC 5737 reserved IP; those get
            // routed to a black-hole + time out only after the SDK's internal retry/backoff
            // fully elapses (~60s even with Options.Timeout=500ms).
            EndpointUrl = "opc.tcp://127.0.0.1:1",
            Timeout = TimeSpan.FromMilliseconds(500),
            AutoAcceptCertificates = true, // dev-mode to bypass cert validation in the test
        };
        using var drv = new OpcUaClientDriver(opts, "opcua-unreach");

        await Should.ThrowAsync<Exception>(async () =>
            await drv.InitializeAsync("{}", TestContext.Current.CancellationToken));

        var health = drv.GetHealth();
        health.State.ShouldBe(DriverState.Faulted);
        health.LastError.ShouldNotBeNull();
    }

    /// <summary>Verifies that Reinitialize against unreachable endpoint re-throws the error.</summary>
    [Fact]
    public async Task Reinitialize_against_unreachable_endpoint_re_throws()
    {
        var opts = new OpcUaClientDriverOptions
        {
            // Port 1 on loopback is effectively guaranteed to be closed — the OS responds
            // with TCP RST immediately instead of hanging on connect, which keeps the
            // unreachable-host tests snappy. Don't use an RFC 5737 reserved IP; those get
            // routed to a black-hole + time out only after the SDK's internal retry/backoff
            // fully elapses (~60s even with Options.Timeout=500ms).
            EndpointUrl = "opc.tcp://127.0.0.1:1",
            Timeout = TimeSpan.FromMilliseconds(500),
            AutoAcceptCertificates = true,
        };
        using var drv = new OpcUaClientDriver(opts, "opcua-reinit");

        await Should.ThrowAsync<Exception>(async () =>
            await drv.InitializeAsync("{}", TestContext.Current.CancellationToken));
        await Should.ThrowAsync<Exception>(async () =>
            await drv.ReinitializeAsync("{}", TestContext.Current.CancellationToken));
    }
}
