using Shouldly;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;

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

/// <summary>
///     Tests for <see cref="OpcUaClientDriverFactoryExtensions"/> — the factory that lets the
///     Server-side <c>DriverFactoryBootstrap</c> materialise a real <see cref="OpcUaClientDriver"/>
///     from a <c>DriverInstance</c> row instead of falling back to a stub.
/// </summary>
public class OpcUaClientDriverFactoryTests
{
    private const string SampleConfig =
        """{"EndpointUrl":"opc.tcp://host:4840","SecurityMode":"None","AutoAcceptCertificates":true}""";

    /// <summary>Verifies the factory builds a driver carrying the right type + instance identity.</summary>
    [Fact]
    public void CreateInstance_builds_an_OpcUaClient_driver_with_the_right_identity()
    {
        var driver = OpcUaClientDriverFactoryExtensions.CreateInstance("drv-1", SampleConfig, NullLoggerFactory.Instance);
        driver.DriverType.ShouldBe("OpcUaClient");
        driver.DriverInstanceId.ShouldBe("drv-1");
    }

    /// <summary>Verifies the public driver-type-name constant matches the driver's DriverType.</summary>
    [Fact]
    public void DriverTypeName_is_OpcUaClient()
        => OpcUaClientDriverFactoryExtensions.DriverTypeName.ShouldBe("OpcUaClient");

    /// <summary>Verifies a JSON literal that deserialises to null is rejected with a clear error.</summary>
    [Fact]
    public void CreateInstance_throws_on_null_json_deserialisation()
        => Should.Throw<System.InvalidOperationException>(
            () => OpcUaClientDriverFactoryExtensions.CreateInstance("drv-1", "null", NullLoggerFactory.Instance));

    private const string RawTagsConfig =
        """
        {
          "EndpointUrl": "opc.tcp://host:4840",
          "RawTags": [
            { "rawPath": "Plant/Line1/Temp", "tagConfig": "{\"nodeId\":\"ns=2;s=Temperature\"}", "writeIdempotent": false },
            { "rawPath": "Plant/Line1/Speed", "tagConfig": "{\"nodeId\":\"ns=2;s=Speed\"}", "writeIdempotent": true }
          ]
        }
        """;

    /// <summary>
    ///     Verifies the v3 authored <c>RawTags</c> (RawPath + TagConfig blob + WriteIdempotent) bind
    ///     through the DriverConfig deserialiser directly onto <see cref="OpcUaClientDriverOptions"/> —
    ///     the OpcUaClient driver has no separate DTO, so the factory's direct-into-options path must
    ///     populate the RawPath resolution list.
    /// </summary>
    [Fact]
    public void CreateInstance_binds_rawtags_from_driverconfig()
    {
        var driver = OpcUaClientDriverFactoryExtensions.CreateInstance("drv-raw", RawTagsConfig, NullLoggerFactory.Instance);
        // The factory succeeds and identity is intact — the deserialiser tolerated + bound the RawTags array.
        driver.DriverType.ShouldBe("OpcUaClient");
        driver.DriverInstanceId.ShouldBe("drv-raw");

        // Assert the actual binding: deserialise the same DriverConfig into public options with the
        // factory's serialiser settings (case-insensitive + string enums) and confirm RawTags populated.
        var opts = System.Text.Json.JsonSerializer.Deserialize<OpcUaClientDriverOptions>(
            RawTagsConfig,
            new System.Text.Json.JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true,
                Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() },
            });
        opts.ShouldNotBeNull();
        opts!.RawTags.Count.ShouldBe(2);
        opts.RawTags[0].RawPath.ShouldBe("Plant/Line1/Temp");
        opts.RawTags[0].TagConfig.ShouldContain("ns=2;s=Temperature");
        opts.RawTags[0].WriteIdempotent.ShouldBeFalse();
        opts.RawTags[1].WriteIdempotent.ShouldBeTrue();
    }
}
