using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.OpcUa;

namespace ZB.MOM.WW.OtOpcUa.Core.Tests.OpcUa;

[Trait("Category", "Unit")]
public sealed class IdentificationFolderBuilderTests
{
    /// <summary>Records folder and property additions for test verification.</summary>
    private sealed class RecordingBuilder : IAddressSpaceBuilder
    {
        /// <summary>Gets or sets the list of added folders.</summary>
        public List<(string BrowseName, string DisplayName)> Folders { get; } = [];

        /// <summary>Gets or sets the list of added properties.</summary>
        public List<(string BrowseName, DriverDataType DataType, object? Value)> Properties { get; } = [];

        /// <summary>Records a folder and returns this builder for chaining.</summary>
        /// <param name="browseName">The browse name of the folder.</param>
        /// <param name="displayName">The display name of the folder.</param>
        public IAddressSpaceBuilder Folder(string browseName, string displayName)
        {
            Folders.Add((browseName, displayName));
            return this; // flat recording — identification fields land in the same bucket
        }

        /// <summary>Not supported in test context.</summary>
        /// <param name="browseName">The browse name of the variable.</param>
        /// <param name="displayName">The display name of the variable.</param>
        /// <param name="attributeInfo">The attribute information.</param>
        public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo)
            => throw new NotSupportedException("Identification fields use AddProperty, not Variable");

        /// <summary>Records a property addition.</summary>
        /// <param name="browseName">The browse name of the property.</param>
        /// <param name="dataType">The data type of the property.</param>
        /// <param name="value">The property value.</param>
        public void AddProperty(string browseName, DriverDataType dataType, object? value)
            => Properties.Add((browseName, dataType, value));
    }

    private static Equipment EmptyEquipment() => new()
    {
        EquipmentId = "EQ-000000000001",
        UnsLineId = "line-1",
        Name = "eq-1",
        MachineCode = "machine_001",
    };

    private static Equipment FullyPopulatedEquipment() => new()
    {
        EquipmentId = "EQ-000000000001",
        UnsLineId = "line-1",
        Name = "eq-1",
        MachineCode = "machine_001",
        Manufacturer = "Siemens",
        Model = "S7-1500",
        SerialNumber = "SN-12345",
        HardwareRevision = "Rev-A",
        SoftwareRevision = "Fw-2.3.1",
        YearOfConstruction = 2023,
        AssetLocation = "Warsaw-West/Bldg-3",
        ManufacturerUri = "https://siemens.example",
        DeviceManualUri = "https://siemens.example/manual",
    };

    /// <summary>Verifies that HasAnyFields returns false when all fields are null.</summary>
    [Fact]
    public void HasAnyFields_AllNull_ReturnsFalse()
    {
        IdentificationFolderBuilder.HasAnyFields(EmptyEquipment()).ShouldBeFalse();
    }

    /// <summary>Verifies that HasAnyFields returns true when at least one field is non-null.</summary>
    [Fact]
    public void HasAnyFields_OneNonNull_ReturnsTrue()
    {
        var eq = EmptyEquipment();
        eq.SerialNumber = "SN-1";
        IdentificationFolderBuilder.HasAnyFields(eq).ShouldBeTrue();
    }

    /// <summary>Verifies that Build returns null and emits no folder when all fields are null.</summary>
    [Fact]
    public void Build_AllNull_ReturnsNull_AndDoesNotEmit_Folder()
    {
        var builder = new RecordingBuilder();

        var result = IdentificationFolderBuilder.Build(builder, EmptyEquipment());

        result.ShouldBeNull();
        builder.Folders.ShouldBeEmpty("no Identification folder when every field is null");
        builder.Properties.ShouldBeEmpty();
    }

    /// <summary>Verifies that Build emits all nine fields when fully populated.</summary>
    [Fact]
    public void Build_FullyPopulated_EmitsAllNineFields()
    {
        var builder = new RecordingBuilder();

        var result = IdentificationFolderBuilder.Build(builder, FullyPopulatedEquipment());

        result.ShouldNotBeNull();
        builder.Folders.ShouldContain(f => f.BrowseName == "Identification");
        builder.Properties.Count.ShouldBe(9);
        builder.Properties.Select(p => p.BrowseName).ShouldBe(
            ["Manufacturer", "Model", "SerialNumber",
             "HardwareRevision", "SoftwareRevision",
             "YearOfConstruction", "AssetLocation",
             "ManufacturerUri", "DeviceManualUri"],
            "property order matches decision #139 exactly");
    }

    /// <summary>Verifies that Build emits only non-null fields.</summary>
    [Fact]
    public void Build_OnlyNonNull_Are_Emitted()
    {
        var eq = EmptyEquipment();
        eq.Manufacturer = "Siemens";
        eq.SerialNumber = "SN-1";
        eq.YearOfConstruction = 2024;
        var builder = new RecordingBuilder();

        IdentificationFolderBuilder.Build(builder, eq);

        builder.Properties.Count.ShouldBe(3, "only the 3 non-null fields are exposed");
        builder.Properties.Select(p => p.BrowseName).ShouldBe(
            ["Manufacturer", "SerialNumber", "YearOfConstruction"]);
    }

    /// <summary>Verifies that YearOfConstruction maps short to Int32 DriverDataType.</summary>
    [Fact]
    public void YearOfConstruction_Maps_Short_To_Int32_DriverDataType()
    {
        var eq = EmptyEquipment();
        eq.YearOfConstruction = 2023;
        var builder = new RecordingBuilder();

        IdentificationFolderBuilder.Build(builder, eq);

        var prop = builder.Properties.Single(p => p.BrowseName == "YearOfConstruction");
        prop.DataType.ShouldBe(DriverDataType.Int32);
        prop.Value.ShouldBe(2023, "short is widened to int for OPC UA Int32 representation");
    }

    /// <summary>Verifies that string values round-trip through Build.</summary>
    [Fact]
    public void Build_StringValues_RoundTrip()
    {
        var eq = FullyPopulatedEquipment();
        var builder = new RecordingBuilder();

        IdentificationFolderBuilder.Build(builder, eq);

        builder.Properties.Single(p => p.BrowseName == "Manufacturer").Value.ShouldBe("Siemens");
        builder.Properties.Single(p => p.BrowseName == "DeviceManualUri").Value.ShouldBe("https://siemens.example/manual");
    }

    /// <summary>Verifies that field names match decision 139 exactly.</summary>
    [Fact]
    public void FieldNames_Match_Decision139_Exactly()
    {
        IdentificationFolderBuilder.FieldNames.ShouldBe(
            ["Manufacturer", "Model", "SerialNumber",
             "HardwareRevision", "SoftwareRevision",
             "YearOfConstruction", "AssetLocation",
             "ManufacturerUri", "DeviceManualUri"]);
    }

    /// <summary>Verifies that the folder name is Identification.</summary>
    [Fact]
    public void FolderName_Is_Identification()
    {
        IdentificationFolderBuilder.FolderName.ShouldBe("Identification");
    }
}
