using Opc.Ua;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Client.UI.Services;
using ZB.MOM.WW.OtOpcUa.Client.UI.Tests.Fakes;
using ZB.MOM.WW.OtOpcUa.Client.UI.ViewModels;

namespace ZB.MOM.WW.OtOpcUa.Client.UI.Tests;

public class ReadWriteViewModelTests
{
    private readonly FakeOpcUaClientService _service;
    private readonly ReadWriteViewModel _vm;

    /// <summary>Initializes a new instance of the ReadWriteViewModelTests class.</summary>
    public ReadWriteViewModelTests()
    {
        _service = new FakeOpcUaClientService
        {
            ReadResult = new DataValue(new Variant("TestValue"), StatusCodes.Good, DateTime.UtcNow, DateTime.UtcNow)
        };
        var dispatcher = new SynchronousUiDispatcher();
        _vm = new ReadWriteViewModel(_service, dispatcher);
    }

    /// <summary>Verifies that the read command cannot execute when disconnected.</summary>
    [Fact]
    public void ReadCommand_CannotExecute_WhenDisconnected()
    {
        _vm.IsConnected = false;
        _vm.SelectedNodeId = "ns=2;s=SomeNode";
        _vm.ReadCommand.CanExecute(null).ShouldBeFalse();
    }

    /// <summary>Verifies that the read command cannot execute when no node is selected.</summary>
    [Fact]
    public void ReadCommand_CannotExecute_WhenNoNodeSelected()
    {
        _vm.IsConnected = true;
        _vm.SelectedNodeId = null;
        _vm.ReadCommand.CanExecute(null).ShouldBeFalse();
    }

    /// <summary>Verifies that the read command can execute when connected and a node is selected.</summary>
    [Fact]
    public void ReadCommand_CanExecute_WhenConnectedAndNodeSelected()
    {
        _vm.IsConnected = true;
        _vm.SelectedNodeId = "ns=2;s=SomeNode";
        _vm.ReadCommand.CanExecute(null).ShouldBeTrue();
    }

    /// <summary>Verifies that the read command updates value and status.</summary>
    [Fact]
    public async Task ReadCommand_UpdatesValueAndStatus()
    {
        _vm.IsConnected = true;
        _vm.SelectedNodeId = "ns=2;s=SomeNode";
        // auto-read fires on selection change, so reset count
        var countBefore = _service.ReadCallCount;

        await _vm.ReadCommand.ExecuteAsync(null);

        _vm.CurrentValue.ShouldBe("TestValue");
        _vm.CurrentStatus.ShouldNotBeNull();
        _vm.SourceTimestamp.ShouldNotBeNull();
        _vm.ServerTimestamp.ShouldNotBeNull();
        (_service.ReadCallCount - countBefore).ShouldBe(1);
    }

    /// <summary>Verifies that auto-read fires on selection change when connected.</summary>
    [Fact]
    public void AutoRead_OnSelectionChange_WhenConnected()
    {
        _vm.IsConnected = true;
        _vm.SelectedNodeId = "ns=2;s=SomeNode";

        // The auto-read fires asynchronously; give it a moment
        // In synchronous dispatcher it should fire immediately
        _service.ReadCallCount.ShouldBeGreaterThanOrEqualTo(1);
    }

    /// <summary>Verifies that null selection does not call the service.</summary>
    [Fact]
    public void NullSelection_DoesNotCallService()
    {
        _vm.IsConnected = true;
        _vm.SelectedNodeId = null;

        _service.ReadCallCount.ShouldBe(0);
    }

    /// <summary>Verifies that the write command updates write status.</summary>
    [Fact]
    public async Task WriteCommand_UpdatesWriteStatus()
    {
        _vm.IsConnected = true;
        _vm.SelectedNodeId = "ns=2;s=SomeNode";
        _vm.WriteValue = "NewValue";

        // Reset read count from auto-read
        var readCountBefore = _service.ReadCallCount;

        await _vm.WriteCommand.ExecuteAsync(null);

        _vm.WriteStatus.ShouldNotBeNull();
        _service.WriteCallCount.ShouldBe(1);
        _service.LastWriteValue.ShouldBe("NewValue");
    }

    /// <summary>Verifies that the write command cannot execute when disconnected.</summary>
    [Fact]
    public void WriteCommand_CannotExecute_WhenDisconnected()
    {
        _vm.IsConnected = false;
        _vm.SelectedNodeId = "ns=2;s=SomeNode";
        _vm.WriteCommand.CanExecute(null).ShouldBeFalse();
    }

    /// <summary>Verifies that read command error sets error status.</summary>
    [Fact]
    public async Task ReadCommand_Error_SetsErrorStatus()
    {
        _service.ReadException = new Exception("Read failed");
        _vm.IsConnected = true;

        // We need to set SelectedNodeId and manually trigger read
        // because auto-read catches the exception too
        _vm.SelectedNodeId = "ns=2;s=SomeNode";

        _vm.CurrentStatus.ShouldContain("Error");
    }

    /// <summary>Verifies that clear resets all properties.</summary>
    [Fact]
    public void Clear_ResetsAllProperties()
    {
        _vm.IsConnected = true;
        _vm.SelectedNodeId = "ns=2;s=SomeNode";
        _vm.WriteValue = "test";
        _vm.WriteStatus = "Good";

        _vm.Clear();

        _vm.SelectedNodeId.ShouldBeNull();
        _vm.CurrentValue.ShouldBeNull();
        _vm.CurrentStatus.ShouldBeNull();
        _vm.SourceTimestamp.ShouldBeNull();
        _vm.ServerTimestamp.ShouldBeNull();
        _vm.WriteValue.ShouldBeNull();
        _vm.WriteStatus.ShouldBeNull();
    }

    /// <summary>Verifies that IsNodeSelected tracks the selected node ID.</summary>
    [Fact]
    public void IsNodeSelected_TracksSelectedNodeId()
    {
        _vm.IsNodeSelected.ShouldBeFalse();
        _vm.SelectedNodeId = "ns=2;s=SomeNode";
        _vm.IsNodeSelected.ShouldBeTrue();
        _vm.SelectedNodeId = null;
        _vm.IsNodeSelected.ShouldBeFalse();
    }
}