using System.Text.Json;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Types;
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;

namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers;

/// <summary>
///     v3 cross-seam characterization net for the raw-tag path. Injects every
///     <see cref="TagConfigGoldenCorpus"/> blob as a raw <c>Tag</c>'s <c>TagConfig</c> under a
///     RawFolder → Driver → Device chain, flattens the snapshot through
///     <see cref="DeploymentArtifact.ParseDriverInstances(System.ReadOnlySpan{byte})"/> into the driver's
///     merged <c>RawTags</c> array (keyed by RawPath), and asserts over the WHOLE corpus that:
///     <list type="number">
///       <item>every corpus tag survives (none dropped) and lands at its expected RawPath;</item>
///       <item>the decoded <see cref="RawTagEntry.TagConfig"/> is byte-identical to the authored blob
///             (the artifact carries the raw address blob verbatim — no PascalCase/FullName rewrite); and</item>
///       <item><see cref="TagConfigIntent.Parse"/> over the decoded blob equals the parse over the original
///             blob (record value equality incl. the nested alarm intent) — so any downstream intent
///             consumer sees identical platform intents whether it reads the authored Tag or the deploy
///             artifact. The permanent regression guard for the compose→encode→decode raw-tag seam.</item>
///     </list>
///     <b>Dark address space (Batch 1):</b> the same snapshot yields an EMPTY equipment-tag plan set —
///     equipment-tag variable materialization lands in Batch 4 (asserted here so the darkness is pinned).
/// </summary>
public sealed class TagConfigCorpusParityTests
{
    private const string Folder = "Plant";
    private const string DriverName = "Modbus1";
    private const string DeviceName = "PLC-A";

    private static byte[] Snapshot() => JsonSerializer.SerializeToUtf8Bytes(new
    {
        Clusters = new[] { new { ClusterId = "MAIN" } },
        RawFolders = new[]
        {
            new { RawFolderId = "f1", ParentRawFolderId = (string?)null, Name = Folder, ClusterId = "MAIN" },
        },
        DriverInstances = new[]
        {
            new { DriverInstanceId = "drv1", Name = DriverName, DriverType = "Modbus", Enabled = true, DriverConfig = "{\"UnitId\":1}", ClusterId = "MAIN", RawFolderId = "f1" },
        },
        Devices = new[]
        {
            new { DeviceId = "dev1", DriverInstanceId = "drv1", Name = DeviceName, DeviceConfig = "{\"Host\":\"10.0.0.5\",\"Port\":502}" },
        },
        Tags = TagConfigGoldenCorpus.Blobs
            .Select((blob, i) => new
            {
                TagId = $"tag-{i:D2}",
                DeviceId = "dev1",
                TagGroupId = (string?)null,
                Name = $"Sig{i:D2}",
                DataType = "Float",
                TagConfig = blob,
                WriteIdempotent = i % 2 == 0,
            })
            .ToArray(),
    });

    /// <summary>Byte-parity + intent-parity of every corpus blob round-tripped through the deploy artifact
    /// as a raw tag's TagConfig (keyed by RawPath).</summary>
    [Fact]
    public void Artifact_round_trips_every_TagConfig_blob_byte_and_intent_identical()
    {
        var blob = Snapshot();

        var spec = DeploymentArtifact.ParseDriverInstances(blob).ShouldHaveSingleItem();
        using var doc = JsonDocument.Parse(spec.DriverConfig);
        var rawTags = doc.RootElement.GetProperty("RawTags").EnumerateArray().ToList();

        // (1) No corpus tag dropped.
        rawTags.Count.ShouldBe(TagConfigGoldenCorpus.Blobs.Count);

        for (var i = 0; i < TagConfigGoldenCorpus.Blobs.Count; i++)
        {
            var expectedBlob = TagConfigGoldenCorpus.Blobs[i];
            var expectedRawPath = $"{Folder}/{DriverName}/{DeviceName}/Sig{i:D2}";

            var entry = rawTags.SingleOrDefault(e => e.GetProperty("RawPath").GetString() == expectedRawPath);
            entry.ValueKind.ShouldNotBe(JsonValueKind.Undefined, $"corpus blob #{i} missing at RawPath {expectedRawPath}");

            // (2) The raw address blob is carried verbatim (no rewrite).
            var decodedTagConfig = entry.GetProperty("TagConfig").GetString();
            decodedTagConfig.ShouldBe(expectedBlob, $"corpus blob #{i} TagConfig not byte-preserved");

            // (3) The platform intent parses identically off the decoded blob and the authored blob.
            TagConfigIntent.Parse(decodedTagConfig)
                .ShouldBe(TagConfigIntent.Parse(expectedBlob), $"corpus blob #{i} intent diverged: '{expectedBlob}'");
        }
    }

    /// <summary>Dark address space this batch: the same snapshot materializes no equipment-tag plans.</summary>
    [Fact]
    public void ParseComposition_over_the_corpus_emits_dark_equipment_tags()
    {
        DeploymentArtifact.ParseComposition(Snapshot()).EquipmentTags.ShouldBeEmpty();
    }
}
