using System.Collections;
using System.Data.Common;

namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;

/// <summary>
/// <see cref="DbDataReader"/> decorator that
/// counts the number of rows read by the script and fires a single audit
/// emission callback when the reader closes.
/// </summary>
/// <remarks>
/// <para>
/// The wrapping reader counts each successful <see cref="Read"/> /
/// <see cref="ReadAsync(CancellationToken)"/> and invokes <c>onClose</c>
/// exactly once — on <see cref="Close"/>, <see cref="CloseAsync"/>, or
/// disposal — with the running tally. This lets
/// <see cref="AuditingDbCommand"/> emit one
/// <c>DbOutbound</c>/<c>DbWrite</c> row per <c>ExecuteReader</c> with
/// <c>Extra.rowsReturned</c> populated, matching the vocabulary lock.
/// </para>
/// <para>
/// Multiple result sets via <see cref="NextResult"/> are folded into a single
/// <c>rowsReturned</c> tally — the script sees one audit row per
/// <c>ExecuteReader</c> call, not per result set.
/// </para>
/// </remarks>
internal sealed class AuditingDbDataReader : DbDataReader
{
    private readonly DbDataReader _inner;
    private readonly Action<int> _onClose;
    private int _rowsReturned;
    private bool _closed;

    /// <summary>
    /// Initializes a new instance of the <see cref="AuditingDbDataReader"/> class, wrapping a data reader to count rows read.
    /// </summary>
    /// <param name="inner">The underlying DbDataReader to wrap and audit.</param>
    /// <param name="onClose">Callback invoked once when the reader closes, receiving the total rows read.</param>
    public AuditingDbDataReader(DbDataReader inner, Action<int> onClose)
    {
        _inner = inner ?? throw new ArgumentNullException(nameof(inner));
        _onClose = onClose ?? throw new ArgumentNullException(nameof(onClose));
    }

    // -- Row-count interception ------------------------------------------

    /// <inheritdoc />
    public override bool Read()
    {
        var more = _inner.Read();
        if (more) _rowsReturned++;
        return more;
    }

    /// <inheritdoc />
    public override async Task<bool> ReadAsync(CancellationToken cancellationToken)
    {
        var more = await _inner.ReadAsync(cancellationToken).ConfigureAwait(false);
        if (more) _rowsReturned++;
        return more;
    }

    /// <inheritdoc />
    public override void Close()
    {
        if (!_closed)
        {
            _closed = true;
            try { _inner.Close(); }
            finally { SafeFireOnClose(); }
        }
    }

    /// <inheritdoc />
    public override async Task CloseAsync()
    {
        if (!_closed)
        {
            _closed = true;
            try { await _inner.CloseAsync().ConfigureAwait(false); }
            finally { SafeFireOnClose(); }
        }
    }

    /// <inheritdoc />
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // DbDataReader.Dispose calls Close on most providers, but we
            // guard with _closed to ensure onClose fires exactly once.
            if (!_closed)
            {
                _closed = true;
                try { _inner.Dispose(); }
                finally { SafeFireOnClose(); }
            }
            else
            {
                _inner.Dispose();
            }
        }
        base.Dispose(disposing);
    }

    /// <inheritdoc />
    public override async ValueTask DisposeAsync()
    {
        if (!_closed)
        {
            _closed = true;
            try { await _inner.DisposeAsync().ConfigureAwait(false); }
            finally { SafeFireOnClose(); }
        }
        else
        {
            await _inner.DisposeAsync().ConfigureAwait(false);
        }
        GC.SuppressFinalize(this);
    }

    private void SafeFireOnClose()
    {
        // The onClose callback runs the audit emission, which is itself
        // best-effort and swallows internally — but defend the reader's own
        // close path anyway so an audit fault never propagates out of
        // Close/Dispose.
        try { _onClose(_rowsReturned); }
        catch { /* audit emission is best-effort by contract */ }
    }

    // -- Forwarded surface ------------------------------------------------

    /// <inheritdoc />
    public override object this[int ordinal] => _inner[ordinal];
    /// <inheritdoc />
    public override object this[string name] => _inner[name];
    /// <inheritdoc />
    public override int Depth => _inner.Depth;
    /// <inheritdoc />
    public override int FieldCount => _inner.FieldCount;
    /// <inheritdoc />
    public override bool HasRows => _inner.HasRows;
    /// <inheritdoc />
    public override bool IsClosed => _inner.IsClosed;
    /// <inheritdoc />
    public override int RecordsAffected => _inner.RecordsAffected;
    /// <inheritdoc />
    public override int VisibleFieldCount => _inner.VisibleFieldCount;
    /// <inheritdoc />
    public override bool GetBoolean(int ordinal) => _inner.GetBoolean(ordinal);
    /// <inheritdoc />
    public override byte GetByte(int ordinal) => _inner.GetByte(ordinal);
    /// <inheritdoc />
    public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length)
        => _inner.GetBytes(ordinal, dataOffset, buffer, bufferOffset, length);
    /// <inheritdoc />
    public override char GetChar(int ordinal) => _inner.GetChar(ordinal);
    /// <inheritdoc />
    public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length)
        => _inner.GetChars(ordinal, dataOffset, buffer, bufferOffset, length);
    /// <inheritdoc />
    public override string GetDataTypeName(int ordinal) => _inner.GetDataTypeName(ordinal);
    /// <inheritdoc />
    public override DateTime GetDateTime(int ordinal) => _inner.GetDateTime(ordinal);
    /// <inheritdoc />
    public override decimal GetDecimal(int ordinal) => _inner.GetDecimal(ordinal);
    /// <inheritdoc />
    public override double GetDouble(int ordinal) => _inner.GetDouble(ordinal);
    /// <inheritdoc />
    public override IEnumerator GetEnumerator() => ((IEnumerable)_inner).GetEnumerator();
    /// <inheritdoc />
    public override Type GetFieldType(int ordinal) => _inner.GetFieldType(ordinal);
    /// <inheritdoc />
    public override float GetFloat(int ordinal) => _inner.GetFloat(ordinal);
    /// <inheritdoc />
    public override Guid GetGuid(int ordinal) => _inner.GetGuid(ordinal);
    /// <inheritdoc />
    public override short GetInt16(int ordinal) => _inner.GetInt16(ordinal);
    /// <inheritdoc />
    public override int GetInt32(int ordinal) => _inner.GetInt32(ordinal);
    /// <inheritdoc />
    public override long GetInt64(int ordinal) => _inner.GetInt64(ordinal);
    /// <inheritdoc />
    public override string GetName(int ordinal) => _inner.GetName(ordinal);
    /// <inheritdoc />
    public override int GetOrdinal(string name) => _inner.GetOrdinal(name);
    /// <inheritdoc />
    public override string GetString(int ordinal) => _inner.GetString(ordinal);
    /// <inheritdoc />
    public override object GetValue(int ordinal) => _inner.GetValue(ordinal);
    /// <inheritdoc />
    public override int GetValues(object[] values) => _inner.GetValues(values);
    /// <inheritdoc />
    public override bool IsDBNull(int ordinal) => _inner.IsDBNull(ordinal);
    /// <inheritdoc />
    public override bool NextResult() => _inner.NextResult();
    /// <inheritdoc />
    public override Task<bool> NextResultAsync(CancellationToken cancellationToken) => _inner.NextResultAsync(cancellationToken);
}
