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

namespace ZB.MOM.WW.OtOpcUa.Driver.Sql.Browser.Tests;

/// <summary>
///     Records how many command executions are ever in flight at once, and how long each one is artificially
///     held open. Shared by <see cref="ConcurrencyProbingDbConnection"/> and the commands it creates.
/// </summary>
/// <remarks>
///     This exists because "the session serializes on a gate" is otherwise unfalsifiable from the outside:
///     against a real SQLite connection, overlapping calls mostly still *work*, so a test that only asserted
///     correct results would pass with the gate deleted. Measuring the overlap directly is what makes the
///     gate's removal visible.
/// </remarks>
public sealed class ConcurrencyProbe
{
    private int _inFlight;
    private int _peak;

    /// <summary>How long each command execution is held before it reaches the real provider.</summary>
    public TimeSpan Delay { get; init; } = TimeSpan.FromMilliseconds(120);

    /// <summary>The greatest number of executions observed in flight simultaneously.</summary>
    public int Peak => Volatile.Read(ref _peak);

    /// <summary>Marks the start of one command execution.</summary>
    public void Enter()
    {
        var current = Interlocked.Increment(ref _inFlight);
        int observed;
        while (current > (observed = Volatile.Read(ref _peak)))
        {
            if (Interlocked.CompareExchange(ref _peak, current, observed) == observed) break;
        }
    }

    /// <summary>Marks the end of one command execution.</summary>
    public void Exit() => Interlocked.Decrement(ref _inFlight);
}

/// <summary>
///     A pass-through <see cref="DbConnection"/> that hands out commands instrumented with a
///     <see cref="ConcurrencyProbe"/>. Everything else delegates to the wrapped connection, so the code under
///     test runs against a real SQLite catalog.
/// </summary>
/// <param name="inner">The real connection to delegate to. Not owned — the caller disposes it.</param>
/// <param name="probe">The probe that observes command overlap.</param>
public sealed class ConcurrencyProbingDbConnection(DbConnection inner, ConcurrencyProbe probe) : DbConnection
{
    /// <inheritdoc />
    [System.Diagnostics.CodeAnalysis.AllowNull]
    public override string ConnectionString
    {
        get => inner.ConnectionString;
        set => inner.ConnectionString = value!;
    }

    /// <inheritdoc />
    public override string Database => inner.Database;

    /// <inheritdoc />
    public override string DataSource => inner.DataSource;

    /// <inheritdoc />
    public override string ServerVersion => inner.ServerVersion;

    /// <inheritdoc />
    public override ConnectionState State => inner.State;

    /// <inheritdoc />
    public override void ChangeDatabase(string databaseName) => inner.ChangeDatabase(databaseName);

    /// <inheritdoc />
    public override void Close() => inner.Close();

    /// <inheritdoc />
    public override void Open() => inner.Open();

    /// <inheritdoc />
    protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) =>
        inner.BeginTransaction(isolationLevel);

    /// <inheritdoc />
    protected override DbCommand CreateDbCommand() =>
        new ConcurrencyProbingDbCommand(inner.CreateCommand(), this, probe);
}

/// <summary>
///     A pass-through <see cref="DbCommand"/> that reports every execution to a <see cref="ConcurrencyProbe"/>
///     and holds it open for the probe's delay, so overlapping executions are observable.
/// </summary>
/// <param name="inner">The real command to delegate to.</param>
/// <param name="owner">The connection that created this command.</param>
/// <param name="probe">The probe that observes command overlap.</param>
public sealed class ConcurrencyProbingDbCommand(DbCommand inner, DbConnection owner, ConcurrencyProbe probe)
    : DbCommand
{
    /// <inheritdoc />
    [System.Diagnostics.CodeAnalysis.AllowNull]
    public override string CommandText
    {
        get => inner.CommandText;
        set => inner.CommandText = value!;
    }

    /// <inheritdoc />
    public override int CommandTimeout
    {
        get => inner.CommandTimeout;
        set => inner.CommandTimeout = value;
    }

    /// <inheritdoc />
    public override CommandType CommandType
    {
        get => inner.CommandType;
        set => inner.CommandType = value;
    }

    /// <inheritdoc />
    public override bool DesignTimeVisible
    {
        get => inner.DesignTimeVisible;
        set => inner.DesignTimeVisible = value;
    }

    /// <inheritdoc />
    public override UpdateRowSource UpdatedRowSource
    {
        get => inner.UpdatedRowSource;
        set => inner.UpdatedRowSource = value;
    }

    /// <inheritdoc />
    protected override DbConnection? DbConnection
    {
        get => owner;
        set { /* The session never reassigns the connection; the wrapped command keeps its own. */ }
    }

    /// <inheritdoc />
    protected override DbParameterCollection DbParameterCollection => inner.Parameters;

    /// <inheritdoc />
    protected override DbTransaction? DbTransaction
    {
        get => inner.Transaction;
        set => inner.Transaction = value;
    }

    /// <inheritdoc />
    public override void Cancel() => inner.Cancel();

    /// <inheritdoc />
    public override int ExecuteNonQuery() => inner.ExecuteNonQuery();

    /// <inheritdoc />
    public override object? ExecuteScalar() => inner.ExecuteScalar();

    /// <inheritdoc />
    public override void Prepare() => inner.Prepare();

    /// <inheritdoc />
    protected override DbParameter CreateDbParameter() => inner.CreateParameter();

    /// <inheritdoc />
    protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
    {
        probe.Enter();
        try
        {
            Thread.Sleep(probe.Delay);
            return inner.ExecuteReader(behavior);
        }
        finally
        {
            probe.Exit();
        }
    }

    /// <inheritdoc />
    protected override async Task<DbDataReader> ExecuteDbDataReaderAsync(
        CommandBehavior behavior, CancellationToken cancellationToken)
    {
        probe.Enter();
        try
        {
            await Task.Delay(probe.Delay, cancellationToken).ConfigureAwait(false);
            return await inner.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false);
        }
        finally
        {
            probe.Exit();
        }
    }

    /// <inheritdoc />
    protected override void Dispose(bool disposing)
    {
        if (disposing) inner.Dispose();
        base.Dispose(disposing);
    }
}
