using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels.MissionControl;
using Xunit;

namespace ClaudeDo.Ui.Tests.ViewModels.MissionControl;

public class ConPtyPaneViewModelTests
{
    private static ConPtyPaneViewModel NewTaskPane(
        Func<Task<TerminalLaunchDescriptor>> descriptorFactory,
        string taskId = "t1")
        => new(taskId, "Some Task", descriptorFactory);

    private static Task<TerminalLaunchDescriptor> NeverCompletes()
        => new TaskCompletionSource<TerminalLaunchDescriptor>().Task;

    private static Task<TerminalLaunchDescriptor> Failing(string message = "boom")
        => Task.FromException<TerminalLaunchDescriptor>(new InvalidOperationException(message));

    // ── CanSubmitForReview gating (fix: don't offer Submit for Review on a dead/starting pane) ──

    [Fact]
    public void CanSubmitForReview_False_WhileStarting()
    {
        using var pane = NewTaskPane(NeverCompletes);
        pane.Start();

        Assert.True(pane.Terminal.IsStarting);
        Assert.False(pane.SubmitForReviewCommand.CanExecute(null));
    }

    [Fact]
    public void CanSubmitForReview_False_AfterLaunchFailure()
    {
        using var pane = NewTaskPane(() => Failing());
        pane.Start();

        Assert.NotNull(pane.Terminal.StartError);
        Assert.True(pane.Terminal.HasExited);
        Assert.False(pane.SubmitForReviewCommand.CanExecute(null));
    }

    [Fact]
    public void CanSubmitForReview_True_WhenRunning()
    {
        using var pane = NewTaskPane(NeverCompletes);
        SetRunning(pane);

        Assert.True(pane.SubmitForReviewCommand.CanExecute(null));
    }

    [Fact]
    public void CanSubmitForReview_False_ForAdHocPane_EvenWhileRunning()
    {
        using var pane = ConPtyPaneViewModel.CreateAdHoc("Ad hoc", NeverCompletes);
        SetRunning(pane);

        Assert.False(pane.IsTaskBased);
        Assert.False(pane.SubmitForReviewCommand.CanExecute(null));
    }

    [Fact]
    public void CanSubmitForReview_False_WhileSubmitPending()
    {
        using var pane = NewTaskPane(NeverCompletes);
        SetRunning(pane);
        Assert.True(pane.SubmitForReviewCommand.CanExecute(null));

        pane.IsSubmitPending = true;

        Assert.False(pane.SubmitForReviewCommand.CanExecute(null));

        pane.IsSubmitPending = false;

        Assert.True(pane.SubmitForReviewCommand.CanExecute(null));
    }

    // ── Retry (fix: a failed launch used to permanently occupy the TaskId dedupe slot) ──────────

    [Fact]
    public void RetryCommand_Disabled_BeforeAndWhileStarting()
    {
        using var pane = NewTaskPane(NeverCompletes);
        Assert.False(pane.RetryCommand.CanExecute(null));

        pane.Start();

        Assert.False(pane.RetryCommand.CanExecute(null)); // still starting, no failure yet
    }

    [Fact]
    public void RetryCommand_Enabled_AfterLaunchFailure()
    {
        using var pane = NewTaskPane(() => Failing());
        pane.Start();

        Assert.True(pane.RetryCommand.CanExecute(null));
    }

    [Fact]
    public void Retry_ReplacesTerminal_AndRefetchesDescriptor()
    {
        var callCount = 0;
        Func<Task<TerminalLaunchDescriptor>> factory = () =>
        {
            callCount++;
            return callCount == 1 ? Failing() : NeverCompletes();
        };

        using var pane = NewTaskPane(factory);
        pane.Start();
        Assert.True(pane.RetryCommand.CanExecute(null));
        var terminalBeforeRetry = pane.Terminal;

        pane.RetryCommand.Execute(null);

        Assert.Equal(2, callCount);
        Assert.NotSame(terminalBeforeRetry, pane.Terminal);
        Assert.Null(pane.Terminal.StartError);
        Assert.False(pane.Terminal.HasExited);
        Assert.False(pane.RetryCommand.CanExecute(null));
    }

    [Fact]
    public void Retry_FailsAgain_StillOffersRetry()
    {
        using var pane = NewTaskPane(() => Failing());
        pane.Start();
        Assert.True(pane.RetryCommand.CanExecute(null));

        pane.RetryCommand.Execute(null);

        Assert.NotNull(pane.Terminal.StartError);
        Assert.True(pane.RetryCommand.CanExecute(null));
    }

    // ── Died-at-startup (fix: an immediate ConPTY exit used to leave StartError null, so Retry
    //    never became reachable and the pane was stuck reusing the same persisted session id) ──

    [Fact]
    public void RetryCommand_Enabled_AfterProcessExitsNonzeroRightAfterLaunch()
    {
        using var pane = NewTaskPane(NeverCompletes);
        SetRunning(pane);
        Assert.False(pane.RetryCommand.CanExecute(null));

        pane.Terminal.OnSessionProcessExited(null, 1);

        Assert.NotNull(pane.Terminal.StartError);
        Assert.True(pane.RetryCommand.CanExecute(null));
    }

    private static void SetRunning(ConPtyPaneViewModel pane) => pane.Terminal.IsRunning = true;
}
