using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ZB.MOM.WW.MxGateway.Client;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;

/// <summary>
///     Driver-side wrapper around the gateway's <see cref="MxGatewaySession"/>. Owns the
///     MXAccess <c>Register</c> handle, caches the per-tag item handles AddItem returns,
///     and coordinates the read / write / subscribe call paths. PRs 4.2-4.5 fill this in
///     incrementally:
///     <list type="bullet">
///       <item>PR 4.2 (this PR) — skeleton + lifecycle wiring.</item>
///       <item>PR 4.3 — write path.</item>
///       <item>PR 4.4 — subscription registry + event pump + the production
///         <see cref="IGalaxyDataReader"/> implementation that drives the read path.</item>
///       <item>PR 4.5 — reconnect supervisor.</item>
///     </list>
/// </summary>
public sealed class GalaxyMxSession : IAsyncDisposable
{
    private readonly GalaxyMxAccessOptions _options;
    private readonly ILogger _logger;

    // Owned gateway client + session — populated when ConnectAsync runs. Tests can leave
    // them null and exercise the surface via injected IGalaxyDataReader fakes.
    private MxGatewayClient? _ownedClient;
    private MxGatewaySession? _session;
    private int _serverHandle;
    private bool _disposed;

    /// <summary>Initializes a new instance of the GalaxyMxSession class.</summary>
    /// <param name="options">MX Access configuration options.</param>
    /// <param name="logger">Optional logger instance; uses NullLogger if not provided.</param>
    public GalaxyMxSession(GalaxyMxAccessOptions options, ILogger? logger = null)
    {
        _options = options ?? throw new ArgumentNullException(nameof(options));
        _logger = logger ?? NullLogger.Instance;
    }

    /// <summary>Gets a value indicating whether the session is connected.</summary>
    public bool IsConnected => _session is not null;

    /// <summary>
    ///     Server-side handle returned by MXAccess <c>Register</c>. Zero before
    ///     <see cref="ConnectAsync"/> opens the session.
    /// </summary>
    public int ServerHandle => _serverHandle;

    /// <summary>
    ///     Connect the underlying gateway client + open an MXAccess session + register the
    ///     configured client name. Idempotent — second calls are no-ops while
    ///     <see cref="IsConnected"/> is true.
    /// </summary>
    /// <param name="clientOptions">The MX gateway client options.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    public async Task ConnectAsync(MxGatewayClientOptions clientOptions, CancellationToken cancellationToken)
    {
        ObjectDisposedException.ThrowIf(_disposed, this);
        if (_session is not null) return;

        _ownedClient = MxGatewayClient.Create(clientOptions);
        _session = await _ownedClient.OpenSessionAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
        _serverHandle = await _session.RegisterAsync(_options.ClientName, cancellationToken).ConfigureAwait(false);
        _logger.LogInformation(
            "GalaxyMxSession connected — clientName={ClientName} serverHandle={Handle}",
            _options.ClientName, _serverHandle);
    }

    /// <summary>
    ///     Test seam — attach a session opened externally (e.g. against an in-process gw
    ///     fake). Skips the gateway-client construction so tests can drive the session
    ///     surface without spinning a real gRPC channel. Caller retains client ownership.
    /// </summary>
    /// <param name="session">The MX gateway session to attach.</param>
    /// <param name="serverHandle">The server handle value to use.</param>
    internal void AttachForTests(MxGatewaySession session, int serverHandle)
    {
        ObjectDisposedException.ThrowIf(_disposed, this);
        _session = session ?? throw new ArgumentNullException(nameof(session));
        _serverHandle = serverHandle;
    }

    /// <summary>
    ///     Returns the underlying gateway session. Null until <see cref="ConnectAsync"/> or
    ///     <see cref="AttachForTests"/> runs. PR 4.3 / 4.4 use this to issue commands.
    /// </summary>
    public MxGatewaySession? Session => _session;

    /// <summary>Disposes the session and underlying gateway client resources.</summary>
    public async ValueTask DisposeAsync()
    {
        if (_disposed) return;
        _disposed = true;

        if (_session is not null)
        {
            try { await _session.DisposeAsync().ConfigureAwait(false); }
            catch (Exception ex) { _logger.LogWarning(ex, "GalaxyMxSession session dispose failed (best-effort)"); }
        }
        _session = null;

        if (_ownedClient is not null)
        {
            try { await _ownedClient.DisposeAsync().ConfigureAwait(false); }
            catch (Exception ex) { _logger.LogWarning(ex, "GalaxyMxSession client dispose failed (best-effort)"); }
        }
        _ownedClient = null;
    }
}
