package agent

import (
	"context"
	"fmt"
	"testing"

	"github.com/stretchr/testify/require"
)

func TestCodexInterruptActiveThreadRetriesServerActualTurnOnce(t *testing.T) {
	interruptCalls := 0
	driver, writer := newTestCodexDriver(t, func(req capturedRPCRequest) appServerEnvelope {
		require.Equal(t, "turn/interrupt", req.Method)
		interruptCalls++
		if interruptCalls == 1 {
			require.Equal(t, "turn-stale", req.Params["turnId"])
			return appServerEnvelope{Error: &appServerResponseError{
				Code:    -32602,
				Message: "expected active turn id turn-stale but found turn-live",
			}}
		}
		require.Equal(t, "turn-live", req.Params["turnId"])
		return appServerEnvelope{Result: rawRPCResult(t, map[string]any{})}
	})
	driver.threadID = "thread-1"
	driver.threadStatus = appServerThreadStatus{Type: "active"}
	driver.activeTurnID = "turn-stale"

	err := driver.interruptActiveThread(context.Background(), "thread-1", "turn-stale")

	require.NoError(t, err)
	require.Equal(t, []string{"turn/interrupt", "turn/interrupt"}, capturedRPCMethods(writer.requests))
	require.Equal(t, "turn-live", driver.currentActiveTurnID())
}

func TestCodexInterruptActiveThreadSecondMismatchStopsAfterTwo(t *testing.T) {
	interruptCalls := 0
	driver, writer := newTestCodexDriver(t, func(req capturedRPCRequest) appServerEnvelope {
		require.Equal(t, "turn/interrupt", req.Method)
		interruptCalls++
		expected := fmt.Sprint(req.Params["turnId"])
		actual := "turn-live"
		if interruptCalls == 2 {
			actual = "turn-newer"
		}
		return appServerEnvelope{Error: &appServerResponseError{
			Code:    -32602,
			Message: fmt.Sprintf("expected active turn id %s but found %s", expected, actual),
		}}
	})
	driver.threadID = "thread-1"
	driver.threadStatus = appServerThreadStatus{Type: "active"}
	driver.activeTurnID = "turn-stale"

	interruptedTurnID, err := driver.interruptActiveThreadWithTarget(context.Background(), "thread-1", "turn-stale")

	require.Error(t, err)
	require.Contains(t, err.Error(), "turn-newer")
	require.Equal(t, "turn-newer", interruptedTurnID, "snapshot correlation must follow the final concrete mismatch target")
	require.Equal(t, []string{"turn/interrupt", "turn/interrupt"}, capturedRPCMethods(writer.requests))
	require.Equal(t, "turn-newer", driver.currentActiveTurnID())
}

func TestCodexInterruptActiveThreadMismatchThenTerminalRaceReadsInactive(t *testing.T) {
	interruptCalls := 0
	driver, writer := newTestCodexDriver(t, func(req capturedRPCRequest) appServerEnvelope {
		switch req.Method {
		case "turn/interrupt":
			interruptCalls++
			if interruptCalls == 1 {
				return appServerEnvelope{Error: &appServerResponseError{
					Code:    -32602,
					Message: "expected active turn id turn-stale but found turn-live",
				}}
			}
			return appServerEnvelope{Error: &appServerResponseError{
				Code:    -32602,
				Message: "no active turn to interrupt",
			}}
		case "thread/read":
			return appServerEnvelope{Result: rawRPCResult(t, appServerThreadResult{
				Thread: appServerThread{ID: "thread-1", Status: appServerThreadStatus{Type: "idle"}},
			})}
		default:
			t.Fatalf("unexpected method %s", req.Method)
			return appServerEnvelope{}
		}
	})
	driver.threadID = "thread-1"
	driver.threadStatus = appServerThreadStatus{Type: "active"}
	driver.activeTurnID = "turn-stale"

	err := driver.interruptActiveThread(context.Background(), "thread-1", "turn-stale")

	require.NoError(t, err)
	require.Equal(t, []string{"turn/interrupt", "turn/interrupt", "thread/read"}, capturedRPCMethods(writer.requests))
	require.Empty(t, driver.currentActiveTurnID())
}
