using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels;
using ClaudeDo.Ui.ViewModels.Conflicts;
using ClaudeDo.Ui.ViewModels.Islands;
using ClaudeDo.Ui.ViewModels.Modals;
using Microsoft.EntityFrameworkCore;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Ui.Tests.ViewModels;

public class TasksIslandMoveToListTests : IDisposable
{
    private readonly string _dbPath;

    public TasksIslandMoveToListTests()
    {
        _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_movetolist_{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 sealed class FakeDialogService : IDialogService
    {
        public bool ConfirmResult;
        public int ConfirmCalls;
        public string? LastMessage;

        public Task<bool> ConfirmAsync(string message)
        {
            ConfirmCalls++;
            LastMessage = message;
            return Task.FromResult(ConfirmResult);
        }

        public Task ShowAboutAsync(AboutModalViewModel vm) => throw new NotImplementedException();
        public Task ShowWeeklyReportAsync(WeeklyReportModalViewModel vm) => throw new NotImplementedException();
        public Task ShowUsageMonitorAsync(UsageMonitorModalViewModel vm) => throw new NotImplementedException();
        public Task ShowSettingsAsync(SettingsModalViewModel vm) => throw new NotImplementedException();
        public Task ShowListSettingsAsync(ListSettingsModalViewModel vm) => throw new NotImplementedException();
        public Task ShowRepoImportAsync(RepoImportModalViewModel vm) => throw new NotImplementedException();
        public Task ShowWorktreesOverviewAsync(WorktreesOverviewModalViewModel vm) => throw new NotImplementedException();
        public Task<IReadOnlyList<string>?> ShowMergeHelperSelectionAsync(MergeHelperSelectionModalViewModel vm) => throw new NotImplementedException();
        public Task ShowWorkerConnectionAsync(WorkerConnectionModalViewModel vm) => throw new NotImplementedException();
        public Task ShowConflictResolverAsync(ConflictResolverViewModel vm) => throw new NotImplementedException();
        public Task ShowLogVisualizerAsync(LogVisualizerViewModel vm) => throw new NotImplementedException();
        public Task ShowErrorAsync(string message) => Task.CompletedTask;
        public void ShowMissionControl(MissionControlViewModel vm) => throw new NotImplementedException();
    }

    private TasksIslandViewModel BuildViewModel() => new(new TestDbFactory(NewContext), worker: null);

    private async Task SeedListsAsync(string? sourceDir = null, string? targetDir = null)
    {
        await using var db = NewContext();
        db.Lists.Add(new ListEntity { Id = "source", Name = "Source", CreatedAt = DateTime.UtcNow, WorkingDir = sourceDir });
        db.Lists.Add(new ListEntity { Id = "target", Name = "Target", CreatedAt = DateTime.UtcNow, WorkingDir = targetDir });
        await db.SaveChangesAsync();
    }

    private async Task SeedTaskAsync(string id, TaskStatus status, string listId = "source", string? parentTaskId = null)
    {
        await using var db = NewContext();
        db.Tasks.Add(new TaskEntity
        {
            Number = TestTaskNumbers.Next(),
            Id = id, ListId = listId, Title = id, CreatedAt = DateTime.UtcNow,
            Status = status, ParentTaskId = parentTaskId, SortOrder = 0,
        });
        await db.SaveChangesAsync();
    }

    private async Task SeedWorktreeAsync(string taskId, WorktreeState state)
    {
        await using var db = NewContext();
        db.Worktrees.Add(new WorktreeEntity
        {
            TaskId = taskId, Path = $"C:/wt/{taskId}", BranchName = $"task/{taskId}",
            BaseCommit = "abc123", State = state, CreatedAt = DateTime.UtcNow,
        });
        await db.SaveChangesAsync();
    }

    private static ListNavItemViewModel SourceList(string? workingDir = null) =>
        new() { Id = "user:source", Kind = ListKind.User, Name = "Source", WorkingDir = workingDir };

    private static ListNavItemViewModel TargetList(string? workingDir = null) =>
        new() { Id = "user:target", Kind = ListKind.User, Name = "Target", WorkingDir = workingDir };

    private static async Task LoadAndWaitAsync(TasksIslandViewModel vm, ListNavItemViewModel list)
    {
        vm.LoadForList(list);
        var deadline = DateTime.UtcNow.AddSeconds(5);
        while (DateTime.UtcNow < deadline)
        {
            await Task.Delay(25);
            if (vm.Items.Count > 0) break;
        }
        await Task.Delay(50);
    }

    private async Task<TaskEntity> GetTaskAsync(string id)
    {
        await using var db = NewContext();
        return await db.Tasks.AsNoTracking().FirstAsync(t => t.Id == id);
    }

    [Fact]
    public async Task Move_RunningTask_IsRejected_AndReportsError()
    {
        await SeedListsAsync();
        await SeedTaskAsync("t1", TaskStatus.Running);

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, SourceList());
        string? error = null;
        vm.ErrorReported += msg => error = msg;

        var row = vm.Items.First(r => r.Id == "t1");
        await vm.MoveTaskToListAsync(row, TargetList());

        Assert.NotNull(error);
        var entity = await GetTaskAsync("t1");
        Assert.Equal("source", entity.ListId);
    }

    [Fact]
    public async Task Move_TaskWithActiveWorktree_IsRejected_AndReportsError_EvenSameRepo()
    {
        var dir = "C:/repo";
        await SeedListsAsync(dir, dir);
        await SeedTaskAsync("t1", TaskStatus.Idle);
        await SeedWorktreeAsync("t1", WorktreeState.Active);

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, SourceList(dir));
        string? error = null;
        vm.ErrorReported += msg => error = msg;

        var row = vm.Items.First(r => r.Id == "t1");
        await vm.MoveTaskToListAsync(row, TargetList(dir));

        Assert.NotNull(error);
        var entity = await GetTaskAsync("t1");
        Assert.Equal("source", entity.ListId);
    }

    [Fact]
    public async Task Move_DescendantWithKeptWorktree_IsRejected()
    {
        await SeedListsAsync();
        await SeedTaskAsync("parent", TaskStatus.Idle);
        await SeedTaskAsync("child", TaskStatus.Idle, parentTaskId: "parent");
        await SeedWorktreeAsync("child", WorktreeState.Kept);

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, SourceList());
        string? error = null;
        vm.ErrorReported += msg => error = msg;

        var row = vm.Items.First(r => r.Id == "parent");
        await vm.MoveTaskToListAsync(row, TargetList());

        Assert.NotNull(error);
        var entity = await GetTaskAsync("parent");
        Assert.Equal("source", entity.ListId);
    }

    [Fact]
    public async Task Move_SameWorkingDir_MovesWithoutConfirmDialog()
    {
        var dir = "C:/repo";
        await SeedListsAsync(dir, dir);
        await SeedTaskAsync("t1", TaskStatus.Idle);

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, SourceList(dir));
        var dialogs = new FakeDialogService();
        vm.Dialogs = dialogs;

        var row = vm.Items.First(r => r.Id == "t1");
        await vm.MoveTaskToListAsync(row, TargetList(dir));

        Assert.Equal(0, dialogs.ConfirmCalls);
        var entity = await GetTaskAsync("t1");
        Assert.Equal("target", entity.ListId);
    }

    [Fact]
    public async Task Move_BothWithoutWorkingDir_MovesWithoutConfirmDialog()
    {
        await SeedListsAsync();
        await SeedTaskAsync("t1", TaskStatus.Idle);

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, SourceList());
        var dialogs = new FakeDialogService();
        vm.Dialogs = dialogs;

        var row = vm.Items.First(r => r.Id == "t1");
        await vm.MoveTaskToListAsync(row, TargetList());

        Assert.Equal(0, dialogs.ConfirmCalls);
        var entity = await GetTaskAsync("t1");
        Assert.Equal("target", entity.ListId);
    }

    [Fact]
    public async Task Move_DifferentWorkingDir_AsksConfirmation_MovesOnConfirm()
    {
        await SeedListsAsync("C:/repoA", "C:/repoB");
        await SeedTaskAsync("t1", TaskStatus.Idle);

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, SourceList("C:/repoA"));
        var dialogs = new FakeDialogService { ConfirmResult = true };
        vm.Dialogs = dialogs;

        var row = vm.Items.First(r => r.Id == "t1");
        await vm.MoveTaskToListAsync(row, TargetList("C:/repoB"));

        Assert.Equal(1, dialogs.ConfirmCalls);
        var entity = await GetTaskAsync("t1");
        Assert.Equal("target", entity.ListId);
    }

    [Fact]
    public async Task Move_DifferentWorkingDir_CancelledConfirmation_DoesNotMove()
    {
        await SeedListsAsync("C:/repoA", "C:/repoB");
        await SeedTaskAsync("t1", TaskStatus.Idle);

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, SourceList("C:/repoA"));
        var dialogs = new FakeDialogService { ConfirmResult = false };
        vm.Dialogs = dialogs;

        var row = vm.Items.First(r => r.Id == "t1");
        await vm.MoveTaskToListAsync(row, TargetList("C:/repoB"));

        Assert.Equal(1, dialogs.ConfirmCalls);
        var entity = await GetTaskAsync("t1");
        Assert.Equal("source", entity.ListId);
    }

    [Fact]
    public async Task Move_TargetIsCurrentList_IsNoOp()
    {
        await SeedListsAsync();
        await SeedTaskAsync("t1", TaskStatus.Idle);

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, SourceList());
        var dialogs = new FakeDialogService();
        vm.Dialogs = dialogs;

        var row = vm.Items.First(r => r.Id == "t1");
        await vm.MoveTaskToListAsync(row, SourceList());

        Assert.Equal(0, dialogs.ConfirmCalls);
        var entity = await GetTaskAsync("t1");
        Assert.Equal("source", entity.ListId);
    }

    [Fact]
    public async Task Move_DifferentWorkingDir_ConfirmMessageNamesBothRepos()
    {
        await SeedListsAsync("C:/repoA", "C:/repoB");
        await SeedTaskAsync("t1", TaskStatus.Idle);

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, SourceList("C:/repoA"));
        var dialogs = new FakeDialogService { ConfirmResult = false };
        vm.Dialogs = dialogs;

        var row = vm.Items.First(r => r.Id == "t1");
        await vm.MoveTaskToListAsync(row, TargetList("C:/repoB"));

        Assert.NotNull(dialogs.LastMessage);
        Assert.Contains("C:/repoA", dialogs.LastMessage);
        Assert.Contains("C:/repoB", dialogs.LastMessage);
    }

    // The source repo comes from the task's own list, not from the island's current list — those
    // differ whenever a smart/virtual list is shown, and a wrong source dir would ask (or skip
    // asking) for confirmation at the wrong times.
    [Fact]
    public async Task Move_SourceRepoIsReadFromTaskOwnList_NotCurrentList()
    {
        await SeedListsAsync("C:/repoA", "C:/repoA");
        await SeedTaskAsync("t1", TaskStatus.Idle);

        var vm = BuildViewModel();
        // Island shows a virtual list carrying no working dir of its own.
        await LoadAndWaitAsync(vm, SourceList(workingDir: null));
        var dialogs = new FakeDialogService { ConfirmResult = true };
        vm.Dialogs = dialogs;

        var row = vm.Items.First(r => r.Id == "t1");
        await vm.MoveTaskToListAsync(row, TargetList("C:/repoA"));

        // Both lists really are the same repo, so no confirmation should have been needed.
        Assert.Equal(0, dialogs.ConfirmCalls);
        var entity = await GetTaskAsync("t1");
        Assert.Equal("target", entity.ListId);
    }

    [Fact]
    public async Task Move_TargetIsSmartList_IsNoOp()
    {
        await SeedListsAsync();
        await SeedTaskAsync("t1", TaskStatus.Idle);

        var vm = BuildViewModel();
        await LoadAndWaitAsync(vm, SourceList());

        var row = vm.Items.First(r => r.Id == "t1");
        var smart = new ListNavItemViewModel { Id = "smart:my-day", Kind = ListKind.Smart, Name = "My Day" };
        await vm.MoveTaskToListAsync(row, smart);

        var entity = await GetTaskAsync("t1");
        Assert.Equal("source", entity.ListId);
    }
}
