package migrate

import (
	"encoding/json"
	"os"
	"path/filepath"
	"testing"

	"github.com/sipeed/picoclaw/pkg/config"
)

func TestCamelToSnake(t *testing.T) {
	tests := []struct {
		name  string
		input string
		want  string
	}{
		{"simple", "apiKey", "api_key"},
		{"two words", "apiBase", "api_base"},
		{"three words", "maxToolIterations", "max_tool_iterations"},
		{"already snake", "api_key", "api_key"},
		{"single word", "enabled", "enabled"},
		{"all lower", "model", "model"},
		{"consecutive caps", "apiURL", "api_url"},
		{"starts upper", "Model", "model"},
		{"bridge url", "bridgeUrl", "bridge_url"},
		{"client id", "clientId", "client_id"},
		{"app secret", "appSecret", "app_secret"},
		{"verification token", "verificationToken", "verification_token"},
		{"allow from", "allowFrom", "allow_from"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := camelToSnake(tt.input)
			if got != tt.want {
				t.Errorf("camelToSnake(%q) = %q, want %q", tt.input, got, tt.want)
			}
		})
	}
}

func TestConvertKeysToSnake(t *testing.T) {
	input := map[string]interface{}{
		"apiKey":  "test-key",
		"apiBase": "https://example.com",
		"nested": map[string]interface{}{
			"maxTokens": float64(8192),
			"allowFrom": []interface{}{"user1", "user2"},
			"deeperLevel": map[string]interface{}{
				"clientId": "abc",
			},
		},
	}

	result := convertKeysToSnake(input)
	m, ok := result.(map[string]interface{})
	if !ok {
		t.Fatal("expected map[string]interface{}")
	}

	if _, ok := m["api_key"]; !ok {
		t.Error("expected key 'api_key' after conversion")
	}
	if _, ok := m["api_base"]; !ok {
		t.Error("expected key 'api_base' after conversion")
	}

	nested, ok := m["nested"].(map[string]interface{})
	if !ok {
		t.Fatal("expected nested map")
	}
	if _, ok := nested["max_tokens"]; !ok {
		t.Error("expected key 'max_tokens' in nested map")
	}
	if _, ok := nested["allow_from"]; !ok {
		t.Error("expected key 'allow_from' in nested map")
	}

	deeper, ok := nested["deeper_level"].(map[string]interface{})
	if !ok {
		t.Fatal("expected deeper_level map")
	}
	if _, ok := deeper["client_id"]; !ok {
		t.Error("expected key 'client_id' in deeper level")
	}
}

func TestLoadOpenClawConfig(t *testing.T) {
	tmpDir := t.TempDir()
	configPath := filepath.Join(tmpDir, "openclaw.json")

	openclawConfig := map[string]interface{}{
		"providers": map[string]interface{}{
			"anthropic": map[string]interface{}{
				"apiKey":  "sk-ant-test123",
				"apiBase": "https://api.anthropic.com",
			},
		},
		"agents": map[string]interface{}{
			"defaults": map[string]interface{}{
				"maxTokens": float64(4096),
				"model":     "claude-3-opus",
			},
		},
	}

	data, err := json.Marshal(openclawConfig)
	if err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(configPath, data, 0644); err != nil {
		t.Fatal(err)
	}

	result, err := LoadOpenClawConfig(configPath)
	if err != nil {
		t.Fatalf("LoadOpenClawConfig: %v", err)
	}

	providers, ok := result["providers"].(map[string]interface{})
	if !ok {
		t.Fatal("expected providers map")
	}
	anthropic, ok := providers["anthropic"].(map[string]interface{})
	if !ok {
		t.Fatal("expected anthropic map")
	}
	if anthropic["api_key"] != "sk-ant-test123" {
		t.Errorf("api_key = %v, want sk-ant-test123", anthropic["api_key"])
	}

	agents, ok := result["agents"].(map[string]interface{})
	if !ok {
		t.Fatal("expected agents map")
	}
	defaults, ok := agents["defaults"].(map[string]interface{})
	if !ok {
		t.Fatal("expected defaults map")
	}
	if defaults["max_tokens"] != float64(4096) {
		t.Errorf("max_tokens = %v, want 4096", defaults["max_tokens"])
	}
}

func TestConvertConfig(t *testing.T) {
	t.Run("providers mapping", func(t *testing.T) {
		data := map[string]interface{}{
			"providers": map[string]interface{}{
				"anthropic": map[string]interface{}{
					"api_key":  "sk-ant-test",
					"api_base": "https://api.anthropic.com",
				},
				"openrouter": map[string]interface{}{
					"api_key": "sk-or-test",
				},
				"groq": map[string]interface{}{
					"api_key": "gsk-test",
				},
			},
		}

		cfg, warnings, err := ConvertConfig(data)
		if err != nil {
			t.Fatalf("ConvertConfig: %v", err)
		}
		if len(warnings) != 0 {
			t.Errorf("expected no warnings, got %v", warnings)
		}
		if cfg.Providers.Anthropic.APIKey != "sk-ant-test" {
			t.Errorf("Anthropic.APIKey = %q, want %q", cfg.Providers.Anthropic.APIKey, "sk-ant-test")
		}
		if cfg.Providers.OpenRouter.APIKey != "sk-or-test" {
			t.Errorf("OpenRouter.APIKey = %q, want %q", cfg.Providers.OpenRouter.APIKey, "sk-or-test")
		}
		if cfg.Providers.Groq.APIKey != "gsk-test" {
			t.Errorf("Groq.APIKey = %q, want %q", cfg.Providers.Groq.APIKey, "gsk-test")
		}
	})

	t.Run("unsupported provider warning", func(t *testing.T) {
		data := map[string]interface{}{
			"providers": map[string]interface{}{
				"deepseek": map[string]interface{}{
					"api_key": "sk-deep-test",
				},
			},
		}

		_, warnings, err := ConvertConfig(data)
		if err != nil {
			t.Fatalf("ConvertConfig: %v", err)
		}
		if len(warnings) != 1 {
			t.Fatalf("expected 1 warning, got %d", len(warnings))
		}
		if warnings[0] != "Provider 'deepseek' not supported in PicoClaw, skipping" {
			t.Errorf("unexpected warning: %s", warnings[0])
		}
	})

	t.Run("channels mapping", func(t *testing.T) {
		data := map[string]interface{}{
			"channels": map[string]interface{}{
				"telegram": map[string]interface{}{
					"enabled":    true,
					"token":      "tg-token-123",
					"allow_from": []interface{}{"user1"},
				},
				"discord": map[string]interface{}{
					"enabled": true,
					"token":   "disc-token-456",
				},
			},
		}

		cfg, _, err := ConvertConfig(data)
		if err != nil {
			t.Fatalf("ConvertConfig: %v", err)
		}
		if !cfg.Channels.Telegram.Enabled {
			t.Error("Telegram should be enabled")
		}
		if cfg.Channels.Telegram.Token != "tg-token-123" {
			t.Errorf("Telegram.Token = %q, want %q", cfg.Channels.Telegram.Token, "tg-token-123")
		}
		if len(cfg.Channels.Telegram.AllowFrom) != 1 || cfg.Channels.Telegram.AllowFrom[0] != "user1" {
			t.Errorf("Telegram.AllowFrom = %v, want [user1]", cfg.Channels.Telegram.AllowFrom)
		}
		if !cfg.Channels.Discord.Enabled {
			t.Error("Discord should be enabled")
		}
	})

	t.Run("unsupported channel warning", func(t *testing.T) {
		data := map[string]interface{}{
			"channels": map[string]interface{}{
				"email": map[string]interface{}{
					"enabled": true,
				},
			},
		}

		_, warnings, err := ConvertConfig(data)
		if err != nil {
			t.Fatalf("ConvertConfig: %v", err)
		}
		if len(warnings) != 1 {
			t.Fatalf("expected 1 warning, got %d", len(warnings))
		}
		if warnings[0] != "Channel 'email' not supported in PicoClaw, skipping" {
			t.Errorf("unexpected warning: %s", warnings[0])
		}
	})

	t.Run("agent defaults", func(t *testing.T) {
		data := map[string]interface{}{
			"agents": map[string]interface{}{
				"defaults": map[string]interface{}{
					"model":               "claude-3-opus",
					"max_tokens":          float64(4096),
					"temperature":         0.5,
					"max_tool_iterations": float64(10),
					"workspace":           "~/.openclaw/workspace",
				},
			},
		}

		cfg, _, err := ConvertConfig(data)
		if err != nil {
			t.Fatalf("ConvertConfig: %v", err)
		}
		if cfg.Agents.Defaults.Model != "claude-3-opus" {
			t.Errorf("Model = %q, want %q", cfg.Agents.Defaults.Model, "claude-3-opus")
		}
		if cfg.Agents.Defaults.MaxTokens != 4096 {
			t.Errorf("MaxTokens = %d, want %d", cfg.Agents.Defaults.MaxTokens, 4096)
		}
		if cfg.Agents.Defaults.Temperature != 0.5 {
			t.Errorf("Temperature = %f, want %f", cfg.Agents.Defaults.Temperature, 0.5)
		}
		if cfg.Agents.Defaults.Workspace != "~/.picoclaw/workspace" {
			t.Errorf("Workspace = %q, want %q", cfg.Agents.Defaults.Workspace, "~/.picoclaw/workspace")
		}
	})

	t.Run("empty config", func(t *testing.T) {
		data := map[string]interface{}{}

		cfg, warnings, err := ConvertConfig(data)
		if err != nil {
			t.Fatalf("ConvertConfig: %v", err)
		}
		if len(warnings) != 0 {
			t.Errorf("expected no warnings, got %v", warnings)
		}
		if cfg.Agents.Defaults.Model != "glm-4.7" {
			t.Errorf("default model should be glm-4.7, got %q", cfg.Agents.Defaults.Model)
		}
	})
}

func TestMergeConfig(t *testing.T) {
	t.Run("fills empty fields", func(t *testing.T) {
		existing := config.DefaultConfig()
		incoming := config.DefaultConfig()
		incoming.Providers.Anthropic.APIKey = "sk-ant-incoming"
		incoming.Providers.OpenRouter.APIKey = "sk-or-incoming"

		result := MergeConfig(existing, incoming)
		if result.Providers.Anthropic.APIKey != "sk-ant-incoming" {
			t.Errorf("Anthropic.APIKey = %q, want %q", result.Providers.Anthropic.APIKey, "sk-ant-incoming")
		}
		if result.Providers.OpenRouter.APIKey != "sk-or-incoming" {
			t.Errorf("OpenRouter.APIKey = %q, want %q", result.Providers.OpenRouter.APIKey, "sk-or-incoming")
		}
	})

	t.Run("preserves existing non-empty fields", func(t *testing.T) {
		existing := config.DefaultConfig()
		existing.Providers.Anthropic.APIKey = "sk-ant-existing"

		incoming := config.DefaultConfig()
		incoming.Providers.Anthropic.APIKey = "sk-ant-incoming"
		incoming.Providers.OpenAI.APIKey = "sk-oai-incoming"

		result := MergeConfig(existing, incoming)
		if result.Providers.Anthropic.APIKey != "sk-ant-existing" {
			t.Errorf("Anthropic.APIKey should be preserved, got %q", result.Providers.Anthropic.APIKey)
		}
		if result.Providers.OpenAI.APIKey != "sk-oai-incoming" {
			t.Errorf("OpenAI.APIKey should be filled, got %q", result.Providers.OpenAI.APIKey)
		}
	})

	t.Run("merges enabled channels", func(t *testing.T) {
		existing := config.DefaultConfig()
		incoming := config.DefaultConfig()
		incoming.Channels.Telegram.Enabled = true
		incoming.Channels.Telegram.Token = "tg-token"

		result := MergeConfig(existing, incoming)
		if !result.Channels.Telegram.Enabled {
			t.Error("Telegram should be enabled after merge")
		}
		if result.Channels.Telegram.Token != "tg-token" {
			t.Errorf("Telegram.Token = %q, want %q", result.Channels.Telegram.Token, "tg-token")
		}
	})

	t.Run("preserves existing enabled channels", func(t *testing.T) {
		existing := config.DefaultConfig()
		existing.Channels.Telegram.Enabled = true
		existing.Channels.Telegram.Token = "existing-token"

		incoming := config.DefaultConfig()
		incoming.Channels.Telegram.Enabled = true
		incoming.Channels.Telegram.Token = "incoming-token"

		result := MergeConfig(existing, incoming)
		if result.Channels.Telegram.Token != "existing-token" {
			t.Errorf("Telegram.Token should be preserved, got %q", result.Channels.Telegram.Token)
		}
	})
}

func TestPlanWorkspaceMigration(t *testing.T) {
	t.Run("copies available files", func(t *testing.T) {
		srcDir := t.TempDir()
		dstDir := t.TempDir()

		os.WriteFile(filepath.Join(srcDir, "AGENTS.md"), []byte("# Agents"), 0644)
		os.WriteFile(filepath.Join(srcDir, "SOUL.md"), []byte("# Soul"), 0644)
		os.WriteFile(filepath.Join(srcDir, "USER.md"), []byte("# User"), 0644)

		actions, err := PlanWorkspaceMigration(srcDir, dstDir, false)
		if err != nil {
			t.Fatalf("PlanWorkspaceMigration: %v", err)
		}

		copyCount := 0
		skipCount := 0
		for _, a := range actions {
			if a.Type == ActionCopy {
				copyCount++
			}
			if a.Type == ActionSkip {
				skipCount++
			}
		}
		if copyCount != 3 {
			t.Errorf("expected 3 copies, got %d", copyCount)
		}
		if skipCount != 2 {
			t.Errorf("expected 2 skips (TOOLS.md, HEARTBEAT.md), got %d", skipCount)
		}
	})

	t.Run("plans backup for existing destination files", func(t *testing.T) {
		srcDir := t.TempDir()
		dstDir := t.TempDir()

		os.WriteFile(filepath.Join(srcDir, "AGENTS.md"), []byte("# Agents from OpenClaw"), 0644)
		os.WriteFile(filepath.Join(dstDir, "AGENTS.md"), []byte("# Existing Agents"), 0644)

		actions, err := PlanWorkspaceMigration(srcDir, dstDir, false)
		if err != nil {
			t.Fatalf("PlanWorkspaceMigration: %v", err)
		}

		backupCount := 0
		for _, a := range actions {
			if a.Type == ActionBackup && filepath.Base(a.Destination) == "AGENTS.md" {
				backupCount++
			}
		}
		if backupCount != 1 {
			t.Errorf("expected 1 backup action for AGENTS.md, got %d", backupCount)
		}
	})

	t.Run("force skips backup", func(t *testing.T) {
		srcDir := t.TempDir()
		dstDir := t.TempDir()

		os.WriteFile(filepath.Join(srcDir, "AGENTS.md"), []byte("# Agents"), 0644)
		os.WriteFile(filepath.Join(dstDir, "AGENTS.md"), []byte("# Existing"), 0644)

		actions, err := PlanWorkspaceMigration(srcDir, dstDir, true)
		if err != nil {
			t.Fatalf("PlanWorkspaceMigration: %v", err)
		}

		for _, a := range actions {
			if a.Type == ActionBackup {
				t.Error("expected no backup actions with force=true")
			}
		}
	})

	t.Run("handles memory directory", func(t *testing.T) {
		srcDir := t.TempDir()
		dstDir := t.TempDir()

		memDir := filepath.Join(srcDir, "memory")
		os.MkdirAll(memDir, 0755)
		os.WriteFile(filepath.Join(memDir, "MEMORY.md"), []byte("# Memory"), 0644)

		actions, err := PlanWorkspaceMigration(srcDir, dstDir, false)
		if err != nil {
			t.Fatalf("PlanWorkspaceMigration: %v", err)
		}

		hasCopy := false
		hasDir := false
		for _, a := range actions {
			if a.Type == ActionCopy && filepath.Base(a.Source) == "MEMORY.md" {
				hasCopy = true
			}
			if a.Type == ActionCreateDir {
				hasDir = true
			}
		}
		if !hasCopy {
			t.Error("expected copy action for memory/MEMORY.md")
		}
		if !hasDir {
			t.Error("expected create dir action for memory/")
		}
	})

	t.Run("handles skills directory", func(t *testing.T) {
		srcDir := t.TempDir()
		dstDir := t.TempDir()

		skillDir := filepath.Join(srcDir, "skills", "weather")
		os.MkdirAll(skillDir, 0755)
		os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte("# Weather"), 0644)

		actions, err := PlanWorkspaceMigration(srcDir, dstDir, false)
		if err != nil {
			t.Fatalf("PlanWorkspaceMigration: %v", err)
		}

		hasCopy := false
		for _, a := range actions {
			if a.Type == ActionCopy && filepath.Base(a.Source) == "SKILL.md" {
				hasCopy = true
			}
		}
		if !hasCopy {
			t.Error("expected copy action for skills/weather/SKILL.md")
		}
	})
}

func TestFindOpenClawConfig(t *testing.T) {
	t.Run("finds openclaw.json", func(t *testing.T) {
		tmpDir := t.TempDir()
		configPath := filepath.Join(tmpDir, "openclaw.json")
		os.WriteFile(configPath, []byte("{}"), 0644)

		found, err := findOpenClawConfig(tmpDir)
		if err != nil {
			t.Fatalf("findOpenClawConfig: %v", err)
		}
		if found != configPath {
			t.Errorf("found %q, want %q", found, configPath)
		}
	})

	t.Run("falls back to config.json", func(t *testing.T) {
		tmpDir := t.TempDir()
		configPath := filepath.Join(tmpDir, "config.json")
		os.WriteFile(configPath, []byte("{}"), 0644)

		found, err := findOpenClawConfig(tmpDir)
		if err != nil {
			t.Fatalf("findOpenClawConfig: %v", err)
		}
		if found != configPath {
			t.Errorf("found %q, want %q", found, configPath)
		}
	})

	t.Run("prefers openclaw.json over config.json", func(t *testing.T) {
		tmpDir := t.TempDir()
		openclawPath := filepath.Join(tmpDir, "openclaw.json")
		os.WriteFile(openclawPath, []byte("{}"), 0644)
		os.WriteFile(filepath.Join(tmpDir, "config.json"), []byte("{}"), 0644)

		found, err := findOpenClawConfig(tmpDir)
		if err != nil {
			t.Fatalf("findOpenClawConfig: %v", err)
		}
		if found != openclawPath {
			t.Errorf("should prefer openclaw.json, got %q", found)
		}
	})

	t.Run("error when no config found", func(t *testing.T) {
		tmpDir := t.TempDir()

		_, err := findOpenClawConfig(tmpDir)
		if err == nil {
			t.Fatal("expected error when no config found")
		}
	})
}

func TestRewriteWorkspacePath(t *testing.T) {
	tests := []struct {
		name  string
		input string
		want  string
	}{
		{"default path", "~/.openclaw/workspace", "~/.picoclaw/workspace"},
		{"custom path", "/custom/path", "/custom/path"},
		{"empty", "", ""},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := rewriteWorkspacePath(tt.input)
			if got != tt.want {
				t.Errorf("rewriteWorkspacePath(%q) = %q, want %q", tt.input, got, tt.want)
			}
		})
	}
}

func TestRunDryRun(t *testing.T) {
	openclawHome := t.TempDir()
	picoClawHome := t.TempDir()

	wsDir := filepath.Join(openclawHome, "workspace")
	os.MkdirAll(wsDir, 0755)
	os.WriteFile(filepath.Join(wsDir, "SOUL.md"), []byte("# Soul"), 0644)
	os.WriteFile(filepath.Join(wsDir, "AGENTS.md"), []byte("# Agents"), 0644)

	configData := map[string]interface{}{
		"providers": map[string]interface{}{
			"anthropic": map[string]interface{}{
				"apiKey": "test-key",
			},
		},
	}
	data, _ := json.Marshal(configData)
	os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644)

	opts := Options{
		DryRun:       true,
		OpenClawHome: openclawHome,
		PicoClawHome: picoClawHome,
	}

	result, err := Run(opts)
	if err != nil {
		t.Fatalf("Run: %v", err)
	}

	picoWs := filepath.Join(picoClawHome, "workspace")
	if _, err := os.Stat(filepath.Join(picoWs, "SOUL.md")); !os.IsNotExist(err) {
		t.Error("dry run should not create files")
	}
	if _, err := os.Stat(filepath.Join(picoClawHome, "config.json")); !os.IsNotExist(err) {
		t.Error("dry run should not create config")
	}

	_ = result
}

func TestRunFullMigration(t *testing.T) {
	openclawHome := t.TempDir()
	picoClawHome := t.TempDir()

	wsDir := filepath.Join(openclawHome, "workspace")
	os.MkdirAll(wsDir, 0755)
	os.WriteFile(filepath.Join(wsDir, "SOUL.md"), []byte("# Soul from OpenClaw"), 0644)
	os.WriteFile(filepath.Join(wsDir, "AGENTS.md"), []byte("# Agents from OpenClaw"), 0644)
	os.WriteFile(filepath.Join(wsDir, "USER.md"), []byte("# User from OpenClaw"), 0644)

	memDir := filepath.Join(wsDir, "memory")
	os.MkdirAll(memDir, 0755)
	os.WriteFile(filepath.Join(memDir, "MEMORY.md"), []byte("# Memory notes"), 0644)

	configData := map[string]interface{}{
		"providers": map[string]interface{}{
			"anthropic": map[string]interface{}{
				"apiKey": "sk-ant-migrate-test",
			},
			"openrouter": map[string]interface{}{
				"apiKey": "sk-or-migrate-test",
			},
		},
		"channels": map[string]interface{}{
			"telegram": map[string]interface{}{
				"enabled": true,
				"token":   "tg-migrate-test",
			},
		},
	}
	data, _ := json.Marshal(configData)
	os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644)

	opts := Options{
		Force:        true,
		OpenClawHome: openclawHome,
		PicoClawHome: picoClawHome,
	}

	result, err := Run(opts)
	if err != nil {
		t.Fatalf("Run: %v", err)
	}

	picoWs := filepath.Join(picoClawHome, "workspace")

	soulData, err := os.ReadFile(filepath.Join(picoWs, "SOUL.md"))
	if err != nil {
		t.Fatalf("reading SOUL.md: %v", err)
	}
	if string(soulData) != "# Soul from OpenClaw" {
		t.Errorf("SOUL.md content = %q, want %q", string(soulData), "# Soul from OpenClaw")
	}

	agentsData, err := os.ReadFile(filepath.Join(picoWs, "AGENTS.md"))
	if err != nil {
		t.Fatalf("reading AGENTS.md: %v", err)
	}
	if string(agentsData) != "# Agents from OpenClaw" {
		t.Errorf("AGENTS.md content = %q", string(agentsData))
	}

	memData, err := os.ReadFile(filepath.Join(picoWs, "memory", "MEMORY.md"))
	if err != nil {
		t.Fatalf("reading memory/MEMORY.md: %v", err)
	}
	if string(memData) != "# Memory notes" {
		t.Errorf("MEMORY.md content = %q", string(memData))
	}

	picoConfig, err := config.LoadConfig(filepath.Join(picoClawHome, "config.json"))
	if err != nil {
		t.Fatalf("loading PicoClaw config: %v", err)
	}
	if picoConfig.Providers.Anthropic.APIKey != "sk-ant-migrate-test" {
		t.Errorf("Anthropic.APIKey = %q, want %q", picoConfig.Providers.Anthropic.APIKey, "sk-ant-migrate-test")
	}
	if picoConfig.Providers.OpenRouter.APIKey != "sk-or-migrate-test" {
		t.Errorf("OpenRouter.APIKey = %q, want %q", picoConfig.Providers.OpenRouter.APIKey, "sk-or-migrate-test")
	}
	if !picoConfig.Channels.Telegram.Enabled {
		t.Error("Telegram should be enabled")
	}
	if picoConfig.Channels.Telegram.Token != "tg-migrate-test" {
		t.Errorf("Telegram.Token = %q, want %q", picoConfig.Channels.Telegram.Token, "tg-migrate-test")
	}

	if result.FilesCopied < 3 {
		t.Errorf("expected at least 3 files copied, got %d", result.FilesCopied)
	}
	if !result.ConfigMigrated {
		t.Error("config should have been migrated")
	}
	if len(result.Errors) > 0 {
		t.Errorf("expected no errors, got %v", result.Errors)
	}
}

func TestRunOpenClawNotFound(t *testing.T) {
	opts := Options{
		OpenClawHome: "/nonexistent/path/to/openclaw",
		PicoClawHome: t.TempDir(),
	}

	_, err := Run(opts)
	if err == nil {
		t.Fatal("expected error when OpenClaw not found")
	}
}

func TestRunMutuallyExclusiveFlags(t *testing.T) {
	opts := Options{
		ConfigOnly:    true,
		WorkspaceOnly: true,
	}

	_, err := Run(opts)
	if err == nil {
		t.Fatal("expected error for mutually exclusive flags")
	}
}

func TestBackupFile(t *testing.T) {
	tmpDir := t.TempDir()
	filePath := filepath.Join(tmpDir, "test.md")
	os.WriteFile(filePath, []byte("original content"), 0644)

	if err := backupFile(filePath); err != nil {
		t.Fatalf("backupFile: %v", err)
	}

	bakPath := filePath + ".bak"
	bakData, err := os.ReadFile(bakPath)
	if err != nil {
		t.Fatalf("reading backup: %v", err)
	}
	if string(bakData) != "original content" {
		t.Errorf("backup content = %q, want %q", string(bakData), "original content")
	}
}

func TestCopyFile(t *testing.T) {
	tmpDir := t.TempDir()
	srcPath := filepath.Join(tmpDir, "src.md")
	dstPath := filepath.Join(tmpDir, "dst.md")

	os.WriteFile(srcPath, []byte("file content"), 0644)

	if err := copyFile(srcPath, dstPath); err != nil {
		t.Fatalf("copyFile: %v", err)
	}

	data, err := os.ReadFile(dstPath)
	if err != nil {
		t.Fatalf("reading copy: %v", err)
	}
	if string(data) != "file content" {
		t.Errorf("copy content = %q, want %q", string(data), "file content")
	}
}

func TestRunConfigOnly(t *testing.T) {
	openclawHome := t.TempDir()
	picoClawHome := t.TempDir()

	wsDir := filepath.Join(openclawHome, "workspace")
	os.MkdirAll(wsDir, 0755)
	os.WriteFile(filepath.Join(wsDir, "SOUL.md"), []byte("# Soul"), 0644)

	configData := map[string]interface{}{
		"providers": map[string]interface{}{
			"anthropic": map[string]interface{}{
				"apiKey": "sk-config-only",
			},
		},
	}
	data, _ := json.Marshal(configData)
	os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644)

	opts := Options{
		Force:        true,
		ConfigOnly:   true,
		OpenClawHome: openclawHome,
		PicoClawHome: picoClawHome,
	}

	result, err := Run(opts)
	if err != nil {
		t.Fatalf("Run: %v", err)
	}

	if !result.ConfigMigrated {
		t.Error("config should have been migrated")
	}

	picoWs := filepath.Join(picoClawHome, "workspace")
	if _, err := os.Stat(filepath.Join(picoWs, "SOUL.md")); !os.IsNotExist(err) {
		t.Error("config-only should not copy workspace files")
	}
}

func TestRunWorkspaceOnly(t *testing.T) {
	openclawHome := t.TempDir()
	picoClawHome := t.TempDir()

	wsDir := filepath.Join(openclawHome, "workspace")
	os.MkdirAll(wsDir, 0755)
	os.WriteFile(filepath.Join(wsDir, "SOUL.md"), []byte("# Soul"), 0644)

	configData := map[string]interface{}{
		"providers": map[string]interface{}{
			"anthropic": map[string]interface{}{
				"apiKey": "sk-ws-only",
			},
		},
	}
	data, _ := json.Marshal(configData)
	os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644)

	opts := Options{
		Force:         true,
		WorkspaceOnly: true,
		OpenClawHome:  openclawHome,
		PicoClawHome:  picoClawHome,
	}

	result, err := Run(opts)
	if err != nil {
		t.Fatalf("Run: %v", err)
	}

	if result.ConfigMigrated {
		t.Error("workspace-only should not migrate config")
	}

	picoWs := filepath.Join(picoClawHome, "workspace")
	soulData, err := os.ReadFile(filepath.Join(picoWs, "SOUL.md"))
	if err != nil {
		t.Fatalf("reading SOUL.md: %v", err)
	}
	if string(soulData) != "# Soul" {
		t.Errorf("SOUL.md content = %q", string(soulData))
	}
}
