using System.Text.Json;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Types;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

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

/// <summary>
///     v3 (B1-WP4) identity — the shared <see cref="RawPathResolver"/> must build a raw tag's RawPath by
///     walking RawFolder → Driver → Device → TagGroup ancestry, and <see cref="DriverDeviceConfigMerger"/>
///     must fold the endpoint + RawTags into the single driver config. This is the byte-parity authority
///     both the deploy artifact and the draft validator compute against.
/// </summary>
public sealed class RawPathResolverTests
{
    private static RawPathResolver Nested() => new(
        folders: new Dictionary<string, (string?, string)>
        {
            ["f1"] = (null, "Plant"),
            ["f2"] = ("f1", "Line1"),
        },
        drivers: new Dictionary<string, (string?, string)>
        {
            ["drv1"] = ("f2", "Modbus1"),
            ["drvRoot"] = (null, "S7Root"),
        },
        devices: new Dictionary<string, (string, string)>
        {
            ["dev1"] = ("drv1", "PLC-A"),
            ["devRoot"] = ("drvRoot", "PLC-B"),
        },
        groups: new Dictionary<string, (string?, string)>
        {
            ["g1"] = (null, "Motors"),
            ["g2"] = ("g1", "M1"),
        });

    [Fact]
    public void TryBuildTagPath_walks_folder_driver_device_group_ancestry()
    {
        Nested().TryBuildTagPath("dev1", "g2", "Speed")
            .ShouldBe("Plant/Line1/Modbus1/PLC-A/Motors/M1/Speed");
    }

    [Fact]
    public void TryBuildTagPath_device_root_tag_has_no_group_segments()
    {
        Nested().TryBuildTagPath("dev1", null, "Status")
            .ShouldBe("Plant/Line1/Modbus1/PLC-A/Status");
    }

    [Fact]
    public void TryBuildTagPath_cluster_root_driver_has_no_folder_segments()
    {
        Nested().TryBuildTagPath("devRoot", null, "Run")
            .ShouldBe("S7Root/PLC-B/Run");
    }

    [Fact]
    public void TryBuildTagPath_unknown_device_returns_null()
    {
        Nested().TryBuildTagPath("ghost", null, "X").ShouldBeNull();
    }

    [Fact]
    public void TryBuildDriverPrefix_is_folders_plus_driver_name()
    {
        Nested().TryBuildDriverPrefix("drv1").ShouldBe("Plant/Line1/Modbus1");
    }

    [Fact]
    public void Merge_single_device_folds_endpoint_up_and_injects_rawtags()
    {
        var merged = DriverDeviceConfigMerger.Merge(
            driverConfigJson: "{\"UnitId\":3}",
            devices: new[] { new DriverDeviceConfigMerger.DeviceRow("PLC-A", "{\"Host\":\"10.0.0.5\",\"Port\":502}") },
            rawTags: new[] { new RawTagEntry("Plant/Modbus1/PLC-A/Speed", "{\"address\":\"40001\"}", true, "PLC-A") });

        using var doc = JsonDocument.Parse(merged);
        var root = doc.RootElement;
        root.GetProperty("UnitId").GetInt32().ShouldBe(3);
        root.GetProperty("Host").GetString().ShouldBe("10.0.0.5"); // single-device endpoint merged up
        root.GetProperty("Port").GetInt32().ShouldBe(502);
        var rawTags = root.GetProperty("RawTags").EnumerateArray().ToList();
        rawTags.ShouldHaveSingleItem();
        rawTags[0].GetProperty("RawPath").GetString().ShouldBe("Plant/Modbus1/PLC-A/Speed");
        var devices = root.GetProperty("Devices").EnumerateArray().ToList();
        devices.ShouldHaveSingleItem();
        devices[0].GetProperty("DeviceName").GetString().ShouldBe("PLC-A");
    }

    [Fact]
    public void Merge_multi_device_reconstructs_devices_array_with_names()
    {
        var merged = DriverDeviceConfigMerger.Merge(
            driverConfigJson: "{\"Series\":\"0i\"}",
            devices: new[]
            {
                new DriverDeviceConfigMerger.DeviceRow("PLC-A", "{\"HostAddress\":\"10.0.0.5:8193\"}"),
                new DriverDeviceConfigMerger.DeviceRow("PLC-B", "{\"HostAddress\":\"10.0.0.6:8193\"}"),
            },
            rawTags: Array.Empty<RawTagEntry>());

        using var doc = JsonDocument.Parse(merged);
        var devices = doc.RootElement.GetProperty("Devices").EnumerateArray().ToList();
        devices.Count.ShouldBe(2);
        devices.Select(d => d.GetProperty("DeviceName").GetString()).ShouldBe(new[] { "PLC-A", "PLC-B" });
        devices[0].GetProperty("HostAddress").GetString().ShouldBe("10.0.0.5:8193");
        // Multiple devices: no top-level endpoint merge-up (per-device only).
        doc.RootElement.TryGetProperty("HostAddress", out _).ShouldBeFalse();
    }
}
