using System.Linq;
using System.Text.Json;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
using ZB.MOM.WW.OtOpcUa.OpcUaServer;
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;

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

/// <summary>
///     Byte-parity tests for the VirtualTag <c>Historize</c> flag across the artifact-decode path
///     (<c>DeploymentArtifact.BuildEquipmentVirtualTagPlans</c>) and the live compose seam
///     (<c>AddressSpaceComposer.Compose</c>) — H5b. The artifact JSON already carries a Pascal-case
///     <c>"Historize"</c> bool (ConfigComposer serialises the whole VirtualTag entity with
///     <c>DefaultIgnoreCondition.Never</c>); the decode just had to read it. Both sides default to
///     <c>false</c> when the flag is unset/absent/non-bool, so the decoded plans must equal the
///     composer's element-wise (the record has value equality including Historize). Mirrors
///     <c>DeploymentArtifactScriptedAlarmParityTests</c>.
/// </summary>
public sealed class DeploymentArtifactVirtualTagHistorizeParityTests
{
    private static byte[] BlobOf(object snapshot) => JsonSerializer.SerializeToUtf8Bytes(snapshot);

    [Fact]
    public void ParseComposition_virtual_tag_historize_is_byte_parity_with_composer()
    {
        var driver = new DriverInstance
        {
            DriverInstanceId = "drv-1",
            ClusterId = "c1",
            Name = "Modbus1",
            DriverType = "Modbus",
            DriverConfig = "{}",
        };
        var area = new UnsArea { UnsAreaId = "area-1", ClusterId = "c1", Name = "filling" };
        var line = new UnsLine { UnsLineId = "line-1", UnsAreaId = "area-1", Name = "line-1" };
        var equip = new Equipment { EquipmentId = "eq-1", UnsLineId = "line-1", Name = "TestMachine_001", MachineCode = "TESTMACHINE_001" };
        var script = new Script
        {
            ScriptId = "s-1",
            Name = "passthru",
            SourceCode = "return ctx.GetTag(\"TestMachine_001.Source\").Value;",
            SourceHash = "hash-1",
        };
        var vtHist = new VirtualTag { VirtualTagId = "vt-hist", EquipmentId = "eq-1", Name = "Historized", DataType = "Int32", ScriptId = "s-1", Historize = true };
        var vtPlain = new VirtualTag { VirtualTagId = "vt-plain", EquipmentId = "eq-1", Name = "Plain", DataType = "Int32", ScriptId = "s-1", Historize = false };

        var composed = AddressSpaceComposer.Compose(
            new[] { area }, new[] { line }, new[] { equip },
            new[] { driver }, Array.Empty<ScriptedAlarm>(),
            virtualTags: new[] { vtHist, vtPlain },
            scripts: new[] { script });

        // The artifact blob the write side (ConfigComposer) emits: the FULL VirtualTag entity
        // serialised Pascal-case off EF, including the Historize bool.
        var blob = BlobOf(new
        {
            Namespaces = new[] { new { NamespaceId = "ns-eq", Kind = 0 } },
            DriverInstances = new[]
            {
                new { DriverInstanceId = "drv-1", DriverType = "Modbus", DriverConfig = "{}", NamespaceId = "ns-eq" },
            },
            Tags = new object[]
            {
                new
                {
                    TagId = "tag-1",
                    DriverInstanceId = "drv-1",
                    EquipmentId = "eq-1",
                    Name = "Source",
                    FolderPath = (string?)null,
                    DataType = "Int32",
                    TagConfig = "{\"FullName\":\"TestMachine_001.Source\",\"DataType\":\"Int32\"}",
                },
            },
            Scripts = new[]
            {
                new { ScriptId = "s-1", SourceCode = "return ctx.GetTag(\"TestMachine_001.Source\").Value;" },
            },
            VirtualTags = new[]
            {
                ToSnapshot(vtHist),
                ToSnapshot(vtPlain),
            },
        });

        var decoded = DeploymentArtifact.ParseComposition(blob);

        decoded.EquipmentVirtualTags.Count.ShouldBe(2);
        decoded.EquipmentVirtualTags.Single(p => p.VirtualTagId == "vt-hist").Historize.ShouldBeTrue();
        decoded.EquipmentVirtualTags.Single(p => p.VirtualTagId == "vt-plain").Historize.ShouldBeFalse();

        // Byte-parity: element-wise value equality (record equality includes Historize).
        decoded.EquipmentVirtualTags.SequenceEqual(composed.EquipmentVirtualTags).ShouldBeTrue();
    }

    [Fact]
    public void ParseComposition_absent_historize_defaults_false()
    {
        // A VirtualTag object with NO "Historize" property at all (older artifact / unset) decodes
        // to false — same default the composer uses for an unset entity column.
        var blob = BlobOf(new
        {
            Namespaces = new[] { new { NamespaceId = "ns-eq", Kind = 0 } },
            DriverInstances = new[]
            {
                new { DriverInstanceId = "drv-1", DriverType = "Modbus", DriverConfig = "{}", NamespaceId = "ns-eq" },
            },
            Tags = new object[]
            {
                new
                {
                    TagId = "tag-1",
                    DriverInstanceId = "drv-1",
                    EquipmentId = "eq-1",
                    Name = "Source",
                    FolderPath = (string?)null,
                    DataType = "Int32",
                    TagConfig = "{\"FullName\":\"TestMachine_001.Source\"}",
                },
            },
            Scripts = new[]
            {
                new { ScriptId = "s-1", SourceCode = "return ctx.GetTag(\"TestMachine_001.Source\").Value;" },
            },
            VirtualTags = new[]
            {
                new { VirtualTagId = "vt-1", EquipmentId = "eq-1", Name = "NoHistorize", DataType = "Int32", ScriptId = "s-1" },
            },
        });

        var decoded = DeploymentArtifact.ParseComposition(blob);

        decoded.EquipmentVirtualTags.ShouldHaveSingleItem().Historize.ShouldBeFalse();
    }

    // The Pascal-case snapshot a VirtualTag EF entity serialises to (matches ConfigComposer's
    // DefaultIgnoreCondition.Never whole-entity serialisation — includes Historize).
    private static object ToSnapshot(VirtualTag v) => new
    {
        v.VirtualTagId,
        v.EquipmentId,
        v.Name,
        v.DataType,
        v.ScriptId,
        v.ChangeTriggered,
        v.TimerIntervalMs,
        v.Historize,
        v.Enabled,
    };
}
