using Microsoft.Extensions.Logging.Abstractions;
using Opc.Ua.Server;
using Shouldly;
using Xunit;

namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;

/// <summary>
/// #81 residual — verifies <see cref="SdkServiceLevelPublisher"/> locates the standard
/// <c>VariableIds.Server_ServiceLevel</c> node through the SDK's DiagnosticsNodeManager and
/// writes the byte value. Boots a real <see cref="StandardServer"/> on a free port so the
/// SDK populates its predefined diagnostics nodes — that's what production sees.
/// </summary>
public sealed class SdkServiceLevelPublisherTests : IDisposable
{
    private static CancellationToken Ct => TestContext.Current.CancellationToken;

    private readonly string _pkiRoot = Path.Combine(
        Path.GetTempPath(),
        $"otopcua-pki-{Guid.NewGuid():N}");

    /// <summary>Verifies that the publisher writes values to the standard Server.ServiceLevel variable.</summary>
    [Fact]
    public async Task Publish_writes_value_to_Server_ServiceLevel_variable()
    {
        var server = new StandardServer();
        await using var host = new OpcUaApplicationHost(
            new OpcUaApplicationHostOptions
            {
                ApplicationName = "OtOpcUa.SvcLevel",
                ApplicationUri = $"urn:OtOpcUa.SvcLevel:{Guid.NewGuid():N}",
                OpcUaPort = AllocateFreePort(),
                PublicHostname = "localhost",
                PkiStoreRoot = _pkiRoot,
            },
            NullLogger<OpcUaApplicationHost>.Instance);

        await host.StartAsync(server, Ct);

        var publisher = new SdkServiceLevelPublisher(
            server.CurrentInstance,
            NullLogger<SdkServiceLevelPublisher>.Instance);

        publisher.Publish(200);

        var variable = server.CurrentInstance.ServerObject.ServiceLevel;
        variable.ShouldNotBeNull("Server.ServiceLevel must be present in the address space");
        variable.Value.ShouldBe((byte)200);
    }

    /// <summary>Verifies that publishing service level values is idempotent when called multiple times.</summary>
    [Fact]
    public async Task Publish_is_idempotent_when_called_multiple_times()
    {
        var server = new StandardServer();
        await using var host = new OpcUaApplicationHost(
            new OpcUaApplicationHostOptions
            {
                ApplicationName = "OtOpcUa.SvcLevel.Idem",
                ApplicationUri = $"urn:OtOpcUa.SvcLevel.Idem:{Guid.NewGuid():N}",
                OpcUaPort = AllocateFreePort(),
                PublicHostname = "localhost",
                PkiStoreRoot = _pkiRoot,
            },
            NullLogger<OpcUaApplicationHost>.Instance);

        await host.StartAsync(server, Ct);
        var publisher = new SdkServiceLevelPublisher(
            server.CurrentInstance,
            NullLogger<SdkServiceLevelPublisher>.Instance);

        publisher.Publish(100);
        publisher.Publish(150);
        publisher.Publish(240);

        server.CurrentInstance.ServerObject.ServiceLevel.Value.ShouldBe((byte)240);
    }

    private static int AllocateFreePort()
    {
        using var listener = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Loopback, 0);
        listener.Start();
        var port = ((System.Net.IPEndPoint)listener.LocalEndpoint).Port;
        listener.Stop();
        return port;
    }

    /// <summary>Disposes and cleans up the test resources.</summary>
    public void Dispose()
    {
        if (Directory.Exists(_pkiRoot))
        {
            try { Directory.Delete(_pkiRoot, recursive: true); }
            catch { /* best-effort */ }
        }
    }
}
