using Bunit;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Dialogs;
using ZB.MOM.WW.ScadaBridge.CentralUI.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;

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

public class NodeBrowserDialogSelectionTests : BunitContext
{
    private readonly IBrowseService _browse = Substitute.For<IBrowseService>();

    public NodeBrowserDialogSelectionTests()
    {
        Services.AddSingleton(_browse);
        _browse.BrowseChildrenAsync(
                Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string?>(),
                Arg.Any<string?>(), Arg.Any<CancellationToken>())
            .Returns(new BrowseNodeResult(Array.Empty<BrowseNode>(), Truncated: false, Failure: null));
        _browse.SearchAsync(
                Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(),
                Arg.Any<int>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
            .Returns(new SearchAddressSpaceResult(
                Matches: new[]
                {
                    new AddressSpaceMatch(
                        new BrowseNode("ns=2;s=Pump1.Speed", "Speed", BrowseNodeClass.Variable, HasChildren: false, DataType: "Double"),
                        Path: "Devices/Pump1/Speed"),
                },
                CapReached: false, Failure: null));
    }

    [Fact]
    public void Confirm_emits_full_node_with_datatype_on_OnNodeSelected()
    {
        BrowseNode? captured = null;
        var cut = Render<NodeBrowserDialog>(p => p
            .Add(c => c.SiteId, "plant-a")
            .Add(c => c.ConnectionName, "PLC-OPC")
            .Add(c => c.OnNodeSelected, EventCallback.Factory.Create<BrowseNode>(this, n => captured = n)));
        cut.InvokeAsync(() => cut.Instance.ShowAsync("plant-a", "PLC-OPC", null));
        cut.Render();

        cut.Find("[data-test=node-search-input]").Input("Pump1");
        cut.Find("[data-test=node-search-button]").Click();
        cut.FindAll("[data-test=node-search-result] button.btn-link")[0].Click();
        cut.Find(".modal-footer .btn-primary").Click();

        Assert.NotNull(captured);
        Assert.Equal("ns=2;s=Pump1.Speed", captured!.NodeId);
        Assert.Equal("Double", captured.DataType);
    }

    [Fact]
    public void ShowSearch_false_hides_search_box()
    {
        var cut = Render<NodeBrowserDialog>(p => p
            .Add(c => c.SiteId, "plant-a")
            .Add(c => c.ConnectionName, "PLC-MX")
            .Add(c => c.ShowSearch, false));
        cut.InvokeAsync(() => cut.Instance.ShowAsync("plant-a", "PLC-MX", null));
        cut.Render();

        Assert.Empty(cut.FindAll("[data-test=node-search-input]"));
    }

    // The Secured Writes page browses MxGateway with ShowSearch=false, so the
    // ONLY user-reachable selection path in production is the tree (Select(TreeNode)
    // → _selectedNode = node.Source). This guards that path end-to-end: a tree-node
    // click must carry the node's DataType through Confirm → OnNodeSelected.
    [Fact]
    public void TreeSelect_emits_full_node_with_datatype_on_OnNodeSelected()
    {
        _browse.BrowseChildrenAsync(
                Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string?>(),
                Arg.Any<string?>(), Arg.Any<CancellationToken>())
            .Returns(new BrowseNodeResult(
                new[] { new BrowseNode("Area1.Pump.Speed", "Speed", BrowseNodeClass.Variable, HasChildren: false, DataType: "Float") },
                Truncated: false, Failure: null));

        BrowseNode? captured = null;
        var cut = Render<NodeBrowserDialog>(p => p
            .Add(c => c.SiteId, "plant-a")
            .Add(c => c.ConnectionName, "PLC-MX")
            .Add(c => c.ShowSearch, false)
            .Add(c => c.OnNodeSelected, EventCallback.Factory.Create<BrowseNode>(this, n => captured = n)));
        cut.InvokeAsync(() => cut.Instance.ShowAsync("plant-a", "PLC-MX", null));
        cut.Render();

        // Click the tree node's select control (tree path, not search). It is a
        // <button class="btn btn-link"> since the 2026-08-11 UI sweep replaced the
        // href="javascript:void(0)" anchor; both click handlers were preserved.
        cut.Find(".node-browser-tree button.btn-link").Click();
        cut.Find(".modal-footer .btn-primary").Click();

        Assert.NotNull(captured);
        Assert.Equal("Area1.Pump.Speed", captured!.NodeId);
        Assert.Equal("Float", captured.DataType);
    }
}
