using Microsoft.Extensions.Logging.Abstractions;
using MongoDB.Driver;
using StellaOps.Feedser.Core.Jobs;
using StellaOps.Feedser.Storage.Mongo;

namespace StellaOps.Feedser.Storage.Mongo.Tests;

[Collection("mongo-fixture")]
public sealed class MongoJobStoreTests : IClassFixture<MongoIntegrationFixture>
{
    private readonly MongoIntegrationFixture _fixture;

    public MongoJobStoreTests(MongoIntegrationFixture fixture)
    {
        _fixture = fixture;
    }

    [Fact]
    public async Task CreateStartCompleteLifecycle()
    {
        await ResetCollectionAsync();
        var collection = _fixture.Database.GetCollection<JobRunDocument>(MongoStorageDefaults.Collections.Jobs);
        var store = new MongoJobStore(collection, NullLogger<MongoJobStore>.Instance);

        var request = new JobRunCreateRequest(
            Kind: "mongo:test",
            Trigger: "unit",
            Parameters: new Dictionary<string, object?> { ["scope"] = "lifecycle" },
            ParametersHash: "abc",
            Timeout: TimeSpan.FromSeconds(5),
            LeaseDuration: TimeSpan.FromSeconds(2),
            CreatedAt: DateTimeOffset.UtcNow);

        var created = await store.CreateAsync(request, CancellationToken.None);
        Assert.Equal(JobRunStatus.Pending, created.Status);

        var started = await store.TryStartAsync(created.RunId, DateTimeOffset.UtcNow, CancellationToken.None);
        Assert.NotNull(started);
        Assert.Equal(JobRunStatus.Running, started!.Status);

        var completed = await store.TryCompleteAsync(created.RunId, new JobRunCompletion(JobRunStatus.Succeeded, DateTimeOffset.UtcNow, null), CancellationToken.None);
        Assert.NotNull(completed);
        Assert.Equal(JobRunStatus.Succeeded, completed!.Status);

        var recent = await store.GetRecentRunsAsync("mongo:test", 10, CancellationToken.None);
        var snapshot = Assert.Single(recent);
        Assert.Equal(JobRunStatus.Succeeded, snapshot.Status);

        var active = await store.GetActiveRunsAsync(CancellationToken.None);
        Assert.Empty(active);

        var last = await store.GetLastRunAsync("mongo:test", CancellationToken.None);
        Assert.NotNull(last);
        Assert.Equal(completed.RunId, last!.RunId);
    }

    [Fact]
    public async Task StartAndFailRunHonorsStateTransitions()
    {
        await ResetCollectionAsync();
        var collection = _fixture.Database.GetCollection<JobRunDocument>(MongoStorageDefaults.Collections.Jobs);
        var store = new MongoJobStore(collection, NullLogger<MongoJobStore>.Instance);

        var request = new JobRunCreateRequest(
            Kind: "mongo:failure",
            Trigger: "unit",
            Parameters: new Dictionary<string, object?>(),
            ParametersHash: null,
            Timeout: null,
            LeaseDuration: null,
            CreatedAt: DateTimeOffset.UtcNow);

        var created = await store.CreateAsync(request, CancellationToken.None);
        var firstStart = await store.TryStartAsync(created.RunId, DateTimeOffset.UtcNow, CancellationToken.None);
        Assert.NotNull(firstStart);

        // Second start attempt should be rejected once running.
        var secondStart = await store.TryStartAsync(created.RunId, DateTimeOffset.UtcNow.AddSeconds(1), CancellationToken.None);
        Assert.Null(secondStart);

        var failure = await store.TryCompleteAsync(
            created.RunId,
            new JobRunCompletion(JobRunStatus.Failed, DateTimeOffset.UtcNow.AddSeconds(2), "boom"),
            CancellationToken.None);

        Assert.NotNull(failure);
        Assert.Equal("boom", failure!.Error);
        Assert.Equal(JobRunStatus.Failed, failure.Status);
    }

    [Fact]
    public async Task CompletingUnknownRunReturnsNull()
    {
        await ResetCollectionAsync();
        var collection = _fixture.Database.GetCollection<JobRunDocument>(MongoStorageDefaults.Collections.Jobs);
        var store = new MongoJobStore(collection, NullLogger<MongoJobStore>.Instance);

        var result = await store.TryCompleteAsync(Guid.NewGuid(), new JobRunCompletion(JobRunStatus.Succeeded, DateTimeOffset.UtcNow, null), CancellationToken.None);

        Assert.Null(result);
    }

    private async Task ResetCollectionAsync()
    {
        try
        {
            await _fixture.Database.DropCollectionAsync(MongoStorageDefaults.Collections.Jobs);
        }
        catch (MongoCommandException ex) when (ex.CodeName == "NamespaceNotFound" || ex.Message.Contains("ns not found", StringComparison.OrdinalIgnoreCase))
        {
        }
    }
}
