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

namespace ClaudeDo.Worker.Tests.Repositories;

public sealed class TaskRepositoryPlanningTests : IDisposable
{
    private readonly DbFixture _db = new();
    private readonly ClaudeDoDbContext _ctx;
    private readonly TaskRepository _tasks;
    private readonly ListRepository _lists;

    public TaskRepositoryPlanningTests()
    {
        _ctx = _db.CreateContext();
        _tasks = new TaskRepository(_ctx);
        _lists = new ListRepository(_ctx);
    }

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

    private async Task<string> CreateListAsync(string? id = null)
    {
        var listId = id ?? Guid.NewGuid().ToString();
        await _lists.AddAsync(new ListEntity
        {
            Id = listId,
            Name = "Test List",
            CreatedAt = DateTime.UtcNow,
        });
        return listId;
    }

    private TaskEntity MakeTask(
        string listId,
        TaskStatus status = TaskStatus.Idle,
        string? parentId = null,
        PlanningPhase phase = PlanningPhase.None) => new()
    {
        Id = Guid.NewGuid().ToString(),
        ListId = listId,
        Title = "t",
        Status = status,
        PlanningPhase = phase,
        CreatedAt = DateTime.UtcNow,
        CommitType = "feat",
        ParentTaskId = parentId,
    };

    [Fact]
    public async Task GetChildrenAsync_ReturnsOnlyDirectChildren_Sorted()
    {
        var listId = await CreateListAsync();
        var parent = MakeTask(listId, phase: PlanningPhase.Active);
        parent.Title = "parent";
        await _tasks.AddAsync(parent);

        var childA = MakeTask(listId, parentId: parent.Id);
        childA.Title = "a";
        await _tasks.AddAsync(childA);
        childA.SortOrder = 1;
        await _tasks.UpdateAsync(childA);

        var childB = MakeTask(listId, parentId: parent.Id);
        childB.Title = "b";
        await _tasks.AddAsync(childB);
        childB.SortOrder = 0;
        await _tasks.UpdateAsync(childB);

        var unrelated = MakeTask(listId);
        await _tasks.AddAsync(unrelated);

        var children = await _tasks.GetChildrenAsync(parent.Id);

        Assert.Equal(2, children.Count);
        Assert.Equal("b", children[0].Title);
        Assert.Equal("a", children[1].Title);
    }

    [Fact]
    public async Task CreateChildAsync_CreatesIdleChildUnderParent()
    {
        var listId = await CreateListAsync();
        var parent = MakeTask(listId, phase: PlanningPhase.Active);
        await _tasks.AddAsync(parent);

        var child = await _tasks.CreateChildAsync(
            parent.Id,
            title: "child title",
            description: "child desc",
            commitType: "feat");

        Assert.Equal(TaskStatus.Idle, child.Status);
        Assert.Equal(parent.Id, child.ParentTaskId);
        Assert.Equal(listId, child.ListId);
        Assert.Equal("child title", child.Title);
        Assert.Equal("child desc", child.Description);
        Assert.Equal("feat", child.CommitType);

        var loaded = await _tasks.GetByIdAsync(child.Id);
        Assert.NotNull(loaded);
        Assert.Equal(TaskStatus.Idle, loaded!.Status);
    }

    [Fact]
    public async Task CreateChildAsync_ThrowsIfParentNotFound()
    {
        var listId = await CreateListAsync();
        _ = listId;

        await Assert.ThrowsAsync<InvalidOperationException>(() =>
            _tasks.CreateChildAsync("nonexistent-parent-id", "t", null, null));
    }

    [Fact]
    public async Task CreateChildAsync_NoMaxTurns_ChildInherits()
    {
        var listId = await CreateListAsync();
        var parent = MakeTask(listId, phase: PlanningPhase.Active);
        await _tasks.AddAsync(parent);

        var child = await _tasks.CreateChildAsync(parent.Id, "child", null, null);

        Assert.Null(child.MaxTurns);
        var loaded = await _tasks.GetByIdAsync(child.Id);
        Assert.Null(loaded!.MaxTurns);
    }

    [Fact]
    public async Task CreateChildAsync_WithMaxTurns_Persists()
    {
        var listId = await CreateListAsync();
        var parent = MakeTask(listId, phase: PlanningPhase.Active);
        await _tasks.AddAsync(parent);

        var child = await _tasks.CreateChildAsync(parent.Id, "child", null, null, maxTurns: 200);

        Assert.Equal(200, child.MaxTurns);
        var loaded = await _tasks.GetByIdAsync(child.Id);
        Assert.Equal(200, loaded!.MaxTurns);
    }

    [Theory]
    [InlineData(0)]
    [InlineData(-5)]
    public async Task CreateChildAsync_InvalidMaxTurns_Throws(int maxTurns)
    {
        var listId = await CreateListAsync();
        var parent = MakeTask(listId, phase: PlanningPhase.Active);
        await _tasks.AddAsync(parent);

        await Assert.ThrowsAsync<ArgumentException>(() =>
            _tasks.CreateChildAsync(parent.Id, "child", null, null, maxTurns: maxTurns));
    }

    [Fact]
    public async Task SetPlanningStartedAsync_IdleTask_TransitionsToActivePhase()
    {
        var listId = await CreateListAsync();
        var task = MakeTask(listId);
        await _tasks.AddAsync(task);

        var result = await _tasks.SetPlanningStartedAsync(task.Id, "tok-abc");

        Assert.NotNull(result);
        Assert.Equal(TaskStatus.Idle, result!.Status);
        Assert.Equal(PlanningPhase.Active, result.PlanningPhase);
        Assert.Equal("tok-abc", result.PlanningSessionToken);

        var loaded = await _tasks.GetByIdAsync(task.Id);
        Assert.Equal(PlanningPhase.Active, loaded!.PlanningPhase);
        Assert.Equal("tok-abc", loaded.PlanningSessionToken);
    }

    [Fact]
    public async Task SetPlanningStartedAsync_NonIdleTask_ReturnsNull()
    {
        var listId = await CreateListAsync();
        var task = MakeTask(listId, TaskStatus.Queued);
        await _tasks.AddAsync(task);

        var result = await _tasks.SetPlanningStartedAsync(task.Id, "tok-xyz");

        Assert.Null(result);
        var loaded = await _tasks.GetByIdAsync(task.Id);
        Assert.Equal(TaskStatus.Queued, loaded!.Status);
        Assert.Null(loaded.PlanningSessionToken);
    }

    [Fact]
    public async Task UpdatePlanningSessionIdAsync_StoresClaudeSessionId()
    {
        var listId = await CreateListAsync();
        var task = MakeTask(listId);
        await _tasks.AddAsync(task);
        await _tasks.SetPlanningStartedAsync(task.Id, "tok");

        await _tasks.UpdatePlanningSessionIdAsync(task.Id, "claude-session-42");

        var loaded = await _tasks.GetByIdAsync(task.Id);
        Assert.Equal("claude-session-42", loaded!.PlanningSessionId);
    }

    [Fact]
    public async Task FindByPlanningTokenAsync_ReturnsTask_WhenTokenMatches()
    {
        var listId = await CreateListAsync();
        var task = MakeTask(listId);
        await _tasks.AddAsync(task);
        await _tasks.SetPlanningStartedAsync(task.Id, "unique-token-123");

        var found = await _tasks.FindByPlanningTokenAsync("unique-token-123");

        Assert.NotNull(found);
        Assert.Equal(task.Id, found!.Id);
    }

    [Fact]
    public async Task FindByPlanningTokenAsync_ReturnsNull_WhenTokenUnknown()
    {
        var found = await _tasks.FindByPlanningTokenAsync("no-such-token");
        Assert.Null(found);
    }

    [Fact]
    public async Task DiscardPlanningAsync_DeletesDraftsAndResetsParent()
    {
        var listId = await CreateListAsync();
        var parent = MakeTask(listId);
        await _tasks.AddAsync(parent);
        await _tasks.SetPlanningStartedAsync(parent.Id, "tok");
        await _tasks.UpdatePlanningSessionIdAsync(parent.Id, "claude-42");
        var c1 = await _tasks.CreateChildAsync(parent.Id, "c1", null, null);
        var c2 = await _tasks.CreateChildAsync(parent.Id, "c2", null, null);

        var outcome = await _tasks.DiscardPlanningAsync(parent.Id, dequeueQueuedChildren: false);

        Assert.Equal(DiscardPlanningResult.Discarded, outcome.Result);
        Assert.Null(await _tasks.GetByIdAsync(c1.Id));
        Assert.Null(await _tasks.GetByIdAsync(c2.Id));

        var parentLoaded = await _tasks.GetByIdAsync(parent.Id);
        Assert.Equal(TaskStatus.Idle, parentLoaded!.Status);
        Assert.Equal(PlanningPhase.None, parentLoaded.PlanningPhase);
        Assert.Null(parentLoaded.PlanningSessionId);
        Assert.Null(parentLoaded.PlanningSessionToken);
        Assert.Null(parentLoaded.PlanningFinalizedAt);
    }

    [Fact]
    public async Task DiscardPlanningAsync_OnNonPlanningTask_ReturnsFalse()
    {
        var listId = await CreateListAsync();
        var task = MakeTask(listId);
        await _tasks.AddAsync(task);

        var outcome = await _tasks.DiscardPlanningAsync(task.Id, dequeueQueuedChildren: false);

        Assert.Equal(DiscardPlanningResult.NotInPlanning, outcome.Result);
    }

    [Fact]
    public async Task DeleteAsync_ParentWithChildren_ThrowsOrDoesNotDelete()
    {
        var listId = await CreateListAsync();
        var parent = MakeTask(listId, phase: PlanningPhase.Active);
        await _tasks.AddAsync(parent);
        await _tasks.CreateChildAsync(parent.Id, "c", null, null);

        await Assert.ThrowsAsync<Microsoft.Data.Sqlite.SqliteException>(async () =>
        {
            await _tasks.DeleteAsync(parent.Id);
        });

        var stillThere = await _tasks.GetByIdAsync(parent.Id);
        Assert.NotNull(stillThere);
    }

}
