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

namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;

/// <summary>Connection parameters resolved from the flat config dict.</summary>
public record MxGatewayConnectionOptions(
    string Endpoint, string ApiKey, string ClientName, int WriteUserId,
    bool UseTls, string? CaFile, string? ServerName, int ReadTimeoutMs,
    // Maximum in-flight supervisory advise commands on the bulk-subscribe path when
    // WriteUserId == 0 (the gateway worker has no BULK supervisory advise). Sourced from
    // DataConnectionOptions.MxSupervisoryAdviseParallelism.
    int SupervisoryAdviseParallelism = 16);

/// <summary>One advised-tag value change pushed from the gateway event stream.</summary>
public record MxValueUpdate(string TagPath, object? Value, QualityCode Quality, DateTimeOffset Timestamp);

/// <summary>Per-tag read outcome.</summary>
public record MxReadOutcome(string TagPath, bool Success, object? Value, QualityCode Quality, DateTimeOffset Timestamp, string? Error);

/// <summary>Per-tag write outcome.</summary>
public record MxWriteOutcome(string TagPath, bool Success, string? Error);

/// <summary>Per-tag outcome of a bulk subscribe (AddItem + Advise in one gateway command).</summary>
/// <param name="TagPath">The requested tag address.</param>
/// <param name="Success">Whether the item was added and advised.</param>
/// <param name="SubscriptionId">Gateway item handle (as a string) when successful.</param>
/// <param name="Error">Per-tag failure reason when not successful.</param>
public record MxSubscribeOutcome(string TagPath, bool Success, string? SubscriptionId, string? Error);

/// <summary>One node in a Galaxy browse level.</summary>
public record MxBrowseChild(string NodeId, string DisplayName, BrowseNodeClass NodeClass, bool HasChildren, string? DataType = null);

/// <summary>
/// Seam over the MxAccess Gateway .NET client + Galaxy repository client. Decouples
/// <see cref="MxGatewayDataConnection"/> from the generated gRPC/protobuf types so the
/// adapter is unit-testable with a fake. The real implementation lives in
/// <c>RealMxGatewayClient</c>.
/// </summary>
public interface IMxGatewayClient : IAsyncDisposable
{
    /// <summary>Opens the gateway session and registers the client (Register → serverHandle held internally).</summary>
    /// <param name="options">Resolved connection parameters.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    Task ConnectAsync(MxGatewayConnectionOptions options, CancellationToken ct = default);

    /// <summary>Closes the session.</summary>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    Task DisconnectAsync(CancellationToken ct = default);

    /// <summary>AddItem + Advise; returns the gateway item handle (as a string subscription id).</summary>
    /// <param name="tagPath">Tag address to subscribe to.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that resolves to the gateway item handle (subscription id).</returns>
    Task<string> SubscribeAsync(string tagPath, CancellationToken ct = default);

    /// <summary>UnAdvise + RemoveItem for a previously returned subscription id.</summary>
    /// <param name="subscriptionId">Subscription id returned by <see cref="SubscribeAsync"/>.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    Task UnsubscribeAsync(string subscriptionId, CancellationToken ct = default);

    /// <summary>
    /// Adds and advises MANY tags in as few gateway commands as the worker allows —
    /// ONE <c>SubscribeBulk</c> round trip in plain-advise mode, or one
    /// <c>AddItemBulk</c> plus bounded-parallel supervisory advises when the connection
    /// has no write-user context (the worker has no bulk supervisory advise).
    /// Replaces the historical 2-RPC-per-tag AddItem + Advise pair.
    /// </summary>
    /// <param name="tagPaths">Tag addresses to subscribe.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>One outcome per requested tag path, in request order.</returns>
    Task<IReadOnlyList<MxSubscribeOutcome>> SubscribeBulkAsync(
        IReadOnlyList<string> tagPaths, CancellationToken ct = default);

    /// <summary>UnAdvise + RemoveItem for many subscription ids in one gateway command.</summary>
    /// <param name="subscriptionIds">Subscription ids previously returned by a subscribe call.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    Task UnsubscribeBulkAsync(IReadOnlyList<string> subscriptionIds, CancellationToken ct = default);

    /// <summary>Snapshot read of one or more tags (ReadBulk).</summary>
    /// <param name="tagPaths">Tag addresses to read.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that resolves to one outcome per requested tag path.</returns>
    Task<IReadOnlyList<MxReadOutcome>> ReadAsync(IReadOnlyList<string> tagPaths, CancellationToken ct = default);

    /// <summary>Write one or more tag/value pairs (WriteBulk with the configured WriteUserId).</summary>
    /// <param name="writes">Tag/value pairs to write.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that resolves to one outcome per requested write.</returns>
    Task<IReadOnlyList<MxWriteOutcome>> WriteAsync(IReadOnlyList<(string TagPath, object? Value)> writes, CancellationToken ct = default);

    /// <summary>One Galaxy browse level (BrowseChildren). <paramref name="parentNodeId"/> null → root.</summary>
    /// <param name="parentNodeId">Parent node id (Galaxy contained path), or null for root.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that resolves to the child nodes and a flag indicating whether the result was truncated.</returns>
    Task<(IReadOnlyList<MxBrowseChild> Children, bool Truncated)> BrowseChildrenAsync(string? parentNodeId, CancellationToken ct = default);

    /// <summary>
    /// Long-running event consumer. Invokes <paramref name="onUpdate"/> for each advised-tag
    /// data change. Resumes from the last delivered worker sequence on reconnect. Completes
    /// (or throws) when the stream ends — the adapter treats that as a disconnect.
    /// </summary>
    /// <param name="onUpdate">Callback invoked per advised-tag value change.</param>
    /// <param name="ct">Cancellation token; ends the loop when cancelled.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    Task RunEventLoopAsync(Action<MxValueUpdate> onUpdate, CancellationToken ct = default);

    /// <summary>
    /// Long-running consumer of the gateway's session-less StreamAlarms feed. Emits a
    /// Snapshot…SnapshotComplete replay of active alarms then live transitions. Re-opens
    /// the stream internally on transport faults (the source replays a fresh snapshot).
    /// Completes only when <paramref name="ct"/> is cancelled.
    /// </summary>
    /// <param name="alarmFilterPrefix">Optional source-reference prefix to scope the feed; null = gateway-wide.</param>
    /// <param name="onTransition">Callback invoked per native alarm transition.</param>
    /// <param name="ct">Cancellation token; ends the loop when cancelled.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    Task RunAlarmStreamAsync(string? alarmFilterPrefix, Action<NativeAlarmTransition> onTransition, CancellationToken ct = default);
}

/// <summary>Builds <see cref="IMxGatewayClient"/> instances.</summary>
public interface IMxGatewayClientFactory
{
    /// <summary>Creates a new, unconnected client instance.</summary>
    /// <returns>A new <see cref="IMxGatewayClient"/> ready to be connected.</returns>
    IMxGatewayClient Create();
}
