using Baya.Application.Contracts.Common;
using Baya.Domain.Entities.Audit;
using Baya.Infrastructure.Persistence;
using Baya.Infrastructure.Persistence.Services.Audit;
using Baya.Tests.Setup.Setups;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using NSubstitute;

namespace Baya.Test.Foundation.Audit;

/// <summary>
/// refinement-phase-9 §9.4: the two-tier audit-log retention sweep. Everyday rows are deleted past the general
/// window; financial/verification rows survive until the longer legal window. The sweep is idempotent.
/// </summary>
public sealed class AuditLogRetentionTests : IDisposable
{
    private static readonly DateTimeOffset Now = new(2026, 8, 10, 10, 0, 0, TimeSpan.Zero);
    private const int GeneralDays = 730;
    private const int FinancialDays = 2555;
    private static readonly string[] FinancialTypes = ["Refund", "NursePayout", "NurseVerification", "PlatformConfig"];

    private readonly SqliteConnection _connection;
    private readonly ApplicationDbContext _db;
    private readonly AuditLogger _audit;

    public AuditLogRetentionTests()
    {
        _connection = new SqliteConnection("DataSource=:memory:");
        _connection.Open();
        var options = new DbContextOptionsBuilder<ApplicationDbContext>().UseSqlite(_connection).Options;
        _db = new ApplicationDbContext(options, TestFieldEncryptor.Instance);
        _db.Database.EnsureCreated();

        var clock = Substitute.For<IDateTimeProvider>();
        clock.UtcNow.Returns(Now);
        _audit = new AuditLogger(_db, Substitute.For<ICurrentUser>(), clock);
    }

    private void Seed(string entityType, int ageDays)
    {
        _db.Set<AuditLog>().Add(new AuditLog
        {
            EntityType = entityType,
            EntityId = $"{entityType}-{ageDays}",
            Action = AuditAction.Updated,
            OccurredAt = Now.AddDays(-ageDays)
        });
        _db.SaveChanges();
    }

    [Fact]
    public async Task Purge_deletes_expired_general_rows_and_keeps_recent_and_financial()
    {
        Seed("SomeEntity", ageDays: 800);       // general, past 730 → deleted
        Seed("SomeEntity", ageDays: 700);       // general, within 730 → kept
        Seed("Refund", ageDays: 800);           // financial, within 2555 → kept
        Seed("NursePayout", ageDays: 3000);     // financial, past 2555 → deleted

        var deleted = await _audit.PurgeExpiredAsync(GeneralDays, FinancialDays, FinancialTypes, maxRowsPerRun: 1000, CancellationToken.None);

        Assert.Equal(2, deleted);
        var survivors = await _db.Set<AuditLog>().AsNoTracking().Select(a => a.EntityId).ToListAsync();
        Assert.Equal(2, survivors.Count);
        Assert.Contains("SomeEntity-700", survivors);
        Assert.Contains("Refund-800", survivors);
    }

    [Fact]
    public async Task Purge_is_idempotent_when_nothing_is_expired()
    {
        Seed("SomeEntity", ageDays: 10);
        Seed("Refund", ageDays: 100);

        var first = await _audit.PurgeExpiredAsync(GeneralDays, FinancialDays, FinancialTypes, maxRowsPerRun: 1000, CancellationToken.None);
        var second = await _audit.PurgeExpiredAsync(GeneralDays, FinancialDays, FinancialTypes, maxRowsPerRun: 1000, CancellationToken.None);

        Assert.Equal(0, first);
        Assert.Equal(0, second);
        Assert.Equal(2, await _db.Set<AuditLog>().CountAsync());
    }

    public void Dispose()
    {
        _db.Dispose();
        _connection.Dispose();
    }
}
