using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.State;
using ClaudeDo.Worker.Tests.Infrastructure;
using Microsoft.EntityFrameworkCore;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Worker.Tests.State;

public sealed class ReviewTransitionTests : IDisposable
{
    private readonly DbFixture _db = new();
    private readonly TestDbContextFactory _factory;
    private readonly TaskStateServiceBuilder.Built _built;
    private readonly ITaskStateService _sut;
    private readonly string _listId;

    public ReviewTransitionTests()
    {
        _factory = _db.CreateFactory();
        _built = TaskStateServiceBuilder.Build(_factory);
        _sut = _built.State;

        _listId = Guid.NewGuid().ToString();
        using var ctx = _factory.CreateDbContext();
        ctx.Lists.Add(new ListEntity
        {
            Id = _listId,
            Name = "Test",
            CreatedAt = DateTime.UtcNow,
            DefaultCommitType = "chore",
        });
        ctx.SaveChanges();
    }

    public void Dispose() => _db.Dispose();

    private async Task<string> SeedTaskAsync(TaskStatus status, string? result = null)
    {
        var id = Guid.NewGuid().ToString();
        await using var ctx = _factory.CreateDbContext();
        ctx.Tasks.Add(new TaskEntity
        {
            Id = id,
            ListId = _listId,
            Title = "task",
            Status = status,
            Result = result,
            CreatedAt = DateTime.UtcNow,
        });
        await ctx.SaveChangesAsync();
        return id;
    }

    private async Task<TaskEntity> GetTaskAsync(string id)
    {
        await using var ctx = _factory.CreateDbContext();
        return await new TaskRepository(ctx).GetByIdAsync(id)
            ?? throw new InvalidOperationException($"task {id} not found");
    }

    // ─── SubmitForReviewAsync ─────────────────────────────────────────────

    [Fact]
    public async Task SubmitForReviewAsync_FromRunning_TransitionsToWaitingForReview()
    {
        var id = await SeedTaskAsync(TaskStatus.Running);

        var result = await _sut.SubmitForReviewAsync(id, DateTime.UtcNow, "the result", default);

        Assert.True(result.Ok);
        var t = await GetTaskAsync(id);
        Assert.Equal(TaskStatus.WaitingForReview, t.Status);
        Assert.Equal("the result", t.Result);
        Assert.NotNull(t.FinishedAt);
    }

    [Fact]
    public async Task SubmitForReviewAsync_FromQueued_Rejects()
    {
        var id = await SeedTaskAsync(TaskStatus.Queued);

        var result = await _sut.SubmitForReviewAsync(id, DateTime.UtcNow, "x", default);

        Assert.False(result.Ok);
        Assert.Equal(TaskStatus.Queued, (await GetTaskAsync(id)).Status);
    }

    // ─── SubmitInteractiveForReviewAsync ──────────────────────────────────

    [Theory]
    [InlineData(TaskStatus.Idle)]
    [InlineData(TaskStatus.Failed)]
    public async Task SubmitInteractiveForReviewAsync_FromIdleOrFailed_TransitionsToWaitingForReview(TaskStatus from)
    {
        var id = await SeedTaskAsync(from);

        var result = await _sut.SubmitInteractiveForReviewAsync(id, DateTime.UtcNow, default);

        Assert.True(result.Ok);
        var t = await GetTaskAsync(id);
        Assert.Equal(TaskStatus.WaitingForReview, t.Status);
        Assert.NotNull(t.FinishedAt);
    }

    [Theory]
    [InlineData(TaskStatus.Running)]
    [InlineData(TaskStatus.Queued)]
    [InlineData(TaskStatus.Done)]
    [InlineData(TaskStatus.WaitingForReview)]
    public async Task SubmitInteractiveForReviewAsync_FromOtherStates_Rejects(TaskStatus from)
    {
        var id = await SeedTaskAsync(from);

        var result = await _sut.SubmitInteractiveForReviewAsync(id, DateTime.UtcNow, default);

        Assert.False(result.Ok);
        Assert.Equal(from, (await GetTaskAsync(id)).Status);
    }

    // ─── ApproveReviewAsync ───────────────────────────────────────────────

    [Fact]
    public async Task ApproveReviewAsync_FromWaitingForReview_TransitionsToDone()
    {
        var id = await SeedTaskAsync(TaskStatus.WaitingForReview);

        var result = await _sut.ApproveReviewAsync(id, default);

        Assert.True(result.Ok);
        Assert.Equal(TaskStatus.Done, (await GetTaskAsync(id)).Status);
    }

    [Fact]
    public async Task ApproveReviewAsync_FromIdle_Rejects()
    {
        var id = await SeedTaskAsync(TaskStatus.Idle);

        var result = await _sut.ApproveReviewAsync(id, default);

        Assert.False(result.Ok);
        Assert.Equal(TaskStatus.Idle, (await GetTaskAsync(id)).Status);
    }

    // ─── RejectToQueueAsync ───────────────────────────────────────────────

    [Fact]
    public async Task RejectToQueueAsync_StoresFeedback_AndQueues_AndWakes()
    {
        var id = await SeedTaskAsync(TaskStatus.WaitingForReview);
        var wakesBefore = _built.WakeCount();

        var result = await _sut.RejectToQueueAsync(id, "please fix the bug", default);

        Assert.True(result.Ok);
        var t = await GetTaskAsync(id);
        Assert.Equal(TaskStatus.Queued, t.Status);
        Assert.Equal("please fix the bug", t.ReviewFeedback);
        Assert.True(_built.WakeCount() > wakesBefore);
    }

    [Fact]
    public async Task RejectToQueueAsync_EmptyFeedback_Rejects()
    {
        var id = await SeedTaskAsync(TaskStatus.WaitingForReview);

        var result = await _sut.RejectToQueueAsync(id, "   ", default);

        Assert.False(result.Ok);
        Assert.Equal(TaskStatus.WaitingForReview, (await GetTaskAsync(id)).Status);
    }

    [Fact]
    public async Task RejectToQueueAsync_FromIdle_Rejects()
    {
        var id = await SeedTaskAsync(TaskStatus.Idle);

        var result = await _sut.RejectToQueueAsync(id, "feedback", default);

        Assert.False(result.Ok);
    }

    // ─── RejectToIdleAsync ────────────────────────────────────────────────

    [Fact]
    public async Task RejectToIdleAsync_Parks_KeepsResult_ClearsFeedback()
    {
        var id = await SeedTaskAsync(TaskStatus.WaitingForReview, result: "run output");

        var result = await _sut.RejectToIdleAsync(id, default);

        Assert.True(result.Ok);
        var t = await GetTaskAsync(id);
        Assert.Equal(TaskStatus.Idle, t.Status);
        Assert.Equal("run output", t.Result);
        Assert.Null(t.ReviewFeedback);
    }

    [Fact]
    public async Task RejectToIdleAsync_FromRunning_Rejects()
    {
        var id = await SeedTaskAsync(TaskStatus.Running);

        var result = await _sut.RejectToIdleAsync(id, default);

        Assert.False(result.Ok);
    }

    // ─── ClearReviewFeedbackAsync ─────────────────────────────────────────

    [Fact]
    public async Task ClearReviewFeedbackAsync_RemovesFeedback_WithoutChangingStatus()
    {
        var id = await SeedTaskAsync(TaskStatus.WaitingForReview);
        await _sut.RejectToQueueAsync(id, "feedback", default); // sets feedback + Queued

        var result = await _sut.ClearReviewFeedbackAsync(id, default);

        Assert.True(result.Ok);
        var t = await GetTaskAsync(id);
        Assert.Null(t.ReviewFeedback);
        Assert.Equal(TaskStatus.Queued, t.Status);
    }

    // ─── CancelAsync from WaitingForReview ────────────────────────────────

    [Fact]
    public async Task CancelAsync_FromWaitingForReview_TransitionsToCancelled()
    {
        var id = await SeedTaskAsync(TaskStatus.WaitingForReview);

        var result = await _sut.CancelAsync(id, DateTime.UtcNow, default);

        Assert.True(result.Ok);
        Assert.Equal(TaskStatus.Cancelled, (await GetTaskAsync(id)).Status);
    }
}
