using Bunit;
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;

namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Design;

/// <summary>
/// MV-13: the shared <c>AttributeListEditor</c> reveals an element-type select
/// plus a repeatable list-value editor for structured List attributes. These are
/// real bUnit rendering/interaction tests over the self-contained component, plus
/// structural assertions pinning the TemplateEdit attribute-form wiring (the page
/// itself is heavyweight to render — see <c>TemplateNativeAlarmSourceEditorTests</c>).
/// </summary>
public class AttributeListEditorTests : BunitContext
{
    private static string DesignComponentsRoot
    {
        get
        {
            var dir = AppContext.BaseDirectory;
            for (var i = 0; i < 6 && dir is not null; i++)
                dir = Directory.GetParent(dir)?.FullName;
            return Path.Combine(dir!, "src", "ZB.MOM.WW.ScadaBridge.CentralUI",
                "Components", "Pages", "Design");
        }
    }

    private static string TemplateEditMarkup
        => File.ReadAllText(Path.Combine(DesignComponentsRoot, "TemplateEdit.razor"));

    // M10 residual: the attribute authoring form moved out of the page into the
    // dialog body component hosted by IDialogService.ShowAsync. Submit-side
    // encoding stays on the page (it owns TemplateService).
    private static string AttributeDialogMarkup
        => File.ReadAllText(Path.Combine(DesignComponentsRoot, "TemplateAttributeDialog.razor"));

    [Fact]
    public void Editor_RendersElementTypeSelect_WithSixValidScalars()
    {
        var cut = Render<AttributeListEditor>(p => p
            .Add(x => x.ElementDataType, DataType.String)
            .Add(x => x.Rows, new List<string>()));

        var select = cut.Find("select.form-select");
        var options = select.QuerySelectorAll("option");
        Assert.Equal(6, options.Length);
        var texts = options.Select(o => o.TextContent).ToArray();
        Assert.Contains("String", texts);
        Assert.Contains("Int32", texts);
        Assert.Contains("Float", texts);
        Assert.Contains("Double", texts);
        Assert.Contains("Boolean", texts);
        Assert.Contains("DateTime", texts);
        // List itself must never appear as an element type.
        Assert.DoesNotContain("List", texts);
    }

    [Fact]
    public void ShowElementType_False_HidesTheSelect()
    {
        var cut = Render<AttributeListEditor>(p => p
            .Add(x => x.ShowElementType, false)
            .Add(x => x.Rows, new List<string>()));

        Assert.Throws<ElementNotFoundException>(() => cut.Find("select.form-select"));
    }

    [Fact]
    public void Editor_RendersOneInputPerRow()
    {
        var cut = Render<AttributeListEditor>(p => p
            .Add(x => x.ElementDataType, DataType.Int32)
            .Add(x => x.Rows, new List<string> { "1", "2", "3" }));

        var inputs = cut.FindAll("input.form-control");
        Assert.Equal(3, inputs.Count);
        Assert.Equal("1", inputs[0].GetAttribute("value"));
        Assert.Equal("3", inputs[2].GetAttribute("value"));
    }

    [Fact]
    public void AddElement_AppendsRow_AndRaisesRowsChanged()
    {
        var rows = new List<string> { "a" };
        List<string>? changed = null;
        var cut = Render<AttributeListEditor>(p => p
            .Add(x => x.Rows, rows)
            .Add(x => x.RowsChanged, r => changed = r));

        cut.Find("button.btn-outline-secondary").Click();

        Assert.NotNull(changed);
        Assert.Equal(2, changed!.Count);
        Assert.Equal("", changed[1]);
    }

    [Fact]
    public void RemoveElement_DropsRow_AndRaisesRowsChanged()
    {
        var rows = new List<string> { "a", "b" };
        List<string>? changed = null;
        var cut = Render<AttributeListEditor>(p => p
            .Add(x => x.Rows, rows)
            .Add(x => x.RowsChanged, r => changed = r));

        // First per-row Remove button.
        cut.FindAll("button.btn-outline-danger")[0].Click();

        Assert.NotNull(changed);
        Assert.Single(changed!);
        Assert.Equal("b", changed![0]);
    }

    [Fact]
    public void EncodedRows_RoundTripThroughCodec()
    {
        // Mirrors what TemplateEdit.SaveAttribute does on submit.
        var rows = new List<string> { "10", "20" };
        var json = AttributeValueCodec.Encode(rows);
        var decoded = AttributeValueCodec.Decode(json, DataType.List, DataType.Int32);
        var list = Assert.IsType<List<int>>(decoded);
        Assert.Equal(new[] { 10, 20 }, list);
    }

    [Fact]
    public void TemplateEdit_RevealsListEditor_AndSendsElementType()
    {
        var dialogMarkup = AttributeDialogMarkup;
        // Conditional reveal on DataType.List.
        Assert.Contains("_dataType == DataType.List", dialogMarkup);
        Assert.Contains("<AttributeListEditor", dialogMarkup);
        Assert.Contains("@bind-ElementDataType=\"_elementDataType\"", dialogMarkup);
        Assert.Contains("@bind-Rows=\"_listRows\"", dialogMarkup);

        var pageMarkup = TemplateEditMarkup;
        // Submit encodes rows to canonical JSON and passes the element type.
        Assert.Contains("AttributeValueCodec.Encode(draft.ListRows)", pageMarkup);
        Assert.Contains("ElementDataType = elementType", pageMarkup);
        // Edit decodes the stored JSON value into rows.
        Assert.Contains("DecodeListRows(", pageMarkup);
    }
}
