using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.LocalDb;

namespace ZB.MOM.WW.ScadaBridge.TestSupport;

/// <summary>
/// A real <see cref="ILocalDb"/> over a temp file, for tests that construct a
/// LocalDb-backed store directly.
/// </summary>
/// <remarks>
/// <para>
/// The stores take <see cref="ILocalDb"/> rather than a connection string, and there is
/// no in-memory mode — <c>LocalDbOptions.Path</c> is a filesystem path. A real one is
/// used rather than a stub on purpose: connections handed out by <c>ILocalDb</c> carry
/// the <c>zb_hlc_next()</c> UDF that the capture triggers call, so a stub returning a
/// bare <c>SqliteConnection</c> would pass these tests while failing closed the moment
/// the table is registered for replication.
/// </para>
/// <para>
/// No <c>onReady</c> callback and no <c>RegisterReplicated</c>: each store's own schema
/// initialization creates its table, which is what keeps a directly-constructed store
/// self-sufficient. Registration is the host's job (<c>SiteLocalDbSetup.OnReady</c>).
/// </para>
/// <para>
/// This lives in a shared support project rather than being copied per test project:
/// seven test projects construct stores that now take <see cref="ILocalDb"/>, and
/// duplicating the fixture would mean seven places to keep the WAL-sidecar cleanup and
/// the "real, not stubbed" rationale correct.
/// </para>
/// </remarks>
public sealed class TestLocalDb : IDisposable
{
    private readonly ServiceProvider _provider;

    private TestLocalDb(ServiceProvider provider, ILocalDb db)
    {
        _provider = provider;
        Db = db;
    }

    /// <summary>The live local database.</summary>
    public ILocalDb Db { get; }

    /// <summary>Opens a local database at <paramref name="databasePath"/>.</summary>
    public static TestLocalDb Create(string databasePath)
    {
        var configuration = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["LocalDb:Path"] = databasePath,
            })
            .Build();

        var provider = new ServiceCollection()
            .AddZbLocalDb(configuration)
            .BuildServiceProvider();

        return new TestLocalDb(provider, provider.GetRequiredService<ILocalDb>());
    }

    /// <summary>
    /// Creates a local database at a fresh temp path. Dispose the instance, then call
    /// <see cref="DeleteFiles"/> with <see cref="Path"/> to clean up.
    /// </summary>
    public static TestLocalDb CreateTemp(string prefix = "localdb")
    {
        var path = System.IO.Path.Combine(
            System.IO.Path.GetTempPath(), $"{prefix}-{Guid.NewGuid():N}.db");
        var db = Create(path);
        db.Path = path;
        return db;
    }

    /// <summary>The filesystem path, when created via <see cref="CreateTemp"/>.</summary>
    public string Path { get; private set; } = string.Empty;

    /// <summary>
    /// Deletes <paramref name="databasePath"/> and its WAL sidecars. Call only after the
    /// owning <see cref="TestLocalDb"/> is disposed — the master connection anchors the WAL.
    /// </summary>
    public static void DeleteFiles(string databasePath)
    {
        foreach (var suffix in new[] { "", "-wal", "-shm" })
        {
            try { File.Delete(databasePath + suffix); } catch { /* best effort */ }
        }
    }

    public void Dispose() => _provider.Dispose();
}
