using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using Microsoft.EntityFrameworkCore;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Data.Tests;

public sealed class MoveToListTests : IDisposable
{
    private readonly string _dbPath;
    private readonly ClaudeDoDbContext _ctx;

    public MoveToListTests()
    {
        _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_movelist_{Guid.NewGuid():N}.db");
        var options = new DbContextOptionsBuilder<ClaudeDoDbContext>()
            .UseSqlite($"Data Source={_dbPath}")
            .Options;
        _ctx = new ClaudeDoDbContext(options);
        _ctx.Database.EnsureCreated();
    }

    public void Dispose()
    {
        _ctx.Dispose();
        try { File.Delete(_dbPath); } catch { }
        try { File.Delete(_dbPath + "-wal"); } catch { }
        try { File.Delete(_dbPath + "-shm"); } catch { }
    }

    private async Task SeedListsAsync(params string[] ids)
    {
        foreach (var id in ids)
            _ctx.Lists.Add(new ListEntity { Id = id, Name = id, CreatedAt = DateTime.UtcNow });
        await _ctx.SaveChangesAsync();
    }

    [Fact]
    public async Task MoveToList_changes_ListId_and_appends_at_end_of_target()
    {
        await SeedListsAsync("source", "target");
        _ctx.Tasks.Add(new TaskEntity
        {
            Id = "t1", Number = 1, ListId = "source", Title = "Task", CreatedAt = DateTime.UtcNow, SortOrder = 0,
        });
        // Existing tasks already in the target list, so we can assert the moved task lands after them.
        _ctx.Tasks.Add(new TaskEntity
        {
            Id = "existing1", Number = 2, ListId = "target", Title = "Existing 1", CreatedAt = DateTime.UtcNow, SortOrder = 0,
        });
        _ctx.Tasks.Add(new TaskEntity
        {
            Id = "existing2", Number = 3, ListId = "target", Title = "Existing 2", CreatedAt = DateTime.UtcNow, SortOrder = 1,
        });
        await _ctx.SaveChangesAsync();
        _ctx.ChangeTracker.Clear();

        await new TaskRepository(_ctx).MoveToListAsync("t1", "target");

        var moved = await _ctx.Tasks.AsNoTracking().FirstAsync(t => t.Id == "t1");
        Assert.Equal("target", moved.ListId);
        Assert.Equal(2, moved.SortOrder);
    }

    [Fact]
    public async Task MoveToList_moves_all_descendants_recursively()
    {
        await SeedListsAsync("source", "target");
        _ctx.Tasks.Add(new TaskEntity
        {
            Id = "parent", Number = 1, ListId = "source", Title = "Parent", CreatedAt = DateTime.UtcNow, SortOrder = 0,
        });
        _ctx.Tasks.Add(new TaskEntity
        {
            Id = "child", Number = 2, ListId = "source", Title = "Child", CreatedAt = DateTime.UtcNow,
            SortOrder = 1, ParentTaskId = "parent",
        });
        _ctx.Tasks.Add(new TaskEntity
        {
            Id = "grandchild", Number = 3, ListId = "source", Title = "Grandchild", CreatedAt = DateTime.UtcNow,
            SortOrder = 2, ParentTaskId = "child",
        });
        await _ctx.SaveChangesAsync();
        _ctx.ChangeTracker.Clear();

        await new TaskRepository(_ctx).MoveToListAsync("parent", "target");

        var all = await _ctx.Tasks.AsNoTracking()
            .Where(t => t.Id == "parent" || t.Id == "child" || t.Id == "grandchild")
            .ToListAsync();

        Assert.All(all, t => Assert.Equal("target", t.ListId));
    }

    [Fact]
    public async Task MoveToList_first_task_in_empty_target_gets_SortOrder_zero()
    {
        await SeedListsAsync("source", "target");
        _ctx.Tasks.Add(new TaskEntity
        {
            Id = "t1", ListId = "source", Title = "Task", CreatedAt = DateTime.UtcNow, SortOrder = 3,
        });
        await _ctx.SaveChangesAsync();
        _ctx.ChangeTracker.Clear();

        await new TaskRepository(_ctx).MoveToListAsync("t1", "target");

        var moved = await _ctx.Tasks.AsNoTracking().FirstAsync(t => t.Id == "t1");
        Assert.Equal("target", moved.ListId);
        Assert.Equal(0, moved.SortOrder);
    }

    // A corrupt parent chain must not hang the descendant walk.
    [Fact(Timeout = 15000)]
    public async Task MoveToList_survives_a_self_parenting_task()
    {
        await SeedListsAsync("source", "target");
        _ctx.Tasks.Add(new TaskEntity
        {
            Id = "t1", ListId = "source", Title = "Task", CreatedAt = DateTime.UtcNow, SortOrder = 0,
        });
        await _ctx.SaveChangesAsync();
        // Set the cycle after insert — the FK tolerates it, the walk must too.
        await _ctx.Tasks.Where(t => t.Id == "t1")
            .ExecuteUpdateAsync(s => s.SetProperty(t => t.ParentTaskId, "t1"));
        _ctx.ChangeTracker.Clear();

        await new TaskRepository(_ctx).MoveToListAsync("t1", "target");

        var moved = await _ctx.Tasks.AsNoTracking().FirstAsync(t => t.Id == "t1");
        Assert.Equal("target", moved.ListId);
    }

    [Fact]
    public async Task MoveToList_throws_when_task_missing()
    {
        await SeedListsAsync("source", "target");

        await Assert.ThrowsAsync<InvalidOperationException>(() =>
            new TaskRepository(_ctx).MoveToListAsync("nope", "target"));
    }
}
