using Microsoft.Extensions.Logging.Abstractions;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Persistence;
using ZB.MOM.WW.ScadaBridge.TestSupport;

namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Persistence;

/// <summary>
/// Task 14: site-local SQLite <c>native_alarm_state</c> store — mirrored native alarm
/// condition snapshots keyed by (instance, source canonical name, source reference).
/// </summary>
/// <remarks>
/// Backed by a real temp-file LocalDb: <see cref="SiteStorageService"/> takes an
/// <c>ILocalDb</c> rather than a connection string, and LocalDb has no in-memory mode.
/// </remarks>
public class NativeAlarmStateStoreTests : IAsyncLifetime, IDisposable
{
    private readonly TestLocalDb _localDb;
    private SiteStorageService _storage = null!;

    public NativeAlarmStateStoreTests()
    {
        _localDb = TestLocalDb.CreateTemp("nas");
    }

    public async Task InitializeAsync()
    {
        _storage = new SiteStorageService(_localDb.Db, NullLogger<SiteStorageService>.Instance);
        await _storage.InitializeAsync();
    }

    public Task DisposeAsync() => Task.CompletedTask;

    [Fact]
    public async Task Upsert_Then_Get_RoundTrips()
    {
        await _storage.UpsertNativeAlarmAsync("inst", "Src", "Tank01.Hi", "{\"Active\":true}", DateTimeOffset.UnixEpoch);

        var rows = await _storage.GetNativeAlarmsAsync("inst", "Src");

        Assert.Single(rows);
        Assert.Equal("Tank01.Hi", rows[0].SourceReference);
        Assert.Equal("{\"Active\":true}", rows[0].ConditionJson);
        Assert.Equal(DateTimeOffset.UnixEpoch, rows[0].LastTransitionAt);
    }

    [Fact]
    public async Task Upsert_SameKey_ReplacesConditionAndTimestamp()
    {
        await _storage.UpsertNativeAlarmAsync("inst", "Src", "Tank01.Hi", "{\"Active\":true}", DateTimeOffset.UnixEpoch);
        await _storage.UpsertNativeAlarmAsync("inst", "Src", "Tank01.Hi", "{\"Active\":false}", DateTimeOffset.UnixEpoch.AddMinutes(5));

        var rows = await _storage.GetNativeAlarmsAsync("inst", "Src");

        Assert.Single(rows);
        Assert.Equal("{\"Active\":false}", rows[0].ConditionJson);
        Assert.Equal(DateTimeOffset.UnixEpoch.AddMinutes(5), rows[0].LastTransitionAt);
    }

    [Fact]
    public async Task Get_ScopesToInstanceAndSourceCanonicalName()
    {
        await _storage.UpsertNativeAlarmAsync("inst", "SrcA", "Tank01.Hi", "{}", DateTimeOffset.UnixEpoch);
        await _storage.UpsertNativeAlarmAsync("inst", "SrcB", "Tank02.Hi", "{}", DateTimeOffset.UnixEpoch);
        await _storage.UpsertNativeAlarmAsync("other", "SrcA", "Tank09.Hi", "{}", DateTimeOffset.UnixEpoch);

        var rows = await _storage.GetNativeAlarmsAsync("inst", "SrcA");

        Assert.Single(rows);
        Assert.Equal("Tank01.Hi", rows[0].SourceReference);
    }

    [Fact]
    public async Task Delete_RemovesSingleRow()
    {
        await _storage.UpsertNativeAlarmAsync("inst", "Src", "Tank01.Hi", "{}", DateTimeOffset.UnixEpoch);
        await _storage.UpsertNativeAlarmAsync("inst", "Src", "Tank01.Lo", "{}", DateTimeOffset.UnixEpoch);

        await _storage.DeleteNativeAlarmAsync("inst", "Src", "Tank01.Hi");

        var rows = await _storage.GetNativeAlarmsAsync("inst", "Src");
        Assert.Single(rows);
        Assert.Equal("Tank01.Lo", rows[0].SourceReference);
    }

    [Fact]
    public async Task ClearForInstance_RemovesAllSourcesForInstanceOnly()
    {
        await _storage.UpsertNativeAlarmAsync("inst", "SrcA", "Tank01.Hi", "{}", DateTimeOffset.UnixEpoch);
        await _storage.UpsertNativeAlarmAsync("inst", "SrcB", "Tank02.Hi", "{}", DateTimeOffset.UnixEpoch);
        await _storage.UpsertNativeAlarmAsync("other", "SrcA", "Tank09.Hi", "{}", DateTimeOffset.UnixEpoch);

        await _storage.ClearNativeAlarmsForInstanceAsync("inst");

        Assert.Empty(await _storage.GetNativeAlarmsAsync("inst", "SrcA"));
        Assert.Empty(await _storage.GetNativeAlarmsAsync("inst", "SrcB"));
        Assert.Single(await _storage.GetNativeAlarmsAsync("other", "SrcA"));
    }

    public void Dispose()
    {
        // Dispose first — the master connection anchors the WAL, so the sidecars
        // cannot be removed while it is open.
        var path = _localDb.Path;
        _localDb.Dispose();
        TestLocalDb.DeleteFiles(path);
    }
}
