package agent

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/drivercontract"
	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
)

func TestCodexAppServerCurrentLaunchConfigKeepsImmutableResumeSnapshot(t *testing.T) {
	driver := &CodexAppServerDriver{}
	cfg := testLaunchConfig(drivercontract.LaunchConfig{WorkDir: "/tmp/agent", PermissionMode: types.AgentPermissionModeFullAccess, Model: "gpt-launch", ReasoningEffort: "high"}, testLaunchSurfaces{BaseInstructions: "base launch instructions", DeveloperInstructions: "developer launch instructions"})
	driver.mu.Lock()
	driver.resetAppServerStateForLaunchLocked(cfg, nil)
	driver.mu.Unlock()

	// Runtime model discovery/rerouting is mutable thread state. It must never
	// rewrite the launch-time model used to rehydrate notLoaded threads.
	driver.updateEffectiveModel("gpt-rerouted")
	cfg.Model = "mutated caller config"
	setTestLaunchSurface(&cfg, "BaseInstructions", "mutated caller instructions")

	got := driver.currentLaunchConfig()
	require.Equal(t, "/tmp/agent", got.WorkDir)
	require.Equal(t, types.AgentPermissionModeFullAccess, got.PermissionMode)
	require.Equal(t, "gpt-launch", got.Model)
	require.Equal(t, "base launch instructions", testLaunchSurface(got, "baseInstructions"))
	require.Equal(t, "developer launch instructions", testLaunchSurface(got, "developerInstructions"))
	require.Equal(t, "high", got.ReasoningEffort)
}

func TestCodexAppServerResumeThreadParamsOmitEmptyOptionals(t *testing.T) {
	driver := &CodexAppServerDriver{}

	params, err := driver.resumeThreadParams(testLaunchConfig(drivercontract.LaunchConfig{ReasoningEffort: "high"}, testLaunchSurfaces{BaseInstructions: " \t\n", DeveloperInstructions: ""}), "thread-1")

	require.NoError(t, err)
	require.Equal(t, "thread-1", params["threadId"])
	require.Equal(t, true, params["excludeTurns"])
	require.NotContains(t, params, "cwd")
	require.NotContains(t, params, "model")
	require.NotContains(t, params, "baseInstructions")
	require.NotContains(t, params, "developerInstructions")
	require.NotContains(t, params, "config", "model reasoning effort must not be sent without its pinned model")
}

func TestCodexAppServerResumeThreadForRecoveryReplaysLaunchPolicy(t *testing.T) {
	driver, writer := newTestCodexDriver(t, func(req capturedRPCRequest) appServerEnvelope {
		switch req.Method {
		case "thread/resume":
			return appServerEnvelope{Result: rawRPCResult(t, appServerThreadResult{
				Thread: appServerThread{ID: "thread-1", Status: appServerThreadStatus{Type: "idle"}},
				Model:  "gpt-launch",
			})}
		case "thread/read":
			return appServerEnvelope{Result: rawRPCResult(t, appServerThreadResult{
				Thread: appServerThread{ID: "thread-1", Status: appServerThreadStatus{Type: "idle"}},
			})}
		default:
			t.Fatalf("unexpected request %s", req.Method)
			return appServerEnvelope{}
		}
	})
	driver.reasoningSummary = "detailed"

	_, err := driver.resumeThreadForRecovery(context.Background(), testLaunchConfig(drivercontract.LaunchConfig{WorkDir: "/tmp/agent", PermissionMode: types.AgentPermissionModeFullAccess, Model: "gpt-launch", ReasoningEffort: "high"}, testLaunchSurfaces{BaseInstructions: "base launch instructions", DeveloperInstructions: "developer launch instructions"}), "thread-1")

	require.NoError(t, err)
	require.Equal(t, []string{"thread/resume", "thread/read"}, capturedRPCMethods(writer.requests))
	resume := writer.requests[0].Params
	require.Equal(t, true, resume["excludeTurns"])
	require.Equal(t, "/tmp/agent", resume["cwd"])
	require.Equal(t, "gpt-launch", resume["model"])
	require.Equal(t, "base launch instructions", resume["baseInstructions"])
	require.Equal(t, "developer launch instructions", resume["developerInstructions"])
	require.Equal(t, map[string]any{
		"model_reasoning_effort":  "high",
		"model_reasoning_summary": "detailed",
	}, resume["config"])
}

func TestCodexAppServerResumeThreadForRecoveryPreservesReasoningEffortLevels(t *testing.T) {
	t.Parallel()

	for _, effort := range []string{"max", "ultra", "xhigh"} {
		t.Run(effort, func(t *testing.T) {
			driver, writer := newTestCodexDriver(t, func(req capturedRPCRequest) appServerEnvelope {
				switch req.Method {
				case "thread/resume":
					return appServerEnvelope{Result: rawRPCResult(t, appServerThreadResult{
						Thread: appServerThread{ID: "thread-1", Status: appServerThreadStatus{Type: "idle"}},
						Model:  "gpt-5.4",
					})}
				case "thread/read":
					return appServerEnvelope{Result: rawRPCResult(t, appServerThreadResult{
						Thread: appServerThread{ID: "thread-1", Status: appServerThreadStatus{Type: "idle"}},
					})}
				default:
					t.Fatalf("unexpected request %s", req.Method)
					return appServerEnvelope{}
				}
			})

			_, err := driver.resumeThreadForRecovery(context.Background(), drivercontract.LaunchConfig{
				Model:           "gpt-5.4",
				ReasoningEffort: effort,
			}, "thread-1")

			require.NoError(t, err)
			require.Equal(t, map[string]any{
				"model_reasoning_effort": effort,
			}, writer.requests[0].Params["config"])
		})
	}
}

func TestCodexAppServerResumeFreshUnmaterializedThreadUsesMetadataFallback(t *testing.T) {
	driver, writer := newTestCodexDriver(t, func(req capturedRPCRequest) appServerEnvelope {
		switch req.Method {
		case "thread/resume":
			return appServerEnvelope{Result: rawRPCResult(t, appServerThreadResult{
				Thread: appServerThread{ID: "thread-fresh", Status: appServerThreadStatus{Type: "idle"}},
			})}
		case "thread/read":
			if req.Params["includeTurns"] == true {
				return appServerEnvelope{Error: &appServerResponseError{
					Code:    -32600,
					Message: "thread thread-fresh is not materialized yet; includeTurns is unavailable before first user message",
				}}
			}
			return appServerEnvelope{Result: rawRPCResult(t, appServerThreadResult{
				Thread: appServerThread{ID: "thread-fresh", Status: appServerThreadStatus{Type: "idle"}},
			})}
		default:
			t.Fatalf("unexpected request %s", req.Method)
			return appServerEnvelope{}
		}
	})

	err := driver.resumeThread(context.Background(), drivercontract.LaunchConfig{}, "thread-fresh")

	require.NoError(t, err)
	require.Equal(t, []string{"thread/resume", "thread/read", "thread/read"}, capturedRPCMethods(writer.requests))
	status, activeTurnID, ok := driver.currentThreadState("thread-fresh")
	require.True(t, ok)
	require.Equal(t, "idle", normalizeThreadStatus(status).Type)
	require.Empty(t, activeTurnID)
}

func TestCodexAckedDeliveryCachedNotLoadedReplaysFullLaunchSnapshot(t *testing.T) {
	readCalls := 0
	driver, writer := newTestCodexDriver(t, func(req capturedRPCRequest) appServerEnvelope {
		switch req.Method {
		case "thread/read":
			readCalls++
			status := "notLoaded"
			if readCalls > 1 {
				status = "idle"
			}
			return appServerEnvelope{Result: rawRPCResult(t, appServerThreadResult{
				Thread: appServerThread{ID: "thread-1", Status: appServerThreadStatus{Type: status}},
			})}
		case "thread/resume":
			require.Equal(t, "/tmp/agent", req.Params["cwd"])
			require.Equal(t, "gpt-launch", req.Params["model"])
			require.Equal(t, "base launch instructions", req.Params["baseInstructions"])
			require.Equal(t, "developer launch instructions", req.Params["developerInstructions"])
			require.Equal(t, map[string]any{
				"model_reasoning_effort":  "high",
				"model_reasoning_summary": "detailed",
			}, req.Params["config"])
			return appServerEnvelope{Result: rawRPCResult(t, appServerThreadResult{
				Thread: appServerThread{ID: "thread-1", Status: appServerThreadStatus{Type: "idle"}},
				Model:  "gpt-launch",
			})}
		case "turn/start":
			require.Equal(t, "delivery-1", req.Params["clientUserMessageId"])
			return appServerEnvelope{Result: rawRPCResult(t, appServerTurnResult{
				Turn: appServerTurn{ID: "turn-1", Status: appServerTurnStatus{Type: "inProgress"}},
			})}
		default:
			t.Fatalf("unexpected request %s", req.Method)
			return appServerEnvelope{}
		}
	})
	cfg := testLaunchConfig(drivercontract.LaunchConfig{WorkDir: "/tmp/agent", PermissionMode: types.AgentPermissionModeFullAccess, Model: "gpt-launch", ReasoningEffort: "high", Env: map[string]string{
		"AGENTTEAM_CODEX_REASONING_SUMMARY": "detailed",
	}}, testLaunchSurfaces{BaseInstructions: "base launch instructions", DeveloperInstructions: "developer launch instructions"})
	driver.mu.Lock()
	driver.resetAppServerStateForLaunchLocked(cfg, writer)
	driver.threadID = "thread-1"
	driver.threadStatus = appServerThreadStatus{Type: "notLoaded"}
	driver.mu.Unlock()

	err := driver.DeliverAckedBusyRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "delivery-1"}, "resume this thread"), "thread-1")

	require.NoError(t, err)
	require.Equal(t, []string{"thread/read", "thread/resume", "thread/read", "turn/start"}, capturedRPCMethods(writer.requests))
}
