using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.ScadaBridge.AuditLog.Site;
using ZB.MOM.WW.ScadaBridge.AuditLog.Tests.TestSupport;
using ZB.MOM.WW.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;

namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Site;

/// <summary>
/// Tests for <see cref="SqliteAuditWriter.PurgeExpiredAsync"/> (arch-review 04, S1/U2).
/// The site SQLite store is ephemeral (~7-day retention); the retention purge deletes
/// rows that have already left the site (Forwarded/Reconciled) and are older than the
/// cutoff, while honoring the hard <see cref="AuditForwardState"/> invariant: a
/// <see cref="AuditForwardState.Pending"/> row is NEVER purged on age alone — dropping
/// an un-forwarded row would silently lose audit data that never reached central.
/// Fixture mirrors <see cref="SqliteAuditWriterBacklogStatsTests"/> (file-backed so the
/// post-purge incremental_vacuum has a real file to shrink).
/// </summary>
public class SqliteAuditWriterRetentionTests : IDisposable
{
    private readonly string _dbPath;

    public SqliteAuditWriterRetentionTests()
    {
        _dbPath = Path.Combine(Path.GetTempPath(),
            $"audit-retention-{Guid.NewGuid():N}.db");
    }

    public void Dispose()
    {
        if (File.Exists(_dbPath))
        {
            try { File.Delete(_dbPath); } catch { /* test cleanup best-effort */ }
        }
    }

    private SqliteAuditWriter CreateWriter()
    {
        var options = new SqliteAuditWriterOptions { DatabasePath = _dbPath };
        return new SqliteAuditWriter(
            Options.Create(options),
            NullLogger<SqliteAuditWriter>.Instance,
            new FakeNodeIdentityProvider());
    }

    private static async Task<Guid> WriteEventAsync(SqliteAuditWriter writer, DateTime occurredAtUtc)
    {
        var id = Guid.NewGuid();
        var evt = ScadaBridgeAuditEventFactory.Create(
            eventId: id,
            occurredAtUtc: occurredAtUtc,
            channel: AuditChannel.ApiOutbound,
            kind: AuditKind.ApiCall,
            status: AuditStatus.Delivered);
        await writer.WriteAsync(evt);
        return id;
    }

    [Fact]
    public async Task PurgeExpired_DeletesForwardedAndReconciled_OlderThanCutoff()
    {
        await using var writer = CreateWriter();
        var oldForwarded = await WriteEventAsync(writer, DateTime.UtcNow.AddDays(-10));
        var oldReconciled = await WriteEventAsync(writer, DateTime.UtcNow.AddDays(-10));
        var oldPending = await WriteEventAsync(writer, DateTime.UtcNow.AddDays(-10));
        var fresh = await WriteEventAsync(writer, DateTime.UtcNow);
        await writer.MarkForwardedAsync(new[] { oldForwarded, oldReconciled, fresh });
        await writer.MarkReconciledAsync(new[] { oldReconciled });

        var purged = await writer.PurgeExpiredAsync(DateTime.UtcNow.AddDays(-7));

        Assert.Equal(2, purged); // oldForwarded + oldReconciled
        // Hard ForwardState invariant: the old *Pending* row survives on age alone.
        var pending = await writer.ReadPendingAsync(10);
        Assert.Contains(pending, e => e.EventId == oldPending);
        // Fresh forwarded row inside the window survives; only oldPending remains Pending.
        var stats = await writer.GetBacklogStatsAsync();
        Assert.Equal(1, stats.PendingCount);
    }

    [Fact]
    public async Task PurgeExpired_IsIdempotent()
    {
        await using var writer = CreateWriter();
        var id = await WriteEventAsync(writer, DateTime.UtcNow.AddDays(-10));
        await writer.MarkForwardedAsync(new[] { id });
        Assert.Equal(1, await writer.PurgeExpiredAsync(DateTime.UtcNow.AddDays(-7)));
        Assert.Equal(0, await writer.PurgeExpiredAsync(DateTime.UtcNow.AddDays(-7)));
    }
}
