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;

/// <summary>
/// Drift guard for the WP5 CSV column dictionary (<see cref="CsvColumnMap"/>): every typed column of a
/// model-backed driver map must name a real property on that driver's <c><![CDATA[<Driver>TagConfigModel]]></c>,
/// and the registered typed-column set per driver must match the batch-plan column dictionary exactly. A
/// model rename or a column-dictionary change breaks this test rather than silently corrupting import/export.
/// </summary>
[Trait("Category", "Unit")]
public sealed class CsvColumnMapReflectionTests
{
    [Fact]
    public void Every_model_backed_typed_column_maps_to_a_real_model_property()
    {
        foreach (var map in CsvColumnMap.All.Where(m => m.ModelType is not null))
        {
            foreach (var col in map.Columns)
            {
                col.ModelProperty.ShouldNotBeNull(
                    $"{map.DriverType} column '{col.Header}' is model-backed but declares no ModelProperty.");

                map.ModelType!.GetProperty(col.ModelProperty!)
                    .ShouldNotBeNull(
                        $"{map.DriverType} column '{col.Header}' maps to '{col.ModelProperty}', " +
                        $"which is not a property on {map.ModelType!.Name}.");
            }
        }
    }

    [Fact]
    public void Model_less_maps_declare_no_model_property()
    {
        foreach (var map in CsvColumnMap.All.Where(m => m.ModelType is null))
        {
            foreach (var col in map.Columns)
            {
                col.ModelProperty.ShouldBeNull(
                    $"{map.DriverType} column '{col.Header}' is model-less but declares ModelProperty '{col.ModelProperty}'.");
            }
        }
    }

    [Theory]
    [InlineData(DriverTypeNames.Modbus, "Region", "Address", "ModbusDataType", "ByteOrder", "BitIndex", "StringLength", "Writable")]
    [InlineData(DriverTypeNames.S7, "Address", "S7DataType", "StringLength", "Writable")]
    [InlineData(DriverTypeNames.AbCip, "TagPath", "AbCipDataType", "Writable")]
    [InlineData(DriverTypeNames.AbLegacy, "Address", "AbLegacyDataType", "Writable")]
    [InlineData(DriverTypeNames.TwinCAT, "SymbolPath", "TwinCATDataType", "Writable")]
    [InlineData(DriverTypeNames.FOCAS, "Address", "FocasDataType")]
    [InlineData(DriverTypeNames.OpcUaClient, "NodeId")]
    [InlineData(DriverTypeNames.Galaxy, "AttributeRef")]
    [InlineData(CsvColumnMap.CalculationDriverType, "ScriptId", "ChangeTriggered", "TimerIntervalMs")]
    public void Registered_typed_columns_match_the_batch_plan_dictionary(string driverType, params string[] expected)
    {
        var map = CsvColumnMap.Resolve(driverType);
        map.ShouldNotBeNull($"No CSV map registered for driver '{driverType}'.");
        map!.Headers.ShouldBe(expected);
    }

    [Fact]
    public void HeadersFor_puts_typed_columns_between_common_leading_and_the_TagConfigJson_fallback()
    {
        var headers = CsvColumnMap.HeadersFor(DriverTypeNames.Modbus);

        // Leading common columns come first, in order.
        headers.Take(CsvColumnMap.CommonLeadingColumns.Count).ShouldBe(CsvColumnMap.CommonLeadingColumns);
        // The typed Modbus columns come next.
        headers.Skip(CsvColumnMap.CommonLeadingColumns.Count).Take(7)
            .ShouldBe(CsvColumnMap.Resolve(DriverTypeNames.Modbus)!.Headers);
        // TagConfigJson is always the trailing fallback.
        headers[^1].ShouldBe(CsvColumnMap.TagConfigJsonColumn);
    }

    [Fact]
    public void Unknown_driver_headers_are_the_common_set_only()
    {
        CsvColumnMap.Resolve("NoSuchDriver").ShouldBeNull();
        CsvColumnMap.HeadersFor("NoSuchDriver").ShouldBe(CsvColumnMap.CommonColumns);
    }
}
