using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using SIGCM2.Application.Audit;
using SIGCM2.Infrastructure.Audit.Jobs;
using SIGCM2.Infrastructure.Persistence;

namespace SIGCM2.Application.Tests.Infrastructure;

/// <summary>
/// SUITE-BE-JOBS — UDT-011 follow-up (issue #24)
/// Verifies that the 3 Quartz audit jobs accept TimeProvider via constructor injection
/// and use the injected clock rather than DateTime.UtcNow inline.
///
/// Tests are construction-level: they confirm the DI contract is satisfied.
/// Full Execute() tests require a live SQL connection and are out of scope.
/// </summary>
public sealed class AuditJobsTimeProviderTests
{
    private static SqlConnectionFactory DummyFactory() =>
        new("Server=.;Database=dummy;Integrated Security=true;");

    // ─────────────────────────────────────────────────────────────────────────
    // AuditIntegrityCheckJob
    // ─────────────────────────────────────────────────────────────────────────

    [Fact]
    public void AuditIntegrityCheckJob_AcceptsFakeTimeProvider_NoException()
    {
        var fake = new FakeTimeProvider();
        fake.SetUtcNow(new DateTimeOffset(2026, 4, 18, 1, 0, 0, TimeSpan.Zero));
        var security = Substitute.For<ISecurityEventLogger>();

        var job = new AuditIntegrityCheckJob(
            DummyFactory(),
            security,
            NullLogger<AuditIntegrityCheckJob>.Instance,
            fake);

        Assert.NotNull(job);
    }

    [Fact]
    public void AuditIntegrityCheckJob_FakeTimeProvider_ReturnsConfiguredUtcNow()
    {
        // Arrange: pin a specific UTC instant
        var expectedUtc = new DateTimeOffset(2026, 1, 15, 12, 0, 0, TimeSpan.Zero);
        var fake = new FakeTimeProvider();
        fake.SetUtcNow(expectedUtc);
        var security = Substitute.For<ISecurityEventLogger>();

        // Act: the job is constructed with the FakeTimeProvider
        new AuditIntegrityCheckJob(
            DummyFactory(),
            security,
            NullLogger<AuditIntegrityCheckJob>.Instance,
            fake);

        // Assert: the FakeTimeProvider returns the pinned value (not wall clock)
        fake.GetUtcNow().UtcDateTime.Should().Be(expectedUtc.UtcDateTime);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // AuditPartitionManagerJob
    // ─────────────────────────────────────────────────────────────────────────

    [Fact]
    public void AuditPartitionManagerJob_AcceptsFakeTimeProvider_NoException()
    {
        var fake = new FakeTimeProvider();
        fake.SetUtcNow(new DateTimeOffset(2026, 4, 18, 2, 0, 0, TimeSpan.Zero));

        var job = new AuditPartitionManagerJob(
            DummyFactory(),
            NullLogger<AuditPartitionManagerJob>.Instance,
            fake);

        Assert.NotNull(job);
    }

    [Fact]
    public void AuditPartitionManagerJob_FakeTimeProvider_ReturnsConfiguredUtcNow()
    {
        // Arrange: pin to 2026-04-01 00:00 UTC (day 1 of month — typical trigger time)
        var expectedUtc = new DateTimeOffset(2026, 4, 1, 2, 0, 0, TimeSpan.Zero);
        var fake = new FakeTimeProvider();
        fake.SetUtcNow(expectedUtc);

        // Act
        new AuditPartitionManagerJob(
            DummyFactory(),
            NullLogger<AuditPartitionManagerJob>.Instance,
            fake);

        // Assert: pinned clock is the one the job would use for partition target
        var now = fake.GetUtcNow().UtcDateTime;
        var expectedTarget = new DateTime(now.Year, now.Month, 1, 0, 0, 0, DateTimeKind.Utc).AddMonths(2);
        expectedTarget.Should().Be(new DateTime(2026, 6, 1, 0, 0, 0, DateTimeKind.Utc));
    }

    // ─────────────────────────────────────────────────────────────────────────
    // AuditRetentionEnforcerJob
    // ─────────────────────────────────────────────────────────────────────────

    [Fact]
    public void AuditRetentionEnforcerJob_AcceptsFakeTimeProvider_NoException()
    {
        var fake = new FakeTimeProvider();
        fake.SetUtcNow(new DateTimeOffset(2026, 1, 1, 3, 0, 0, TimeSpan.Zero));

        var job = new AuditRetentionEnforcerJob(
            DummyFactory(),
            NullLogger<AuditRetentionEnforcerJob>.Instance,
            fake);

        Assert.NotNull(job);
    }

    [Fact]
    public void AuditRetentionEnforcerJob_FakeTimeProvider_CutoffDatesUsePinnedClock()
    {
        // Arrange: pin to Jan 1, 2026 — retention cutoffs are 2016 and 2021
        var pinnedUtc = new DateTimeOffset(2026, 1, 1, 3, 0, 0, TimeSpan.Zero);
        var fake = new FakeTimeProvider();
        fake.SetUtcNow(pinnedUtc);

        // Act
        new AuditRetentionEnforcerJob(
            DummyFactory(),
            NullLogger<AuditRetentionEnforcerJob>.Instance,
            fake);

        // Assert: pinned clock yields deterministic cutoffs (not wall-clock-dependent)
        var now = fake.GetUtcNow().UtcDateTime;
        now.AddYears(-10).Should().Be(new DateTime(2016, 1, 1, 3, 0, 0, DateTimeKind.Utc));
        now.AddYears(-5).Should().Be(new DateTime(2021, 1, 1, 3, 0, 0, DateTimeKind.Utc));
    }
}
