using System.Linq;
using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels;
using ClaudeDo.Ui.ViewModels.Islands;
using ClaudeDo.Ui.ViewModels.MissionControl;
using Microsoft.EntityFrameworkCore;
using Xunit;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Ui.Tests.ViewModels;

public class MissionControlViewModelTests : IDisposable
{
    private readonly string _dbPath;

    public MissionControlViewModelTests()
    {
        _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_mc_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 sealed class TestDbFactory : IDbContextFactory<ClaudeDoDbContext>
    {
        private readonly Func<ClaudeDoDbContext> _create;
        public TestDbFactory(Func<ClaudeDoDbContext> create) => _create = create;
        public ClaudeDoDbContext CreateDbContext() => _create();
    }

    private sealed class FakeWorker : StubWorkerClient { }

    private MissionControlViewModel BuildVm(StubWorkerClient worker)
        => new MissionControlViewModel(new TestDbFactory(NewContext), worker);

    [Fact]
    public void TwoStarts_CreateTwoMonitors_ColumnCountTwo()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);

        worker.RaiseTaskStarted("slot-1", "t1", DateTime.UtcNow);
        worker.RaiseTaskStarted("slot-2", "t2", DateTime.UtcNow);

        Assert.Equal(2, vm.Monitors.Count);
        Assert.Equal(2, vm.ColumnCount);
    }

    [Fact]
    public void DuplicateStart_DoesNotAddSecondMonitor()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);

        worker.RaiseTaskStarted("slot-1", "t1", DateTime.UtcNow);
        worker.RaiseTaskStarted("slot-1", "t1", DateTime.UtcNow);

        Assert.Equal(1, vm.Monitors.Count);
    }

    [Fact]
    public void Finish_KeepsPane_AndFlipsState()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);

        worker.RaiseTaskStarted("slot-1", "t1", DateTime.UtcNow);
        worker.RaiseTaskFinished("slot-1", "t1", "done", DateTime.UtcNow);

        Assert.Equal(1, vm.Monitors.Count);
        Assert.True(vm.Monitors[0].IsDone);
    }

    [Fact]
    public void ClearFinished_RemovesTerminalMonitors()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);

        worker.RaiseTaskStarted("slot-1", "t1", DateTime.UtcNow);
        worker.RaiseTaskStarted("slot-2", "t2", DateTime.UtcNow);
        worker.RaiseTaskFinished("slot-1", "t1", "done", DateTime.UtcNow);

        vm.ClearFinishedCommand.Execute(null);

        Assert.Equal(1, vm.Monitors.Count);
        Assert.Equal("t2", vm.Monitors[0].SubscribedTaskId);
        Assert.Equal(1, vm.ColumnCount);
    }

    [Fact]
    public void SeedsFromActiveTasksOnConstruction()
    {
        var worker = new SeededFakeWorker();
        using var vm = BuildVm(worker);

        Assert.Equal(1, vm.Monitors.Count);
        Assert.Equal("seed1", vm.Monitors[0].SubscribedTaskId);
    }

    private sealed class SeededFakeWorker : StubWorkerClient
    {
        public override IReadOnlyList<ActiveTask> GetActiveTasks()
            => new[] { new ActiveTask("slot-1", "seed1", DateTime.UtcNow) };
    }

    [Fact]
    public void OpenInApp_PropagatesToMonitors_AndCommandInvokesHook()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);

        string? revealed = null;
        vm.OpenInApp = id => revealed = id;

        worker.RaiseTaskStarted("slot-1", "t1", DateTime.UtcNow);

        vm.Monitors[0].OpenInAppCommand.Execute(null);

        Assert.Equal("t1", revealed);
    }

    [Fact]
    public void Detach_RemovesFromGrid_ThenReDockRestores()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);
        TaskMonitorViewModel? detached = null;
        Action? reDock = null;
        vm.ShowDetached = (m, rd) => { detached = m; reDock = rd; };

        worker.RaiseTaskStarted("slot-1", "t1", DateTime.UtcNow);
        var monitor = vm.Monitors[0];

        monitor.DetachCommand.Execute(null);
        Assert.Empty(vm.Monitors);
        Assert.Same(monitor, detached);

        reDock!.Invoke();
        Assert.Single(vm.Monitors);
        Assert.Same(monitor, vm.Monitors[0]);
    }

    [Fact]
    public void ClearFinished_AlsoRemoves_WaitingForReview()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);
        worker.RaiseTaskStarted("slot-1", "t1", DateTime.UtcNow);
        worker.RaiseTaskFinished("slot-1", "t1", "waiting_for_review", DateTime.UtcNow);

        Assert.True(vm.Monitors[0].IsWaitingForReview);
        vm.ClearFinishedCommand.Execute(null);
        Assert.Empty(vm.Monitors);
    }

    [Fact]
    public void Detach_SetsIsDetached_AndReDockClearsIt()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);
        Action? reDock = null;
        vm.ShowDetached = (m, rd) => reDock = rd;
        worker.RaiseTaskStarted("slot-1", "t1", DateTime.UtcNow);
        var monitor = vm.Monitors[0];

        monitor.DetachCommand.Execute(null);
        Assert.True(monitor.IsDetached);
        Assert.Empty(vm.Monitors);

        reDock!.Invoke();
        Assert.False(monitor.IsDetached);
        Assert.Single(vm.Monitors);
    }

    [Fact]
    public void DetachCommand_WhenDetached_RequestsWindowClose()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);
        vm.ShowDetached = (m, rd) => { };
        worker.RaiseTaskStarted("slot-1", "t1", DateTime.UtcNow);
        var monitor = vm.Monitors[0];

        var closeRequested = false;
        monitor.DetachCommand.Execute(null);              // detach (IsDetached = true)
        monitor.CloseWindowRequested = () => closeRequested = true;
        monitor.DetachCommand.Execute(null);              // now acts as re-dock
        Assert.True(closeRequested);
    }

    [Fact]
    public void MoveMonitor_ReordersCollection()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);
        worker.RaiseTaskStarted("s1", "t1", DateTime.UtcNow);
        worker.RaiseTaskStarted("s2", "t2", DateTime.UtcNow);
        worker.RaiseTaskStarted("s3", "t3", DateTime.UtcNow);

        vm.MoveMonitor(vm.Monitors[0], vm.Monitors[2]);   // move t1 to t3's slot
        Assert.Equal(new[] { "t2", "t3", "t1" }, vm.Monitors.Select(m => m.SubscribedTaskId).ToArray());
    }

    [Fact]
    public async Task Queue_ReflectsQueuedTasks_InSortOrder()
    {
        await SeedQueueAsync();
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);

        await vm.RefreshQueueAsync();

        Assert.True(vm.HasQueued);
        Assert.Equal(new[] { "first", "second" }, vm.Queued.Select(q => q.Title).ToArray());
    }

    private async Task SeedQueueAsync()
    {
        await using var db = NewContext();
        db.Lists.Add(new ListEntity { Id = "L1", Name = "Work", CreatedAt = DateTime.UtcNow });
        db.Tasks.Add(new TaskEntity { Id = "q2", ListId = "L1", Title = "second", Status = TaskStatus.Queued, CreatedAt = DateTime.UtcNow, SortOrder = 1 });
        db.Tasks.Add(new TaskEntity { Id = "q1", ListId = "L1", Title = "first", Status = TaskStatus.Queued, CreatedAt = DateTime.UtcNow, SortOrder = 0 });
        db.Tasks.Add(new TaskEntity { Id = "idle1", ListId = "L1", Title = "idle", Status = TaskStatus.Idle, CreatedAt = DateTime.UtcNow, SortOrder = 2 });
        await db.SaveChangesAsync();
    }

    [Fact]
    public async Task EnqueueTaskAsync_SetsTaskQueued_AndShowsInStrip()
    {
        await using (var db = NewContext())
        {
            db.Lists.Add(new ListEntity { Id = "L1", Name = "Work", CreatedAt = DateTime.UtcNow });
            db.Tasks.Add(new TaskEntity { Id = "idleTask", ListId = "L1", Title = "Do the thing", Status = TaskStatus.Idle, CreatedAt = DateTime.UtcNow, SortOrder = 0 });
            await db.SaveChangesAsync();
        }

        var worker = new FakeWorker();
        using var vm = BuildVm(worker);

        await vm.EnqueueTaskAsync("idleTask");

        Assert.True(vm.HasQueued);
        Assert.Contains(vm.Queued, q => q.Id == "idleTask");

        await using var verify = NewContext();
        var entity = await verify.Tasks.FirstAsync(t => t.Id == "idleTask");
        Assert.Equal(TaskStatus.Queued, entity.Status);
    }

    private sealed class ThrowingLaunchSpecWorker : StubWorkerClient
    {
        public override Task<LaunchSpec> GetInteractiveLaunchSpecAsync(string taskId, CancellationToken ct = default)
            => throw new InvalidOperationException("no worktree");
    }

    [Fact]
    public async Task OpenConPtySessionAsync_AddsPane_ToConPtySessionsAndPanes()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);

        await vm.OpenConPtySessionAsync("t1");

        Assert.Single(vm.ConPtySessions);
        Assert.Equal("t1", vm.ConPtySessions[0].TaskId);
        Assert.Single(vm.Panes);
        Assert.Same(vm.ConPtySessions[0], vm.Panes[0]);
        Assert.True(vm.HasPanes);
    }

    [Fact]
    public async Task OpenConPtySessionAsync_SameTaskTwice_DoesNotDuplicate()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);

        await vm.OpenConPtySessionAsync("t1");
        await vm.OpenConPtySessionAsync("t1");

        Assert.Single(vm.ConPtySessions);
    }

    [Fact]
    public async Task OpenConPtySessionAsync_WorkerThrows_RaisesErrorReported_NoPaneAdded()
    {
        var worker = new ThrowingLaunchSpecWorker();
        using var vm = BuildVm(worker);
        string? error = null;
        vm.ErrorReported += msg => error = msg;

        await vm.OpenConPtySessionAsync("t1");

        Assert.Empty(vm.ConPtySessions);
        Assert.Empty(vm.Panes);
        Assert.NotNull(error);
    }

    [Fact]
    public async Task Panes_CombinesMonitorsThenConPtySessions_InOrder()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);

        worker.RaiseTaskStarted("slot-1", "t1", DateTime.UtcNow);
        await vm.OpenConPtySessionAsync("t2");

        Assert.Equal(2, vm.Panes.Count);
        Assert.IsType<TaskMonitorViewModel>(vm.Panes[0]);
        Assert.IsType<ConPtyPaneViewModel>(vm.Panes[1]);
        Assert.Equal(2, vm.ColumnCount);
    }

    [Fact]
    public async Task CloseConPtySession_RemovesFromConPtySessionsAndPanes()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);
        await vm.OpenConPtySessionAsync("t1");
        var pane = vm.ConPtySessions[0];

        pane.CloseCommand.Execute(null);

        Assert.Empty(vm.ConPtySessions);
        Assert.Empty(vm.Panes);
    }

    private sealed class ThrowingAdHocLaunchSpecWorker : StubWorkerClient
    {
        public override Task<LaunchSpec> GetAdHocLaunchSpecAsync(string directory, CancellationToken ct = default)
            => throw new InvalidOperationException("bad directory");
    }

    [Fact]
    public async Task OpenAdHocConPtySessionAsync_AddsPane_TitleFromDirectoryLeaf()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);

        await vm.OpenAdHocConPtySessionAsync(Path.Combine("C:", "Some", "Path", "MyProject"));

        Assert.Single(vm.ConPtySessions);
        Assert.Null(vm.ConPtySessions[0].TaskId);
        Assert.Equal("MyProject", vm.ConPtySessions[0].DisplayTitle);
        Assert.Single(vm.Panes);
        Assert.Same(vm.ConPtySessions[0], vm.Panes[0]);
    }

    [Fact]
    public async Task OpenAdHocConPtySessionAsync_TwoOpens_NeverDeduped_YieldsTwoPanes()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);

        var dir = Path.Combine("C:", "Some", "Path");
        await vm.OpenAdHocConPtySessionAsync(dir);
        await vm.OpenAdHocConPtySessionAsync(dir);

        Assert.Equal(2, vm.ConPtySessions.Count);
        Assert.Equal(2, vm.Panes.Count);
        Assert.All(vm.ConPtySessions, s => Assert.Null(s.TaskId));
    }

    [Fact]
    public async Task OpenAdHocConPtySessionAsync_WorkerThrows_RaisesErrorReported_NoPaneAdded()
    {
        var worker = new ThrowingAdHocLaunchSpecWorker();
        using var vm = BuildVm(worker);
        string? error = null;
        vm.ErrorReported += msg => error = msg;

        await vm.OpenAdHocConPtySessionAsync(Path.Combine("C:", "Some", "Path"));

        Assert.Empty(vm.ConPtySessions);
        Assert.Empty(vm.Panes);
        Assert.NotNull(error);
    }

    [Fact]
    public async Task CloseConPtySession_RemovesAdHocPane_FromConPtySessionsAndPanes()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);
        await vm.OpenAdHocConPtySessionAsync(Path.Combine("C:", "Some", "Path"));
        var pane = vm.ConPtySessions[0];

        pane.CloseCommand.Execute(null);

        Assert.Empty(vm.ConPtySessions);
        Assert.Empty(vm.Panes);
    }

    [Fact]
    public void ToggleLayoutCommand_FlipsIsFocusMode()
    {
        var worker = new FakeWorker();
        using var vm = BuildVm(worker);

        Assert.False(vm.IsFocusMode);
        vm.ToggleLayoutCommand.Execute(null);
        Assert.True(vm.IsFocusMode);
        vm.ToggleLayoutCommand.Execute(null);
        Assert.False(vm.IsFocusMode);
    }
}
