using System.Collections.Specialized;
using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Ui.ViewModels.Islands;
using Microsoft.EntityFrameworkCore;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Ui.Tests.ViewModels;

public class TasksIslandRegroupTests : IDisposable
{
    private readonly string _dbPath;

    public TasksIslandRegroupTests()
    {
        _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_ui_test_{Guid.NewGuid():N}.db");
        using var ctx = NewContext();
        ctx.Database.EnsureCreated();
    }

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

    private ClaudeDoDbContext NewContext()
    {
        var opts = new DbContextOptionsBuilder<ClaudeDoDbContext>()
            .UseSqlite($"Data Source={_dbPath}")
            .Options;
        return new ClaudeDoDbContext(opts);
    }


    private TasksIslandViewModel BuildViewModel()
    {
        var factory = new TestDbFactory(NewContext);
        return new TasksIslandViewModel(factory, worker: null);
    }

    private async Task SeedPlanningWithChildAsync(
        TaskStatus parentStatus,
        TaskStatus childStatus,
        string parentId = "p1",
        string childId = "c1",
        PlanningPhase parentPhase = PlanningPhase.None)
    {
        await using var db = NewContext();
        var list = new ListEntity
        {
            Id = "list1",
            Name = "Default",
            CreatedAt = DateTime.UtcNow,
        };
        db.Lists.Add(list);

        db.Tasks.Add(new TaskEntity
        {
            Number = TestTaskNumbers.Next(),
            Id = parentId,
            ListId = list.Id,
            Title = "Parent",
            CreatedAt = DateTime.UtcNow,
            Status = parentStatus,
            PlanningPhase = parentPhase,
            SortOrder = 0,
        });
        db.Tasks.Add(new TaskEntity
        {
            Number = TestTaskNumbers.Next(),
            Id = childId,
            ListId = list.Id,
            Title = "Child",
            CreatedAt = DateTime.UtcNow,
            Status = childStatus,
            ParentTaskId = parentId,
            SortOrder = 1,
        });
        await db.SaveChangesAsync();
    }

    // Rows is the flat HeaderRow|TaskRowViewModel union — a row's section is read off the
    // nearest preceding HeaderRow, mirroring TasksIslandViewModel.SectionFor. HasAction is
    // unique to the Completed header (its Clear-completed button), so it disambiguates
    // Completed from the (also non-overdue) Open header without depending on locale text.
    private enum Section { None, Overdue, Open, Completed }

    private static Section SectionOf(TasksIslandViewModel vm, TaskRowViewModel row)
    {
        var idx = vm.Rows.IndexOf(row);
        if (idx < 0) return Section.None;
        for (int i = idx - 1; i >= 0; i--)
            if (vm.Rows[i] is HeaderRow h)
                return h.IsOverdue ? Section.Overdue : h.HasAction ? Section.Completed : Section.Open;
        return Section.Open;
    }

    private static bool IsInCompleted(TasksIslandViewModel vm, string taskId) =>
        vm.Rows.OfType<TaskRowViewModel>().Any(r => r.Id == taskId && SectionOf(vm, r) == Section.Completed);

    private static bool IsInOpen(TasksIslandViewModel vm, string taskId, bool requireChild = false) =>
        vm.Rows.OfType<TaskRowViewModel>().Any(r =>
            r.Id == taskId && (!requireChild || r.IsChild) && SectionOf(vm, r) == Section.Open);

    private async Task SeedTasksAsync(params (string Id, TaskStatus Status, DateTime? ScheduledFor, int SortOrder)[] tasks)
    {
        await using var db = NewContext();
        db.Lists.Add(new ListEntity { Id = "list1", Name = "Default", CreatedAt = DateTime.UtcNow });
        foreach (var t in tasks)
        {
            db.Tasks.Add(new TaskEntity
            {
                Number = TestTaskNumbers.Next(),
                Id = t.Id,
                ListId = "list1",
                Title = t.Id,
                CreatedAt = DateTime.UtcNow,
                Status = t.Status,
                ScheduledFor = t.ScheduledFor,
                SortOrder = t.SortOrder,
            });
        }
        await db.SaveChangesAsync();
    }

    private static ListNavItemViewModel VirtualList(string id, string name) =>
        new() { Id = id, Kind = ListKind.Virtual, Name = name };

    private static ListNavItemViewModel UserList(string listEntityId, string name) =>
        new() { Id = $"user:{listEntityId}", Kind = ListKind.User, Name = name };

    private static async Task LoadAndWaitAsync(TasksIslandViewModel vm, ListNavItemViewModel list)
    {
        vm.LoadForList(list);

        // LoadForList fires a background Task; wait briefly until Items are populated
        // or until a timeout occurs (some tests may legitimately expect 0 items, so
        // we just wait a short deterministic period).
        var deadline = DateTime.UtcNow.AddSeconds(5);
        while (DateTime.UtcNow < deadline)
        {
            await Task.Delay(25);
            // Break out as soon as any Items present, or the background task has settled.
            if (vm.Items.Count > 0) break;
        }
        // One more tick for Regroup after load
        await Task.Delay(50);
    }

    [Fact]
    public async Task VirtualQueued_QueuedChildOfPlanningParent_IsNotStandaloneRow()
    {
        await SeedPlanningWithChildAsync(
            parentStatus: TaskStatus.Idle, parentPhase: PlanningPhase.Active,
            childStatus: TaskStatus.Queued,
            parentId: "p1",
            childId: "c1");

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, VirtualList("virtual:queued", "Queued"));

        Assert.DoesNotContain(vm.Items, r => r.Id == "c1" && !r.IsChild);
        Assert.Contains(vm.Items, r => r.Id == "p1" && !r.IsChild);
    }

    [Fact]
    public async Task VirtualQueued_PlannedParentWithQueuedChild_ParentIsStandaloneRow_ChildIsNot()
    {
        await SeedPlanningWithChildAsync(
            parentStatus: TaskStatus.Idle, parentPhase: PlanningPhase.Finalized,
            childStatus: TaskStatus.Queued,
            parentId: "p1",
            childId: "c1");

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, VirtualList("virtual:queued", "Queued"));

        Assert.Contains(vm.Items, r => r.Id == "p1" && !r.IsChild);
        Assert.DoesNotContain(vm.Items, r => r.Id == "c1" && !r.IsChild);
    }

    [Fact]
    public async Task VirtualRunning_RunningChildOfPlanningParent_IsNotStandaloneRow()
    {
        await SeedPlanningWithChildAsync(
            parentStatus: TaskStatus.Idle, parentPhase: PlanningPhase.Active,
            childStatus: TaskStatus.Running,
            parentId: "p1",
            childId: "c1");

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, VirtualList("virtual:running", "Running"));

        Assert.DoesNotContain(vm.Items, r => r.Id == "c1" && !r.IsChild);
        Assert.Contains(vm.Items, r => r.Id == "p1" && !r.IsChild);
    }

    [Fact]
    public async Task Done_ChildOfOpenPlanningParent_StaysNestedUnderParent()
    {
        await SeedPlanningWithChildAsync(
            parentStatus: TaskStatus.Idle, parentPhase: PlanningPhase.Active,
            childStatus: TaskStatus.Done,
            parentId: "p1",
            childId: "c1");

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, UserList("list1", "Default"));

        // Parents with children collapse by default; expand to surface the nested child.
        vm.ToggleExpandCommand.Execute(vm.Items.First(r => r.Id == "p1"));

        // Child with Done status under an open Planning parent should NOT land in the Completed section
        Assert.False(IsInCompleted(vm, "c1"));
        // Child should appear nested (IsChild == true) in the Open section
        Assert.True(IsInOpen(vm, "c1", requireChild: true));
    }

    [Fact]
    public async Task Done_ChildOfDonePlanningParent_MovesToCompleted()
    {
        await SeedPlanningWithChildAsync(
            parentStatus: TaskStatus.Done,
            childStatus: TaskStatus.Done,
            parentId: "p1",
            childId: "c1");

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, UserList("list1", "Default"));

        Assert.True(IsInCompleted(vm, "p1"));
        Assert.True(IsInCompleted(vm, "c1"));
    }

    [Fact]
    public async Task Regroup_Rows_HeaderPositionsAndCountsMatchSections()
    {
        var yesterday = DateTime.Today.AddDays(-1);
        await SeedTasksAsync(
            ("overdue1", TaskStatus.Idle, yesterday, 0),
            ("open1", TaskStatus.Idle, null, 1),
            ("completed1", TaskStatus.Done, null, 2));

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, UserList("list1", "Default"));

        Assert.Equal(6, vm.Rows.Count);

        var overdueHeader = Assert.IsType<HeaderRow>(vm.Rows[0]);
        Assert.True(overdueHeader.IsOverdue);
        Assert.Equal(1, overdueHeader.Count);
        Assert.Equal("overdue1", Assert.IsType<TaskRowViewModel>(vm.Rows[1]).Id);

        // ShowOpenLabel is true here because Overdue is also present — the Open header IS emitted.
        var openHeader = Assert.IsType<HeaderRow>(vm.Rows[2]);
        Assert.False(openHeader.IsOverdue);
        Assert.False(openHeader.HasAction);
        Assert.Equal(1, openHeader.Count);
        Assert.Equal("open1", Assert.IsType<TaskRowViewModel>(vm.Rows[3]).Id);

        var completedHeader = Assert.IsType<HeaderRow>(vm.Rows[4]);
        Assert.True(completedHeader.HasAction);
        Assert.Equal(1, completedHeader.Count);
        Assert.Equal("completed1", Assert.IsType<TaskRowViewModel>(vm.Rows[5]).Id);
    }

    [Fact]
    public async Task Regroup_EmptySections_EmitNoHeader()
    {
        // Open-only: no Overdue means ShowOpenLabel is false too, so the Open header is never
        // emitted either — Rows should hold exactly the one task row, nothing else.
        await SeedTasksAsync(("open1", TaskStatus.Idle, null, 0));

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, UserList("list1", "Default"));

        Assert.False(vm.HasOverdue);
        Assert.False(vm.HasCompleted);
        Assert.False(vm.ShowOpenLabel);
        var row = Assert.Single(vm.Rows);
        Assert.Equal("open1", Assert.IsType<TaskRowViewModel>(row).Id);
    }

    [Fact]
    public async Task Regroup_CompletedSection_HiddenWhileIsShowingCompletedFalse()
    {
        await SeedTasksAsync(
            ("open1", TaskStatus.Idle, null, 0),
            ("completed1", TaskStatus.Done, null, 1));

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, UserList("list1", "Default"));
        Assert.True(IsInCompleted(vm, "completed1"));

        vm.IsShowingCompleted = false;

        Assert.DoesNotContain(vm.Rows.OfType<HeaderRow>(), h => h.HasAction);
        Assert.DoesNotContain(vm.Rows.OfType<TaskRowViewModel>(), r => r.Id == "completed1");
        // HasCompleted reflects the underlying data, independent of the toggle.
        Assert.True(vm.HasCompleted);

        vm.IsShowingCompleted = true;
        Assert.True(IsInCompleted(vm, "completed1"));
    }

    [Fact]
    public async Task Rows_ParentExpandCollapse_ReconcilesGranularly_NoResetEvent()
    {
        await SeedPlanningWithChildAsync(
            parentStatus: TaskStatus.Idle, parentPhase: PlanningPhase.Active,
            childStatus: TaskStatus.Idle,
            parentId: "p1",
            childId: "c1");

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, UserList("list1", "Default"));

        var actions = new List<NotifyCollectionChangedAction>();
        vm.Rows.CollectionChanged += (_, e) => actions.Add(e.Action);

        vm.ToggleExpandCommand.Execute(vm.Items.First(r => r.Id == "p1"));
        vm.ToggleExpandCommand.Execute(vm.Items.First(r => r.Id == "p1"));

        Assert.NotEmpty(actions);
        Assert.DoesNotContain(NotifyCollectionChangedAction.Reset, actions);
    }

    [Fact]
    public async Task ReorderAsync_WithinOpenSection_MovesRowInItemsAndRows()
    {
        await SeedTasksAsync(
            ("t1", TaskStatus.Idle, null, 0),
            ("t2", TaskStatus.Idle, null, 1),
            ("t3", TaskStatus.Idle, null, 2));

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, UserList("list1", "Default"));

        var t1 = vm.Items.First(r => r.Id == "t1");
        var t3 = vm.Items.First(r => r.Id == "t3");

        await vm.ReorderAsync(t1, t3, placeBelow: true);

        Assert.Equal(new[] { "t2", "t3", "t1" }, vm.Items.Select(r => r.Id));
        Assert.Equal(new[] { "t2", "t3", "t1" }, vm.Rows.OfType<TaskRowViewModel>().Select(r => r.Id));
    }

    [Fact]
    public async Task SetScheduledForAsync_LocalDateTime_PersistsAsUtc()
    {
        await SeedTasksAsync(("t1", TaskStatus.Idle, null, 0));

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, UserList("list1", "Default"));
        var row = vm.Items.First(r => r.Id == "t1");

        // ThemedDatePicker hands SetScheduledForAsync Local/Unspecified wall-clock values.
        var localWhen = DateTime.SpecifyKind(new DateTime(2026, 8, 26, 15, 0, 0), DateTimeKind.Local);

        await vm.SetScheduledForAsync(row, localWhen);

        await using var db = NewContext();
        var entity = await db.Tasks.FirstAsync(t => t.Id == "t1");
        Assert.Equal(DateTimeKind.Utc, entity.ScheduledFor!.Value.Kind);
        Assert.Equal(localWhen.ToUniversalTime(), entity.ScheduledFor!.Value);
    }
}
