using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Nexus.Api.Controllers;
using Nexus.Api.Data;
using Nexus.Api.Models;
using Xunit;

namespace Nexus.Api.Tests;

public sealed class TaskBoardV2Tests
{
    [Fact]
    public async Task GetBoardPage_ReturnsAllActiveGroups_AndPaginatesDoneByStableKeyset()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();
        var timestamp = new DateTimeOffset(2026, 7, 30, 12, 0, 0, TimeSpan.Zero);
        var parentId = Guid.Parse("00000000-0000-0000-0000-000000000100");

        await fixture.TaskRepository.AddAsync(new WorkTask
        {
            Id = parentId,
            Title = "Parent",
            State = "Backlog",
            Priority = "High",
            UpdatedAt = timestamp.AddMinutes(-10),
            CreatedAt = timestamp.AddHours(-1)
        });
        await fixture.TaskRepository.AddAsync(new WorkTask
        {
            Id = Guid.Parse("00000000-0000-0000-0000-000000000101"),
            Title = "Child",
            State = "In progress",
            Priority = "Medium",
            ParentTaskId = parentId,
            UpdatedAt = timestamp.AddMinutes(-9),
            CreatedAt = timestamp.AddMinutes(-50)
        });
        await fixture.TaskRepository.AddAsync(new WorkTask
        {
            Id = Guid.Parse("00000000-0000-0000-0000-000000000102"),
            Title = "Review",
            State = "Review",
            UpdatedAt = timestamp.AddMinutes(-8)
        });
        await fixture.TaskRepository.AddAsync(new WorkTask
        {
            Id = Guid.Parse("00000000-0000-0000-0000-000000000103"),
            Title = "Blocked",
            State = "Blocked",
            UpdatedAt = timestamp.AddMinutes(-7)
        });

        var newestDoneId = Guid.Parse("00000000-0000-0000-0000-000000000203");
        var middleDoneId = Guid.Parse("00000000-0000-0000-0000-000000000202");
        var oldestDoneId = Guid.Parse("00000000-0000-0000-0000-000000000201");
        await AddDoneAsync(fixture, oldestDoneId, "Done 1", timestamp.AddMinutes(-3));
        await AddDoneAsync(fixture, middleDoneId, "Done 2", timestamp.AddMinutes(-2));
        await AddDoneAsync(fixture, newestDoneId, "Done 3", timestamp.AddMinutes(-1));

        await fixture.ActivityRepository.AddAsync(new ActivityEvent
        {
            Type = "task",
            Message = "Parent updated",
            TaskId = parentId,
            CreatedAt = timestamp
        });

        var first = await fixture.TaskService.GetBoardPageAsync(2);

        Assert.Single(first.Offen);
        Assert.Single(first.InProgress);
        Assert.Single(first.Review);
        Assert.Single(first.Blocked);
        Assert.Equal([newestDoneId, middleDoneId], first.Done.Select(task => task.Id));
        Assert.True(first.HasMoreDone);
        Assert.NotNull(first.NextDoneCursor);
        Assert.Equal(1, first.Offen[0].ChildTaskCount);
        Assert.Equal(1, first.Offen[0].OpenChildTaskCount);
        Assert.Equal("Parent updated", first.Offen[0].LastActivityMessage);

        var second = await fixture.TaskService.GetBoardPageAsync(2, first.NextDoneCursor);

        Assert.Equal(first.Revision, second.Revision);
        Assert.Empty(second.Offen);
        Assert.Empty(second.InProgress);
        Assert.Empty(second.Review);
        Assert.Empty(second.Blocked);
        Assert.Equal([oldestDoneId], second.Done.Select(task => task.Id));
        Assert.False(second.HasMoreDone);
        Assert.Null(second.NextDoneCursor);
        Assert.DoesNotContain(second.Done, task => first.Done.Any(firstTask => firstTask.Id == task.Id));
    }

    [Fact]
    public async Task GetBoardPage_UsesIdAsTieBreaker_WhenDoneTimestampsMatch()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();
        var timestamp = new DateTimeOffset(2026, 7, 30, 12, 0, 0, TimeSpan.Zero);
        var firstId = Guid.Parse("00000000-0000-0000-0000-000000000003");
        var secondId = Guid.Parse("00000000-0000-0000-0000-000000000002");
        var thirdId = Guid.Parse("00000000-0000-0000-0000-000000000001");

        await AddDoneAsync(fixture, thirdId, "Done 1", timestamp);
        await AddDoneAsync(fixture, firstId, "Done 3", timestamp);
        await AddDoneAsync(fixture, secondId, "Done 2", timestamp);

        var first = await fixture.TaskService.GetBoardPageAsync(2);
        var second = await fixture.TaskService.GetBoardPageAsync(2, first.NextDoneCursor);

        Assert.Equal([firstId, secondId], first.Done.Select(task => task.Id));
        Assert.Equal([thirdId], second.Done.Select(task => task.Id));
    }

    [Fact]
    public async Task GetBoardPage_RejectsMalformedCursor_AsValidationProblem()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();
        var controller = CreateController(fixture);

        var result = await controller.GetBoard(
            CancellationToken.None,
            doneLimit: 50,
            doneCursor: "not-a-valid-cursor");

        var statusResult = Assert.IsAssignableFrom<IStatusCodeHttpResult>(result);
        Assert.Equal(StatusCodes.Status400BadRequest, statusResult.StatusCode);
    }

    [Theory]
    [InlineData(0)]
    [InlineData(101)]
    public async Task GetBoardPage_RejectsOutOfRangeDoneLimit(int doneLimit)
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();
        var controller = CreateController(fixture);

        var result = await controller.GetBoard(CancellationToken.None, doneLimit);

        var statusResult = Assert.IsAssignableFrom<IStatusCodeHttpResult>(result);
        Assert.Equal(StatusCodes.Status400BadRequest, statusResult.StatusCode);
    }

    [Fact]
    public void DoneKeysetPredicate_IsTranslatableByNpgsql()
    {
        var options = new DbContextOptionsBuilder<NexusDbContext>()
            .UseNpgsql("Host=unused;Database=unused;Username=unused;Password=unused")
            .Options;
        using var db = new NexusDbContext(options);
        var cursorUpdatedAt = new DateTimeOffset(2026, 7, 30, 12, 0, 0, TimeSpan.Zero);
        var cursorId = Guid.Parse("00000000-0000-0000-0000-000000000002");

        var sql = db.Tasks
            .AsNoTracking()
            .Where(task => task.State == "Done")
            .Where(task =>
                task.UpdatedAt < cursorUpdatedAt
                || (task.UpdatedAt == cursorUpdatedAt && task.Id.CompareTo(cursorId) < 0))
            .OrderByDescending(task => task.UpdatedAt)
            .ThenByDescending(task => task.Id)
            .Take(51)
            .ToQueryString();

        Assert.Contains("\"UpdatedAt\"", sql, StringComparison.Ordinal);
        Assert.Contains("\"Id\"", sql, StringComparison.Ordinal);
    }

    private static TasksController CreateController(TaskWorkflowFixture fixture)
        => new(
            fixture.TaskService,
            fixture.AgentService,
            fixture.Configuration,
            fixture.ActivityRepository)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(
                    user: TaskWorkflowFixture.CreateUser("bao", "owner"))
            }
        };

    private static async Task AddDoneAsync(
        TaskWorkflowFixture fixture,
        Guid id,
        string title,
        DateTimeOffset updatedAt)
    {
        await fixture.TaskRepository.AddAsync(new WorkTask
        {
            Id = id,
            Title = title,
            State = "Done",
            UpdatedAt = updatedAt,
            CreatedAt = updatedAt.AddHours(-1)
        });
    }
}
