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

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

/// <summary>
///     Task #220 — covers the DriverConfig JSON contract that
///     <see cref="FocasDriverFactoryExtensions.CreateInstance"/> parses when the bootstrap
///     pipeline (task #248) materialises FOCAS DriverInstance rows. Pure unit tests, no pipe
///     or CNC required.
/// </summary>
[Trait("Category", "Unit")]
public sealed class FocasDriverFactoryExtensionsTests
{
    [Fact]
    public void Register_adds_FOCAS_entry_to_registry()
    {
        var registry = new DriverFactoryRegistry();
        FocasDriverFactoryExtensions.Register(registry);
        registry.TryGet("FOCAS").ShouldNotBeNull();
    }

    [Fact]
    public void Register_is_case_insensitive_via_registry()
    {
        var registry = new DriverFactoryRegistry();
        FocasDriverFactoryExtensions.Register(registry);
        registry.TryGet("focas").ShouldNotBeNull();
        registry.TryGet("Focas").ShouldNotBeNull();
    }

    [Fact]
    public void CreateInstance_with_ipc_backend_and_valid_config_returns_FocasDriver()
    {
        const string json = """
        {
          "Backend": "ipc",
          "PipeName": "OtOpcUaFocasHost",
          "SharedSecret": "secret-for-test",
          "ConnectTimeoutMs": 5000,
          "Series": "Thirty_i",
          "TimeoutMs": 3000,
          "Devices": [
            { "HostAddress": "focas://10.0.0.5:8193", "DeviceName": "Lathe1" }
          ],
          "Tags": [
            { "Name": "Override", "DeviceHostAddress": "focas://10.0.0.5:8193",
              "Address": "R100", "DataType": "Int32", "Writable": true }
          ]
        }
        """;

        var driver = FocasDriverFactoryExtensions.CreateInstance("focas-0", json);

        driver.ShouldNotBeNull();
        driver.DriverInstanceId.ShouldBe("focas-0");
        driver.DriverType.ShouldBe("FOCAS");
    }

    [Fact]
    public void CreateInstance_defaults_Backend_to_ipc_when_unspecified()
    {
        // No "Backend" key → defaults to ipc → requires PipeName + SharedSecret.
        const string json = """
        { "PipeName": "p", "SharedSecret": "s" }
        """;
        var driver = FocasDriverFactoryExtensions.CreateInstance("focas-default", json);
        driver.DriverType.ShouldBe("FOCAS");
    }

    [Fact]
    public void CreateInstance_ipc_backend_missing_PipeName_throws()
    {
        const string json = """{ "Backend": "ipc", "SharedSecret": "s" }""";
        Should.Throw<InvalidOperationException>(
            () => FocasDriverFactoryExtensions.CreateInstance("focas-missing-pipe", json))
            .Message.ShouldContain("PipeName");
    }

    [Fact]
    public void CreateInstance_ipc_backend_missing_SharedSecret_throws()
    {
        const string json = """{ "Backend": "ipc", "PipeName": "p" }""";
        Should.Throw<InvalidOperationException>(
            () => FocasDriverFactoryExtensions.CreateInstance("focas-missing-secret", json))
            .Message.ShouldContain("SharedSecret");
    }

    [Fact]
    public void CreateInstance_fwlib_backend_does_not_require_pipe_fields()
    {
        // Direct in-process Fwlib32 path. No pipe config needed; driver connects the DLL
        // natively on first use.
        const string json = """{ "Backend": "fwlib" }""";
        var driver = FocasDriverFactoryExtensions.CreateInstance("focas-fwlib", json);
        driver.DriverInstanceId.ShouldBe("focas-fwlib");
    }

    [Fact]
    public void CreateInstance_unimplemented_backend_yields_driver_that_fails_fast_on_use()
    {
        // Useful for staging DriverInstance rows in the config DB before the Host is
        // actually deployed — the server boots but reads/writes surface clear errors.
        const string json = """{ "Backend": "unimplemented" }""";
        var driver = FocasDriverFactoryExtensions.CreateInstance("focas-unimpl", json);
        driver.DriverInstanceId.ShouldBe("focas-unimpl");
    }

    [Fact]
    public void CreateInstance_unknown_backend_throws_with_expected_list()
    {
        const string json = """{ "Backend": "gibberish", "PipeName": "p", "SharedSecret": "s" }""";
        Should.Throw<InvalidOperationException>(
            () => FocasDriverFactoryExtensions.CreateInstance("focas-bad-backend", json))
            .Message.ShouldContain("gibberish");
    }

    [Fact]
    public void CreateInstance_rejects_unknown_Series()
    {
        const string json = """
        { "Backend": "fwlib", "Series": "NotARealSeries" }
        """;
        Should.Throw<InvalidOperationException>(
            () => FocasDriverFactoryExtensions.CreateInstance("focas-bad-series", json))
            .Message.ShouldContain("NotARealSeries");
    }

    [Fact]
    public void CreateInstance_rejects_tag_with_missing_DataType()
    {
        const string json = """
        {
          "Backend": "fwlib",
          "Devices": [{ "HostAddress": "focas://1.1.1.1:8193" }],
          "Tags": [{ "Name": "Broken", "DeviceHostAddress": "focas://1.1.1.1:8193", "Address": "R1" }]
        }
        """;
        Should.Throw<InvalidOperationException>(
            () => FocasDriverFactoryExtensions.CreateInstance("focas-bad-tag", json))
            .Message.ShouldContain("DataType");
    }

    [Fact]
    public void CreateInstance_null_or_whitespace_args_rejected()
    {
        Should.Throw<ArgumentException>(
            () => FocasDriverFactoryExtensions.CreateInstance("", "{}"));
        Should.Throw<ArgumentException>(
            () => FocasDriverFactoryExtensions.CreateInstance("id", ""));
    }

    [Fact]
    public void Register_twice_throws()
    {
        var registry = new DriverFactoryRegistry();
        FocasDriverFactoryExtensions.Register(registry);
        Should.Throw<InvalidOperationException>(
            () => FocasDriverFactoryExtensions.Register(registry));
    }
}
