using Opc.Ua;
using ZB.MOM.WW.OtOpcUa.Client.Shared;
using ZB.MOM.WW.OtOpcUa.Client.Shared.Models;
using BrowseResult = ZB.MOM.WW.OtOpcUa.Client.Shared.Models.BrowseResult;

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

/// <summary>
///     Test double for the shared OPC UA client service used by UI view-model tests.
///     It lets tests script connection, browse, history, redundancy, and alarm behavior without a live server.
/// </summary>
public sealed class FakeOpcUaClientService : IOpcUaClientService
{
    // Configurable responses
    /// <summary>
    /// Gets or sets the connection metadata returned when a UI test performs a successful connect.
    /// </summary>
    public ConnectionInfo? ConnectResult { get; set; }

    /// <summary>
    /// Gets or sets the exception thrown to simulate a failed connect workflow in the UI.
    /// </summary>
    public Exception? ConnectException { get; set; }

    /// <summary>
    /// Gets or sets the default browse results returned when no parent-specific branch is configured.
    /// </summary>
    public IReadOnlyList<BrowseResult> BrowseResults { get; set; } = [];

    /// <summary>
    /// Gets or sets browse results keyed by parent node so tree-navigation tests can model multiple address-space branches.
    /// </summary>
    public Dictionary<string, IReadOnlyList<BrowseResult>> BrowseResultsByParent { get; set; } = new();

    /// <summary>
    /// Gets or sets the exception thrown to simulate browse failures in the UI tree.
    /// </summary>
    public Exception? BrowseException { get; set; }

    /// <summary>
    /// Gets or sets the current value returned by point-read tests.
    /// </summary>
    public DataValue ReadResult { get; set; } =
        new(new Variant(42), StatusCodes.Good, DateTime.UtcNow, DateTime.UtcNow);

    /// <summary>
    /// Gets or sets the exception thrown to simulate point-read failures in the UI.
    /// </summary>
    public Exception? ReadException { get; set; }

    /// <summary>
    /// Gets or sets the status code returned by write operations in UI tests.
    /// </summary>
    public StatusCode WriteResult { get; set; } = StatusCodes.Good;

    /// <summary>
    /// Gets or sets the exception thrown to simulate failed write workflows in the UI.
    /// </summary>
    public Exception? WriteException { get; set; }

    /// <summary>
    /// Gets or sets the redundancy snapshot returned when the UI inspects server redundancy state.
    /// </summary>
    public RedundancyInfo? RedundancyResult { get; set; }

    /// <summary>
    /// Gets or sets the exception thrown to simulate redundancy lookup failures.
    /// </summary>
    public Exception? RedundancyException { get; set; }

    /// <summary>
    /// Gets or sets the raw historical values returned to history-view tests.
    /// </summary>
    public IReadOnlyList<DataValue> HistoryRawResult { get; set; } = [];

    /// <summary>
    /// Gets or sets the aggregate historical values returned to history-view tests.
    /// </summary>
    public IReadOnlyList<DataValue> HistoryAggregateResult { get; set; } = [];

    /// <summary>
    /// Gets or sets the exception thrown to simulate historical-data failures in the UI.
    /// </summary>
    public Exception? HistoryException { get; set; }

    // Call tracking
    /// <summary>Gets the number of times ConnectAsync has been called.</summary>
    public int ConnectCallCount { get; private set; }
    /// <summary>Gets the number of times DisconnectAsync has been called.</summary>
    public int DisconnectCallCount { get; private set; }
    /// <summary>Gets the number of times ReadValueAsync has been called.</summary>
    public int ReadCallCount { get; private set; }
    /// <summary>Gets the number of times WriteValueAsync has been called.</summary>
    public int WriteCallCount { get; private set; }
    /// <summary>Gets the number of times BrowseAsync has been called.</summary>
    public int BrowseCallCount { get; private set; }
    /// <summary>Gets the number of times SubscribeAsync has been called.</summary>
    public int SubscribeCallCount { get; private set; }
    /// <summary>Gets the number of times UnsubscribeAsync has been called.</summary>
    public int UnsubscribeCallCount { get; private set; }
    /// <summary>Gets the number of times SubscribeAlarmsAsync has been called.</summary>
    public int SubscribeAlarmsCallCount { get; private set; }
    /// <summary>Gets the number of times UnsubscribeAlarmsAsync has been called.</summary>
    public int UnsubscribeAlarmsCallCount { get; private set; }
    /// <summary>Gets the number of times RequestConditionRefreshAsync has been called.</summary>
    public int RequestConditionRefreshCallCount { get; private set; }
    /// <summary>Gets the number of times HistoryReadRawAsync has been called.</summary>
    public int HistoryReadRawCallCount { get; private set; }
    /// <summary>Gets the number of times HistoryReadAggregateAsync has been called.</summary>
    public int HistoryReadAggregateCallCount { get; private set; }
    /// <summary>Gets the number of times GetRedundancyInfoAsync has been called.</summary>
    public int GetRedundancyInfoCallCount { get; private set; }

    /// <summary>Gets the connection settings from the last ConnectAsync call.</summary>
    public ConnectionSettings? LastConnectionSettings { get; private set; }
    /// <summary>Gets the node ID from the last ReadValueAsync call.</summary>
    public NodeId? LastReadNodeId { get; private set; }
    /// <summary>Gets the node ID from the last WriteValueAsync call.</summary>
    public NodeId? LastWriteNodeId { get; private set; }
    /// <summary>Gets the value from the last WriteValueAsync call.</summary>
    public object? LastWriteValue { get; private set; }
    /// <summary>Gets the parent node ID from the last BrowseAsync call.</summary>
    public NodeId? LastBrowseParentNodeId { get; private set; }
    /// <summary>Gets the node ID from the last SubscribeAsync call.</summary>
    public NodeId? LastSubscribeNodeId { get; private set; }
    /// <summary>Gets the interval in milliseconds from the last SubscribeAsync call.</summary>
    public int LastSubscribeIntervalMs { get; private set; }
    /// <summary>Gets the node ID from the last UnsubscribeAsync call.</summary>
    public NodeId? LastUnsubscribeNodeId { get; private set; }
    /// <summary>Gets the aggregate type from the last HistoryReadAggregateAsync call.</summary>
    public AggregateType? LastAggregateType { get; private set; }

    /// <inheritdoc />
    public bool IsConnected { get; set; }

    /// <inheritdoc />
    public ConnectionInfo? CurrentConnectionInfo { get; set; }

    /// <inheritdoc />
    public event EventHandler<DataChangedEventArgs>? DataChanged;

    /// <inheritdoc />
    public event EventHandler<AlarmEventArgs>? AlarmEvent;

    /// <inheritdoc />
    public event EventHandler<ConnectionStateChangedEventArgs>? ConnectionStateChanged;

    /// <inheritdoc />
    public Task<ConnectionInfo> ConnectAsync(ConnectionSettings settings, CancellationToken ct = default)
    {
        ConnectCallCount++;
        LastConnectionSettings = settings;
        if (ConnectException != null) throw ConnectException;
        IsConnected = true;
        CurrentConnectionInfo = ConnectResult;
        return Task.FromResult(ConnectResult!);
    }

    /// <inheritdoc />
    public Task DisconnectAsync(CancellationToken ct = default)
    {
        DisconnectCallCount++;
        IsConnected = false;
        CurrentConnectionInfo = null;
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task<DataValue> ReadValueAsync(NodeId nodeId, CancellationToken ct = default)
    {
        ReadCallCount++;
        LastReadNodeId = nodeId;
        if (ReadException != null) throw ReadException;
        return Task.FromResult(ReadResult);
    }

    /// <inheritdoc />
    public Task<StatusCode> WriteValueAsync(NodeId nodeId, object value, CancellationToken ct = default)
    {
        WriteCallCount++;
        LastWriteNodeId = nodeId;
        LastWriteValue = value;
        if (WriteException != null) throw WriteException;
        return Task.FromResult(WriteResult);
    }

    /// <inheritdoc />
    public Task<IReadOnlyList<BrowseResult>> BrowseAsync(NodeId? parentNodeId = null, CancellationToken ct = default)
    {
        BrowseCallCount++;
        LastBrowseParentNodeId = parentNodeId;
        if (BrowseException != null) throw BrowseException;

        if (parentNodeId != null && BrowseResultsByParent.TryGetValue(parentNodeId.ToString(), out var perParent))
            return Task.FromResult(perParent);

        return Task.FromResult(BrowseResults);
    }

    /// <summary>
    /// Gets or sets the exception thrown to simulate subscribe failures in the UI.
    /// </summary>
    public Exception? SubscribeException { get; set; }

    /// <summary>
    /// Gets or sets the exception thrown to simulate alarm-subscribe failures in the UI.
    /// </summary>
    public Exception? SubscribeAlarmsException { get; set; }

    /// <inheritdoc />
    public Task SubscribeAsync(NodeId nodeId, int intervalMs = 1000, CancellationToken ct = default)
    {
        SubscribeCallCount++;
        LastSubscribeNodeId = nodeId;
        LastSubscribeIntervalMs = intervalMs;
        if (SubscribeException != null) throw SubscribeException;
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task UnsubscribeAsync(NodeId nodeId, CancellationToken ct = default)
    {
        UnsubscribeCallCount++;
        LastUnsubscribeNodeId = nodeId;
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task SubscribeAlarmsAsync(NodeId? sourceNodeId = null, int intervalMs = 1000, CancellationToken ct = default)
    {
        SubscribeAlarmsCallCount++;
        if (SubscribeAlarmsException != null) throw SubscribeAlarmsException;
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task UnsubscribeAlarmsAsync(CancellationToken ct = default)
    {
        UnsubscribeAlarmsCallCount++;
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task RequestConditionRefreshAsync(CancellationToken ct = default)
    {
        RequestConditionRefreshCallCount++;
        return Task.CompletedTask;
    }

    /// <summary>Gets or sets the status code returned by acknowledgment operations in UI tests.</summary>
    public StatusCode AcknowledgeResult { get; set; } = StatusCodes.Good;
    /// <summary>Gets or sets the exception thrown to simulate alarm acknowledgment failures in the UI.</summary>
    public Exception? AcknowledgeException { get; set; }
    /// <summary>Gets the number of times AcknowledgeAlarmAsync has been called.</summary>
    public int AcknowledgeCallCount { get; private set; }

    /// <inheritdoc />
    public Task<StatusCode> AcknowledgeAlarmAsync(string conditionNodeId, byte[] eventId, string comment,
        CancellationToken ct = default)
    {
        AcknowledgeCallCount++;
        if (AcknowledgeException != null) throw AcknowledgeException;
        return Task.FromResult(AcknowledgeResult);
    }

    /// <summary>Gets or sets the status code returned by confirmation operations in UI tests.</summary>
    public StatusCode ConfirmResult { get; set; } = StatusCodes.Good;
    /// <summary>Gets or sets the exception thrown to simulate alarm confirmation failures in the UI.</summary>
    public Exception? ConfirmException { get; set; }
    /// <summary>Gets the number of times ConfirmAlarmAsync has been called.</summary>
    public int ConfirmCallCount { get; private set; }
    /// <summary>Gets the (conditionNodeId, eventId, comment) of the last ConfirmAlarmAsync call.</summary>
    public (string ConditionNodeId, byte[] EventId, string Comment)? LastConfirmCall { get; private set; }

    /// <inheritdoc />
    public Task<StatusCode> ConfirmAlarmAsync(string conditionNodeId, byte[] eventId, string comment,
        CancellationToken ct = default)
    {
        ConfirmCallCount++;
        LastConfirmCall = (conditionNodeId, eventId, comment);
        if (ConfirmException != null) throw ConfirmException;
        return Task.FromResult(ConfirmResult);
    }

    /// <summary>Gets or sets the status code returned by shelve operations in UI tests.</summary>
    public StatusCode ShelveResult { get; set; } = StatusCodes.Good;
    /// <summary>Gets or sets the exception thrown to simulate alarm shelve failures in the UI.</summary>
    public Exception? ShelveException { get; set; }
    /// <summary>Gets the number of times ShelveAlarmAsync has been called.</summary>
    public int ShelveCallCount { get; private set; }
    /// <summary>Gets the (conditionNodeId, kind, shelvingTimeSeconds) of the last ShelveAlarmAsync call.</summary>
    public (string ConditionNodeId, ShelveKind Kind, double ShelvingTimeSeconds)? LastShelveCall { get; private set; }

    /// <inheritdoc />
    public Task<StatusCode> ShelveAlarmAsync(string conditionNodeId, ShelveKind kind, double shelvingTimeSeconds = 0,
        CancellationToken ct = default)
    {
        ShelveCallCount++;
        LastShelveCall = (conditionNodeId, kind, shelvingTimeSeconds);
        if (ShelveException != null) throw ShelveException;
        return Task.FromResult(ShelveResult);
    }

    /// <summary>Gets or sets the status code returned by enable operations in UI tests.</summary>
    public StatusCode EnableResult { get; set; } = StatusCodes.Good;
    /// <summary>Gets or sets the exception thrown to simulate alarm enable failures in the UI.</summary>
    public Exception? EnableException { get; set; }
    /// <summary>Gets the number of times EnableAsync has been called.</summary>
    public int EnableCallCount { get; private set; }
    /// <summary>Gets the conditionNodeId of the last EnableAsync call.</summary>
    public string? LastEnableCall { get; private set; }

    /// <inheritdoc />
    public Task<StatusCode> EnableAsync(string conditionNodeId, CancellationToken ct = default)
    {
        EnableCallCount++;
        LastEnableCall = conditionNodeId;
        if (EnableException != null) throw EnableException;
        return Task.FromResult(EnableResult);
    }

    /// <summary>Gets or sets the status code returned by disable operations in UI tests.</summary>
    public StatusCode DisableResult { get; set; } = StatusCodes.Good;
    /// <summary>Gets or sets the exception thrown to simulate alarm disable failures in the UI.</summary>
    public Exception? DisableException { get; set; }
    /// <summary>Gets the number of times DisableAsync has been called.</summary>
    public int DisableCallCount { get; private set; }
    /// <summary>Gets the conditionNodeId of the last DisableAsync call.</summary>
    public string? LastDisableCall { get; private set; }

    /// <inheritdoc />
    public Task<StatusCode> DisableAsync(string conditionNodeId, CancellationToken ct = default)
    {
        DisableCallCount++;
        LastDisableCall = conditionNodeId;
        if (DisableException != null) throw DisableException;
        return Task.FromResult(DisableResult);
    }

    /// <inheritdoc />
    public Task<IReadOnlyList<DataValue>> HistoryReadRawAsync(NodeId nodeId, DateTime startTime, DateTime endTime,
        int maxValues = 1000, CancellationToken ct = default)
    {
        HistoryReadRawCallCount++;
        if (HistoryException != null) throw HistoryException;
        return Task.FromResult(HistoryRawResult);
    }

    /// <inheritdoc />
    public Task<IReadOnlyList<DataValue>> HistoryReadAggregateAsync(NodeId nodeId, DateTime startTime, DateTime endTime,
        AggregateType aggregate, double intervalMs = 3600000, CancellationToken ct = default)
    {
        HistoryReadAggregateCallCount++;
        LastAggregateType = aggregate;
        if (HistoryException != null) throw HistoryException;
        return Task.FromResult(HistoryAggregateResult);
    }

    /// <inheritdoc />
    public Task<RedundancyInfo> GetRedundancyInfoAsync(CancellationToken ct = default)
    {
        GetRedundancyInfoCallCount++;
        if (RedundancyException != null) throw RedundancyException;
        return Task.FromResult(RedundancyResult!);
    }

    /// <summary>
    /// Releases fake service resources at the end of a UI test run.
    /// </summary>
    public void Dispose()
    {
        // No-op for testing
    }

    /// <summary>
    /// Raises a simulated data-change notification so UI tests can validate live update handling.
    /// </summary>
    /// <param name="args">The data change event arguments to raise.</param>
    public void RaiseDataChanged(DataChangedEventArgs args)
    {
        DataChanged?.Invoke(this, args);
    }

    /// <summary>
    /// Raises a simulated alarm event so UI tests can validate alarm-list behavior.
    /// </summary>
    /// <param name="args">The alarm event arguments to raise.</param>
    public void RaiseAlarmEvent(AlarmEventArgs args)
    {
        AlarmEvent?.Invoke(this, args);
    }

    /// <summary>
    /// Raises a simulated connection-state transition so UI tests can validate status presentation and failover behavior.
    /// </summary>
    /// <param name="args">The connection state changed event arguments to raise.</param>
    public void RaiseConnectionStateChanged(ConnectionStateChangedEventArgs args)
    {
        ConnectionStateChanged?.Invoke(this, args);
    }
}
