using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;

public sealed class MTConnectTagConfigModelTests
{
    [Fact]
    public void Roundtrip_preserves_unknown_keys_and_writes_enum_as_name()
    {
        var json = "{\"fullName\":\"dev1_pos\",\"dataType\":\"Float64\",\"somethingUnknown\":42}";
        var m = MTConnectTagConfigModel.FromJson(json);
        var outJson = m.ToJson();
        outJson.ShouldContain("\"somethingUnknown\":42");
        outJson.ShouldContain("\"dataType\":\"Float64\""); // name, never a number
    }

    [Fact]
    public void Validate_blocks_blank_fullname()
        => MTConnectTagConfigModel.FromJson("{}").Validate().ShouldNotBeNull();

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData("{}")]
    public void FromJson_returns_defaults_for_empty_input(string? json)
    {
        var m = MTConnectTagConfigModel.FromJson(json);

        m.FullName.ShouldBe("");
        m.DataType.ShouldBe(DriverDataType.String);
        m.MtCategory.ShouldBeNull();
        m.MtType.ShouldBeNull();
        m.MtSubType.ShouldBeNull();
        m.Units.ShouldBeNull();
        m.MtDevice.ShouldBeNull();
        m.MtComponent.ShouldBeNull();
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void Validate_blocks_missing_and_whitespace_fullname(string? fullName)
    {
        var m = new MTConnectTagConfigModel { FullName = fullName! };
        m.Validate().ShouldNotBeNull();
    }

    [Fact]
    public void Validate_returns_null_for_a_valid_model()
    {
        var m = new MTConnectTagConfigModel { FullName = "dev1_pos", DataType = DriverDataType.Float64 };
        m.Validate().ShouldBeNull();
    }

    [Fact]
    public void ToJson_emits_camelCase_keys_with_enum_name_and_all_fields()
    {
        var m = new MTConnectTagConfigModel
        {
            FullName = "dev1_partcount",
            DataType = DriverDataType.Int64,
            MtCategory = "EVENT",
            MtType = "PART_COUNT",
            MtSubType = "ACTUAL",
            Units = "COUNT",
            MtDevice = "Mill-01",
            MtComponent = "Controller",
        };

        var json = m.ToJson();

        json.ShouldContain("\"fullName\":\"dev1_partcount\"");
        json.ShouldContain("\"dataType\":\"Int64\"");
        json.ShouldContain("\"mtCategory\":\"EVENT\"");
        json.ShouldContain("\"mtType\":\"PART_COUNT\"");
        json.ShouldContain("\"mtSubType\":\"ACTUAL\"");
        json.ShouldContain("\"units\":\"COUNT\"");
        json.ShouldContain("\"mtDevice\":\"Mill-01\"");
        json.ShouldContain("\"mtComponent\":\"Controller\"");
    }

    [Fact]
    public void ToJson_never_emits_a_bare_numeric_dataType()
    {
        foreach (DriverDataType value in Enum.GetValues<DriverDataType>())
        {
            var json = new MTConnectTagConfigModel { FullName = "x", DataType = value }.ToJson();

            // The enum-serialization trap: "dataType":8 would fault the driver at deploy (string-typed
            // factory DTOs). Assert the value after the key is a quoted name, not a bare digit.
            json.ShouldNotContain($"\"dataType\":{(int)value}");
            json.ShouldContain($"\"dataType\":\"{value}\"");
        }
    }

    [Fact]
    public void FromJson_then_ToJson_preserves_unknown_keys_including_nested_object_and_isHistorized()
    {
        var json = """
            {
                "fullName": "dev1_pos",
                "dataType": "Float64",
                "isHistorized": true,
                "historianTagname": "Mill01.Position",
                "nested": { "a": 1, "b": [1, 2, 3] }
            }
            """;

        var roundTripped = MTConnectTagConfigModel.FromJson(json).ToJson();

        roundTripped.ShouldContain("\"isHistorized\":true");
        roundTripped.ShouldContain("\"historianTagname\":\"Mill01.Position\"");
        roundTripped.ShouldContain("\"nested\":{\"a\":1,\"b\":[1,2,3]}");
        // and the modelled fields still round-trip alongside the preserved keys
        roundTripped.ShouldContain("\"fullName\":\"dev1_pos\"");
        roundTripped.ShouldContain("\"dataType\":\"Float64\"");
    }

    [Fact]
    public void FromJson_of_malformed_json_falls_back_to_defaults_rather_than_throwing()
    {
        var m = MTConnectTagConfigModel.FromJson("{not valid json");

        m.FullName.ShouldBe("");
        m.DataType.ShouldBe(DriverDataType.String);
    }

    [Fact]
    public void Round_trip_is_stable_for_a_fully_populated_model()
    {
        var m = new MTConnectTagConfigModel
        {
            FullName = "dev1_feedrate",
            DataType = DriverDataType.Float64,
            MtCategory = "SAMPLE",
            MtType = "PATH_FEEDRATE",
            MtSubType = "PROGRAMMED",
            Units = "MILLIMETER/SECOND",
            MtDevice = "Mill-01",
            MtComponent = "Path",
        };

        var m2 = MTConnectTagConfigModel.FromJson(m.ToJson());

        m2.FullName.ShouldBe(m.FullName);
        m2.DataType.ShouldBe(m.DataType);
        m2.MtCategory.ShouldBe(m.MtCategory);
        m2.MtType.ShouldBe(m.MtType);
        m2.MtSubType.ShouldBe(m.MtSubType);
        m2.Units.ShouldBe(m.Units);
        m2.MtDevice.ShouldBe(m.MtDevice);
        m2.MtComponent.ShouldBe(m.MtComponent);
    }

    [Fact]
    public void Optional_metadata_keys_are_omitted_when_blank_rather_than_persisted_empty()
    {
        var json = new MTConnectTagConfigModel { FullName = "dev1_pos", MtCategory = "   " }.ToJson();

        json.ShouldNotContain("mtCategory");
    }

    [Fact]
    public void Validate_rejects_a_dataType_that_was_stored_as_a_bare_number()
    {
        // Enum.TryParse (which TagConfigJson.GetEnum uses under the hood) silently accepts numeric text,
        // so a config that was ever hand-authored/migrated with a quoted-number dataType would otherwise
        // load into a plausible-looking enum value with no signal. Guard it explicitly.
        var m = MTConnectTagConfigModel.FromJson("{\"fullName\":\"dev1_pos\",\"dataType\":\"8\"}");

        m.DataType.ShouldBe(DriverDataType.Float64); // 8 == DriverDataType.Float64 — parsed "successfully"
        m.Validate().ShouldNotBeNull();
    }
}
