using Microsoft.Data.Sqlite;
using ZB.MOM.WW.LocalDb.Internal;

namespace ZB.MOM.WW.LocalDb.Tests;

/// <summary>
/// Sustained concurrent write pressure against a real on-disk LocalDb file —
/// the gap named by criterion 5 of
/// ScadaBridge/docs/known-issues/2026-07-20-localdb-disk-io-error-under-load.md §9:
/// Phase 1 shipped only correctness/convergence tests, so nothing exercised
/// many pooled per-operation connections writing a registered (trigger +
/// zb_hlc_next UDF) table at once, which is exactly the shape the ScadaBridge
/// site nodes run under load.
/// </summary>
public sealed class ConcurrentWriteLoadTests : IDisposable
{
    private readonly string _path = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".db");

    public void Dispose()
    {
        SqliteConnection.ClearAllPools();
        foreach (var suffix in new[] { "", "-wal", "-shm" })
        {
            var f = _path + suffix;
            if (File.Exists(f))
                File.Delete(f);
        }
    }

    [Fact]
    public async Task Sustained_Concurrent_Writes_Through_Pooled_Connections_All_Succeed()
    {
        const int Writers = 8;
        const int PerWriter = 250;

        using var db = new SqliteLocalDb(new LocalDbOptions { Path = _path });
        await db.ExecuteAsync(
            "CREATE TABLE load_t (id TEXT PRIMARY KEY, writer INTEGER NOT NULL, seq INTEGER NOT NULL, payload TEXT NOT NULL)");
        db.RegisterReplicated("load_t");

        var payload = new string('x', 256);

        // Concurrent readers churn pooled connections + the WAL index while the
        // writers run; WAL readers must never fail alongside writers.
        using var stopReaders = new CancellationTokenSource();
        var reader = Task.Run(async () =>
        {
            while (!stopReaders.IsCancellationRequested)
            {
                await db.QueryAsync("SELECT COUNT(*) FROM load_t", r => r.GetInt64(0));
            }
        });

        // One fresh pooled connection per operation (the production pattern),
        // every insert firing the capture trigger + zb_hlc_next UDF.
        var writers = Enumerable.Range(0, Writers).Select(w => Task.Run(async () =>
        {
            for (var i = 0; i < PerWriter; i++)
            {
                await db.ExecuteAsync(
                    "INSERT INTO load_t (id, writer, seq, payload) VALUES (@id, @writer, @seq, @payload)",
                    new { id = $"{w}-{i}", writer = w, seq = i, payload });
            }
        })).ToArray();

        await Task.WhenAll(writers); // faults on the first failed write
        stopReaders.Cancel();
        await reader;

        var rows = (await db.QueryAsync("SELECT COUNT(*) FROM load_t", r => r.GetInt64(0)))[0];
        Assert.Equal((long)Writers * PerWriter, rows);

        // Every write must have been captured to the oplog exactly once.
        var oplog = (await db.QueryAsync("SELECT COUNT(*) FROM __localdb_oplog", r => r.GetInt64(0)))[0];
        Assert.Equal((long)Writers * PerWriter, oplog);
    }
}
