using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;

namespace ZB.MOM.WW.ScadaBridge.LoadHarness;

/// <summary>
/// In-process stand-in for an OPC UA / MxGateway server, registered on the REAL
/// <c>DataConnectionFactory</c> under the <see cref="ProtocolName"/> protocol so the
/// whole Data Connection Layer above it — <c>DataConnectionManagerActor</c>,
/// <c>DataConnectionActor</c>, its <c>_instancesByTag</c> fan-out and the
/// <c>TagValueUpdate</c> hand-off to Instance Actors — runs unmodified.
///
/// <para>
/// It implements <see cref="IBatchSubscribableConnection"/> because the batch path is
/// the one a real site takes at this scale; the per-tag fallback would make
/// subscribe setup, not steady-state throughput, the thing being measured.
/// </para>
/// <para>
/// <b>Why faking here does not invalidate the measurement.</b> Everything this class
/// replaces is on the far side of the process boundary: socket I/O, the OPC UA SDK's
/// own session/subscription machinery, and the device. The system under test — the
/// actor hierarchy, the site stream, store-and-forward, health, and the streaming
/// relay — begins at the <see cref="SubscriptionCallback"/> invocation, which is
/// exactly where the real adapter hands off. Driving 375,000 genuine monitored items
/// would measure the OPC UA stack, not ScadaBridge.
/// </para>
/// </summary>
public sealed class SimulatedDataConnection : IDataConnection, IBatchSubscribableConnection
{
    /// <summary>Protocol discriminator this adapter registers under on the factory.</summary>
    public const string ProtocolName = "LoadSim";

    private SubscriptionCallback? _callback;
    private int _subscriptionCounter;

    /// <summary>
    /// The data connection name this adapter was created for, taken from the connection
    /// details. The DCL factory creates adapters as its manager actor processes the
    /// CreateConnectionCommands, so creation order is not connection order — the driver
    /// resolves an adapter by name rather than by index.
    /// </summary>
    public string ConnectionName { get; private set; } = string.Empty;

    /// <summary>Key under which the connection name travels in the connection details.</summary>
    public const string ConnectionNameKey = "connectionName";

    /// <inheritdoc />
    public ConnectionHealth Status { get; private set; } = ConnectionHealth.Disconnected;

    /// <inheritdoc />
    public event Action? Disconnected;

    /// <summary>
    /// The callback captured at subscribe time. The tag driver invokes this to inject
    /// a value change, mirroring what the OPC UA SDK's notification thread does.
    /// Null until the site's Instance Actors have subscribed.
    /// </summary>
    public SubscriptionCallback? ValueCallback => _callback;

    /// <summary>Number of tag paths this connection has accepted subscriptions for.</summary>
    public int SubscribedTagCount => _subscriptionCounter;

    /// <inheritdoc />
    public Task ConnectAsync(IDictionary<string, string> connectionDetails, CancellationToken cancellationToken = default)
    {
        if (connectionDetails.TryGetValue(ConnectionNameKey, out var name))
            ConnectionName = name;

        Status = ConnectionHealth.Connected;
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task DisconnectAsync(CancellationToken cancellationToken = default)
    {
        Status = ConnectionHealth.Disconnected;
        Disconnected?.Invoke();
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task<string> SubscribeAsync(string tagPath, SubscriptionCallback callback, CancellationToken cancellationToken = default)
    {
        _callback = callback;
        return Task.FromResult($"sub-{Interlocked.Increment(ref _subscriptionCounter)}");
    }

    /// <inheritdoc />
    public Task<IReadOnlyList<TagSubscribeResult>> SubscribeBatchAsync(
        IReadOnlyList<string> tagPaths,
        SubscriptionCallback callback,
        CancellationToken cancellationToken = default)
    {
        _callback = callback;
        var results = new List<TagSubscribeResult>(tagPaths.Count);
        foreach (var path in tagPaths)
        {
            results.Add(new TagSubscribeResult(
                path, true, $"sub-{Interlocked.Increment(ref _subscriptionCounter)}", null));
        }

        return Task.FromResult<IReadOnlyList<TagSubscribeResult>>(results);
    }

    /// <inheritdoc />
    public Task UnsubscribeAsync(string subscriptionId, CancellationToken cancellationToken = default)
        => Task.CompletedTask;

    /// <inheritdoc />
    public Task UnsubscribeBatchAsync(IReadOnlyList<string> subscriptionIds, CancellationToken cancellationToken = default)
        => Task.CompletedTask;

    /// <inheritdoc />
    public Task<ReadResult> ReadAsync(string tagPath, CancellationToken cancellationToken = default)
        => Task.FromResult(new ReadResult(true, new TagValue(0d, QualityCode.Good, DateTimeOffset.UtcNow), null));

    /// <inheritdoc />
    public Task<IReadOnlyDictionary<string, ReadResult>> ReadBatchAsync(
        IEnumerable<string> tagPaths, CancellationToken cancellationToken = default)
    {
        var now = DateTimeOffset.UtcNow;
        var results = new Dictionary<string, ReadResult>();
        foreach (var path in tagPaths)
            results[path] = new ReadResult(true, new TagValue(0d, QualityCode.Good, now), null);

        return Task.FromResult<IReadOnlyDictionary<string, ReadResult>>(results);
    }

    /// <inheritdoc />
    public Task<WriteResult> WriteAsync(string tagPath, object? value, CancellationToken cancellationToken = default)
        => Task.FromResult(new WriteResult(true, null));

    /// <inheritdoc />
    public Task<IReadOnlyDictionary<string, WriteResult>> WriteBatchAsync(
        IDictionary<string, object?> values, CancellationToken cancellationToken = default)
    {
        var results = new Dictionary<string, WriteResult>();
        foreach (var key in values.Keys)
            results[key] = new WriteResult(true, null);

        return Task.FromResult<IReadOnlyDictionary<string, WriteResult>>(results);
    }

    /// <inheritdoc />
    public Task<bool> WriteBatchAndWaitAsync(
        IDictionary<string, object?> values, string flagPath, object? flagValue,
        string responsePath, object? responseValue, TimeSpan timeout,
        CancellationToken cancellationToken = default)
        => Task.FromResult(true);

    /// <inheritdoc />
    public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}
