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

namespace ClaudeDo.Ui.Tests.ViewModels;

public class DetailsIslandRoadblockReplyTests : IDisposable
{
    private readonly string _dbPath;

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

        // Loc is a process-wide ambient singleton other tests also mutate — pin it to
        // the real locale data so this test's assertions don't depend on run order.
        var dir = AppContext.BaseDirectory;
        while (dir is not null && !Directory.Exists(Path.Combine(dir, "src", "ClaudeDo.Localization", "locales")))
            dir = Path.GetDirectoryName(dir);
        Loc.Current = new Localizer(
            LocaleStore.Load(Path.Combine(dir!, "src", "ClaudeDo.Localization", "locales")), "en");
    }

    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 RecordingWorkerClient : StubWorkerClient
    {
        public override bool IsConnected => true;
        public (string TaskId, string Prompt)? LastContinue;
        public Exception? ThrowOnContinue;

        public override Task ContinueTaskAsync(string taskId, string followUpPrompt)
        {
            if (ThrowOnContinue is not null) throw ThrowOnContinue;
            LastContinue = (taskId, followUpPrompt);
            return Task.CompletedTask;
        }
    }

    private DetailsIslandViewModel BuildVm(StubWorkerClient worker)
    {
        var factory = new TestDbFactory(NewContext);
        return new DetailsIslandViewModel(factory, worker, new NullServiceProvider(), new ClaudeDo.Ui.Services.MergeCoordinator());
    }

    private static DetailsIslandViewModel PrepareDoneTaskWithRoadblock(DetailsIslandViewModel vm, string taskId = "task-rb-1")
    {
        vm.Bind(new TaskRowViewModel { Id = taskId, Status = TaskStatus.Done });
        vm.Monitor.ApplyState(TaskStatus.Done);
        vm.Roadblocks = "needs go-ahead to merge branches";
        return vm;
    }

    [Fact]
    public void CanReplyToRoadblock_IsFalse_WithoutSessionId()
    {
        var vm = BuildVm(new RecordingWorkerClient());
        PrepareDoneTaskWithRoadblock(vm);

        Assert.True(vm.ShowRoadblockCard);
        Assert.Null(vm.LatestRunSessionId);
        Assert.False(vm.CanReplyToRoadblock);
        Assert.False(vm.SendRoadblockReplyCommand.CanExecute(null));
    }

    [Fact]
    public void CanReplyToRoadblock_BecomesTrue_OnceSessionIdIsPresent()
    {
        var vm = BuildVm(new RecordingWorkerClient());
        PrepareDoneTaskWithRoadblock(vm);

        vm.LatestRunSessionId = "session-abc";

        Assert.True(vm.CanReplyToRoadblock);
    }

    [Fact]
    public void SendRoadblockReplyCommand_IsDisabled_WhenDraftIsEmptyOrWhitespace()
    {
        var vm = BuildVm(new RecordingWorkerClient());
        PrepareDoneTaskWithRoadblock(vm);
        vm.LatestRunSessionId = "session-abc";

        Assert.False(vm.SendRoadblockReplyCommand.CanExecute(null));

        vm.RoadblockReplyDraft = "   ";
        Assert.False(vm.SendRoadblockReplyCommand.CanExecute(null));

        vm.RoadblockReplyDraft = "go ahead, merge it";
        Assert.True(vm.SendRoadblockReplyCommand.CanExecute(null));
    }

    [Fact]
    public async Task SendRoadblockReply_PassesTypedText_NotTheHardcodedContinueConstant()
    {
        var worker = new RecordingWorkerClient();
        var vm = BuildVm(worker);
        PrepareDoneTaskWithRoadblock(vm);
        vm.LatestRunSessionId = "session-abc";
        vm.RoadblockReplyDraft = "yes, go ahead and merge";

        await vm.SendRoadblockReplyCommand.ExecuteAsync(null);

        Assert.Equal(("task-rb-1", "yes, go ahead and merge"), worker.LastContinue);
        Assert.NotEqual("Continue working on this task.", worker.LastContinue?.Prompt);
    }

    [Fact]
    public async Task SendRoadblockReply_ClearsDraft_OnSuccess()
    {
        var worker = new RecordingWorkerClient();
        var vm = BuildVm(worker);
        PrepareDoneTaskWithRoadblock(vm);
        vm.LatestRunSessionId = "session-abc";
        vm.RoadblockReplyDraft = "go ahead";

        await vm.SendRoadblockReplyCommand.ExecuteAsync(null);

        Assert.Equal(string.Empty, vm.RoadblockReplyDraft);
    }

    [Fact]
    public async Task SendRoadblockReply_WhenWorkerThrows_ReportsErrorAndKeepsDraft()
    {
        var worker = new RecordingWorkerClient { ThrowOnContinue = new Exception("override slot busy") };
        var vm = BuildVm(worker);
        PrepareDoneTaskWithRoadblock(vm);
        vm.LatestRunSessionId = "session-abc";
        vm.RoadblockReplyDraft = "go ahead";

        string? reportedError = null;
        vm.ErrorReported += msg => reportedError = msg;

        await vm.SendRoadblockReplyCommand.ExecuteAsync(null);

        Assert.NotNull(reportedError);
        Assert.Contains("override slot busy", reportedError);
        Assert.Equal("go ahead", vm.RoadblockReplyDraft);
    }

    [Fact]
    public void Bind_ClearsRoadblockReplyDraft_ForNewTask()
    {
        var vm = BuildVm(new RecordingWorkerClient());
        PrepareDoneTaskWithRoadblock(vm);
        vm.LatestRunSessionId = "session-abc";
        vm.RoadblockReplyDraft = "leftover text";

        vm.Bind(new TaskRowViewModel { Id = "task-rb-2", Status = TaskStatus.Idle });

        Assert.Equal(string.Empty, vm.RoadblockReplyDraft);
    }

    [Fact]
    public void ShowContinueAndCanContinue_AreUnaffected_ByRoadblockReplyAddition()
    {
        // Regression: the existing Continue button (Failed/Cancelled only) must keep
        // working exactly as before — the roadblock reply field is an additional path,
        // not a replacement.
        var vm = BuildVm(new RecordingWorkerClient());
        vm.Bind(new TaskRowViewModel { Id = "task-rb-3", Status = TaskStatus.Failed });
        vm.Monitor.ApplyState(TaskStatus.Failed);

        Assert.True(vm.ShowContinue);
        Assert.False(vm.ContinueCommand.CanExecute(null)); // no session id yet

        vm.LatestRunSessionId = "session-xyz";
        Assert.True(vm.ContinueCommand.CanExecute(null));

        vm.Monitor.ApplyState(TaskStatus.Done);
        Assert.False(vm.ShowContinue);
    }

    [Theory]
    [InlineData(TaskStatus.WaitingForReview, true)]
    [InlineData(TaskStatus.Done, true)]
    [InlineData(TaskStatus.Failed, true)]
    [InlineData(TaskStatus.Cancelled, true)]
    [InlineData(TaskStatus.Running, false)]
    [InlineData(TaskStatus.Queued, false)]
    [InlineData(TaskStatus.Idle, false)]
    [InlineData(TaskStatus.WaitingForChildren, false)]
    public void ShowRoadblockCard_MatchesStatusMatrix_WhenARoadblockWasReported(TaskStatus status, bool expectedVisible)
    {
        var vm = BuildVm(new RecordingWorkerClient());
        vm.Bind(new TaskRowViewModel { Id = "task-rb-matrix", Status = status });
        vm.Monitor.ApplyState(status);
        vm.Roadblocks = "needs a go-ahead";

        Assert.Equal(expectedVisible, vm.ShowRoadblockCard);
    }

    [Fact]
    public void ShowRoadblockCard_IsFalse_WithoutARoadblock_RegardlessOfStatus()
    {
        var vm = BuildVm(new RecordingWorkerClient());
        vm.Bind(new TaskRowViewModel { Id = "task-rb-none", Status = TaskStatus.Done });
        vm.Monitor.ApplyState(TaskStatus.Done);

        Assert.False(vm.ShowRoadblockCard);
    }
}
