using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;

namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests;

[Trait("Category", "Unit")]
public class TwinCATTagDefinitionFactoryTests
{
    private const string RawPath = "area/line/equip/Speed";

    [Fact]
    public void Maps_tagconfig_into_a_definition_keyed_by_rawpath()
    {
        // v3: deviceHostAddress is NOT read from the blob (dropped); routing comes from the RawTagEntry.
        var json = """{"symbolPath":"MAIN.Speed","dataType":"DInt"}""";
        TwinCATTagDefinitionFactory.FromTagConfig(json, RawPath, out var def).ShouldBeTrue();
        def!.Name.ShouldBe(RawPath);                 // identity is the RawPath, not the blob
        def.SymbolPath.ShouldBe("MAIN.Speed");
        def.DeviceHostAddress.ShouldBe("");          // mapper never reads a device from the blob
        def.DataType.ShouldBe(TwinCATDataType.DInt);
        def.Writable.ShouldBeTrue();
    }

    [Fact]
    public void Defaults_optional_fields_when_only_symbol_path_is_present()
    {
        TwinCATTagDefinitionFactory.FromTagConfig("""{"symbolPath":"GVL.X"}""", RawPath, out var def).ShouldBeTrue();
        def!.SymbolPath.ShouldBe("GVL.X");
        def.DeviceHostAddress.ShouldBe("");
        def.DataType.ShouldBe(TwinCATDataType.DInt); // enum default
    }

    [Fact]
    public void Rejects_a_non_address_blob()
        => TwinCATTagDefinitionFactory.FromTagConfig("""{"FullName":"x"}""", RawPath, out _).ShouldBeFalse();

    [Fact]
    public void Rejects_garbage()
        => TwinCATTagDefinitionFactory.FromTagConfig("not json", RawPath, out _).ShouldBeFalse();

    [Fact]
    public void Rejects_blank_tagconfig()
        => TwinCATTagDefinitionFactory.FromTagConfig("   ", RawPath, out _).ShouldBeFalse();

    [Fact]
    public void Rejects_symbol_path_as_a_non_string()
        => TwinCATTagDefinitionFactory.FromTagConfig("""{"symbolPath":42}""", RawPath, out _).ShouldBeFalse();

    [Fact]
    public void Ignores_a_non_string_data_type_and_falls_back_to_default()
    {
        // dataType as a number is not a String enum — the guard ignores it and keeps the default.
        TwinCATTagDefinitionFactory.FromTagConfig("""{"symbolPath":"MAIN.X","dataType":3}""", RawPath, out var def).ShouldBeTrue();
        def!.DataType.ShouldBe(TwinCATDataType.DInt);
    }

    [Fact]
    public void ToTagConfig_round_trips_through_FromTagConfig()
    {
        var original = new TwinCATTagDefinition(
            Name: RawPath, DeviceHostAddress: "ads://5.23.91.23.1.1:851", SymbolPath: "MAIN.Speeds",
            DataType: TwinCATDataType.Real, Writable: false, ArrayLength: 4);
        var blob = TwinCATTagDefinitionFactory.ToTagConfig(original);

        TwinCATTagDefinitionFactory.FromTagConfig(blob, RawPath, out var def).ShouldBeTrue();
        def!.SymbolPath.ShouldBe("MAIN.Speeds");
        def.DataType.ShouldBe(TwinCATDataType.Real);
        def.Writable.ShouldBeFalse();
        def.ArrayLength.ShouldBe(4);
        // Device + WriteIdempotent travel on the RawTagEntry, not the blob → mapper leaves them defaulted.
        def.DeviceHostAddress.ShouldBe("");
        def.WriteIdempotent.ShouldBeFalse();
    }

    /// <summary>
    ///     End-to-end driver-level proof (v3 delivery): a TwinCAT driver served an authored raw tag
    ///     (RawPath + TagConfig blob + owning DeviceName) resolves it by RawPath and routes it to the
    ///     matching device — the read reaches the fake client instead of returning BadNodeIdUnknown.
    /// </summary>
    [Fact]
    public async Task Driver_resolves_a_raw_tag_and_reads_instead_of_BadNodeIdUnknown()
    {
        var host = "ads://5.23.91.23.1.1:851";
        var blob = """{"symbolPath":"MAIN.Speed","dataType":"DInt"}""";
        var factory = new FakeTwinCATClientFactory { Customise = () => new FakeTwinCATClient { Values = { ["MAIN.Speed"] = 4242 } } };
        var drv = new TwinCATDriver(new TwinCATDriverOptions
        {
            Devices = [new TwinCATDeviceOptions(host)],
            RawTags = [new RawTagEntry(RawPath, blob, WriteIdempotent: false, DeviceName: host)],
            Probe = new TwinCATProbeOptions { Enabled = false },
        }, "twincat-eq", factory);
        await drv.InitializeAsync("{}", CancellationToken.None);

        var r = await drv.ReadAsync([RawPath], CancellationToken.None);

        r[0].StatusCode.ShouldBe(TwinCATStatusMapper.Good);
        r[0].StatusCode.ShouldNotBe(TwinCATStatusMapper.BadNodeIdUnknown);
        r[0].Value.ShouldBe(4242);
    }
}
