package agent

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
	"time"

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

func TestValidateRecoveryCapability(t *testing.T) {
	tests := []struct {
		name     string
		runtime  string
		strategy string
		wantErr  bool
		wantCap  bool
	}{
		{"claude valid", "claude", "claude_session_resume_at", false, true},
		{"claude wrong strategy", "claude", "opencode_session_revert", true, false},
		{"codex valid", "codex", "codex_app_server_turns", false, true},
		{"codex wrong strategy", "codex", "opencode_session_revert", true, false},
		{"kimi valid", "kimi", "kimi_session_turns", false, true},
		{"kimi wrong strategy", "kimi", "codex_app_server_turns", true, false},
		{"codex-app-server unsupported", "codex-app-server", "codex_app_server_turns", true, false},
		{"opencode valid", "opencode", "opencode_session_revert", false, true},
		{"builtin valid", "builtin", "opencode_session_revert", false, true},
		{"builtin wrong strategy", "builtin", "builtin_message_count", true, false},
		{"empty runtime", "", "claude_session_resume_at", true, false},
		{"empty strategy", "claude", "", true, false},
		{"unknown runtime", "unknown", "unknown", true, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cap, err := ValidateRecoveryCapability(tt.runtime, tt.strategy)
			if (err != nil) != tt.wantErr {
				t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
			}
			if !tt.wantErr && cap.Supported != tt.wantCap {
				t.Errorf("Supported = %v, want %v", cap.Supported, tt.wantCap)
			}
		})
	}
}

func TestValidateTypeRejectsCodexAppServer(t *testing.T) {
	_, err := runtimeconfig.ValidateType("codex-app-server")
	if err == nil {
		t.Fatal("ValidateType(codex-app-server) succeeded, want error")
	}
	if !strings.Contains(err.Error(), "codex") {
		t.Fatalf("ValidateType(codex-app-server) error = %q, want valid values to mention codex", err)
	}
	validTypes, ok := strings.CutPrefix(err.Error(), `unsupported runtime type "codex-app-server" (valid types: `)
	if !ok {
		t.Fatalf("ValidateType(codex-app-server) error = %q, want standard valid types message", err)
	}
	if strings.Contains(validTypes, "codex-app-server") {
		t.Fatalf("ValidateType(codex-app-server) valid types = %q, want codex-app-server omitted", validTypes)
	}
}

func TestRuntimeTypeFromName(t *testing.T) {
	tests := []struct {
		name string
		want runtimeconfig.RuntimeType
	}{
		{"claude", runtimeconfig.TypeClaudeCode},
		{"claude-code", runtimeconfig.TypeClaudeCode},
		{"codex", runtimeconfig.TypeCodex},
		{"kimi", runtimeconfig.TypeKimi},
		{"opencode", runtimeconfig.TypeOpenCode},
		{"builtin", runtimeconfig.TypeBuiltin},
	}

	for _, tt := range tests {
		got := runtimeTypeFromName(tt.name)
		if got != tt.want {
			t.Errorf("runtimeTypeFromName(%q) = %q, want %q", tt.name, got, tt.want)
		}
	}
}

func TestDetachedRecoveryLaunchConfigCarriesModelAndProvider(t *testing.T) {
	provider := &drivercontract.ProviderConfig{
		ID:      "agentteam",
		Name:    "AgentTeam",
		BaseURL: "https://provider.example.com/codex",
		APIKey:  "sk-provider",
		EnvKey:  "AGENTTEAM_CODEX_PROVIDER_API_KEY",
		Headers: map[string]string{
			"X-Test": "one",
		},
		WireAPIs: []string{"responses"},
	}

	got := detachedRecoveryLaunchConfig(DetachedRecoveryConfig{
		AgentID:             " agent ",
		Runtime:             "codex",
		Binary:              " /opt/bin/codex ",
		SessionID:           " session ",
		WorkDir:             " /tmp/work ",
		RuntimeStateDir:     " /tmp/agent/.runtime ",
		PermissionMode:      " full-access ",
		Model:               " gpt-5.5 ",
		Provider:            provider,
		RuntimeDebugEnabled: true,
		Env:                 map[string]string{"KIMI_SHARE_DIR": "/tmp/kimi-share"},
	}, runtimeconfig.TypeCodex, false)

	if got.AgentID != "agent" || got.SessionID != "session" || got.WorkDir != "/tmp/work" {
		t.Fatalf("identity fields not trimmed: %#v", got)
	}
	if got.RuntimeStateDir != "/tmp/agent/.runtime" {
		t.Fatalf("RuntimeStateDir = %q, want /tmp/agent/.runtime", got.RuntimeStateDir)
	}
	if got.Model != "gpt-5.5" {
		t.Fatalf("Model = %q, want gpt-5.5", got.Model)
	}
	if !got.RuntimeDebugEnabled {
		t.Fatal("RuntimeDebugEnabled = false, want true")
	}
	if got.Env["KIMI_SHARE_DIR"] != "/tmp/kimi-share" {
		t.Fatalf("Env = %#v, want KIMI_SHARE_DIR", got.Env)
	}
	if got.Provider == nil {
		t.Fatal("Provider is nil")
	}
	if got.Provider == provider {
		t.Fatal("Provider was not cloned")
	}
	if got.Provider.ID != "agentteam" || got.Provider.EnvKey != "AGENTTEAM_CODEX_PROVIDER_API_KEY" {
		t.Fatalf("Provider = %#v, want agentteam provider with env key", got.Provider)
	}
	provider.Headers["X-Test"] = "mutated"
	if got.Provider.Headers["X-Test"] != "one" {
		t.Fatalf("Provider headers were not cloned: %#v", got.Provider.Headers)
	}
	if len(got.MCPs) != 0 {
		t.Fatalf("MCPs = %#v, want none for cleanup", got.MCPs)
	}

	withAgentTeam := detachedRecoveryLaunchConfig(DetachedRecoveryConfig{
		ServerURL: "http://agentteam.example.test/mcp",
		Provider:  provider,
	}, runtimeconfig.TypeCodex, true)
	if len(withAgentTeam.MCPs) != 1 || withAgentTeam.MCPs[0].Name != "agentteam" || withAgentTeam.MCPs[0].URL != "http://agentteam.example.test/mcp" {
		t.Fatalf("agentteam MCP = %#v, want agentteam MCP for restore", withAgentTeam.MCPs)
	}
}

func TestRecoveryTargetAheadErrorMatchesThroughWrap(t *testing.T) {
	err := fmt.Errorf("restore detached recovery: %w", &recoveryTargetAheadError{
		CurrentTurnCount: 4,
		TargetTurnCount:  7,
	})

	if !isRecoveryTargetAheadError(err) {
		t.Fatal("isRecoveryTargetAheadError() = false, want true")
	}
}

func TestRestoreRecoveryPointDetachedUnknownRuntimeUnsupported(t *testing.T) {
	recoveryRef, err := rewindcaps.EncodeOpenCodeSessionRevertRecoveryRef("opencode-session", "msg_1")
	if err != nil {
		t.Fatalf("EncodeOpenCodeSessionRevertRecoveryRef() error = %v", err)
	}

	err = RestoreRecoveryPointDetached(context.Background(), DetachedRecoveryConfig{
		Runtime: "unknown",
		WorkDir: t.TempDir(),
	}, rewindcaps.StrategyOpenCodeSessionRevert, recoveryRef)

	if err == nil {
		t.Fatal("RestoreRecoveryPointDetached() succeeded, want unsupported error")
	}
	if !strings.Contains(err.Error(), "does not support recovery restore") {
		t.Fatalf("RestoreRecoveryPointDetached() error = %q, want unsupported recovery restore", err)
	}
}

func TestCleanupRecoveryArtifactsDetachedDoesNotLaunchCodexRestore(t *testing.T) {
	markerPath := filepath.Join(t.TempDir(), "codex-started")
	recoveryRef, err := rewindcaps.EncodeCodexAppServerRecoveryRef("thread-1", 2)
	if err != nil {
		t.Fatalf("EncodeCodexAppServerRecoveryRef() error = %v", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	err = CleanupRecoveryArtifactsDetached(ctx, DetachedRecoveryConfig{
		Runtime:        "codex",
		Binary:         writeCodexCleanupProbeBinary(t, markerPath),
		WorkDir:        t.TempDir(),
		PermissionMode: types.AgentPermissionModeFullAccess,
	}, []RecoveryArtifact{{
		Strategy:    rewindcaps.StrategyCodexAppServerTurns,
		RecoveryRef: recoveryRef,
	}})
	if err != nil {
		t.Fatalf("CleanupRecoveryArtifactsDetached() error = %v", err)
	}
	if _, err := os.Stat(markerPath); !os.IsNotExist(err) {
		t.Fatalf("Codex cleanup launched a restore process, marker error = %v", err)
	}
}

func writeCodexCleanupProbeBinary(t *testing.T, markerPath string) string {
	t.Helper()

	executable, err := os.Executable()
	if err != nil {
		t.Fatalf("os.Executable() error = %v", err)
	}

	dir := t.TempDir()
	if runtime.GOOS == "windows" {
		path := filepath.Join(dir, "codex.cmd")
		script := fmt.Sprintf("@echo off\r\nset \"AGENTTEAM_TEST_CODEX_CLEANUP_MARKER=%s\"\r\n\"%s\" -test.run=TestCodexCleanupProbe -- %%*\r\n", markerPath, executable)
		if err := os.WriteFile(path, []byte(script), 0o755); err != nil {
			t.Fatalf("write Codex cleanup probe: %v", err)
		}
		return path
	}

	path := filepath.Join(dir, "codex")
	script := fmt.Sprintf("#!/bin/sh\nAGENTTEAM_TEST_CODEX_CLEANUP_MARKER=%s exec %s -test.run=TestCodexCleanupProbe -- \"$@\"\n", testShellQuote(markerPath), testShellQuote(executable))
	if err := os.WriteFile(path, []byte(script), 0o755); err != nil {
		t.Fatalf("write Codex cleanup probe: %v", err)
	}
	return path
}

func TestCodexCleanupProbe(t *testing.T) {
	markerPath := os.Getenv("AGENTTEAM_TEST_CODEX_CLEANUP_MARKER")
	if markerPath == "" {
		return
	}
	if err := os.WriteFile(markerPath, []byte("started"), 0o600); err != nil {
		t.Fatalf("write Codex cleanup probe marker: %v", err)
	}
}
