package cli

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"gitcode-mcp/internal/cache"
	"gitcode-mcp/internal/config"
	"gitcode-mcp/internal/rag"
)

type cliConfigSource struct {
	env       map[string]string
	homeDir   string
	configDir string
	cacheDir  string
}

func newCLIConfigSource(t *testing.T) *cliConfigSource {
	t.Helper()
	root := t.TempDir()
	return &cliConfigSource{
		env:       map[string]string{},
		homeDir:   filepath.Join(root, "home"),
		configDir: filepath.Join(root, "config"),
		cacheDir:  filepath.Join(root, "cache"),
	}
}

func (s *cliConfigSource) Env(key string) string          { return s.env[key] }
func (s *cliConfigSource) UserHomeDir() (string, error)   { return s.homeDir, nil }
func (s *cliConfigSource) UserConfigDir() (string, error) { return s.configDir, nil }
func (s *cliConfigSource) UserCacheDir() (string, error)  { return s.cacheDir, nil }
func (s *cliConfigSource) ReadFile(path string) ([]byte, error) {
	return os.ReadFile(path)
}

type statusReporter struct{ status config.CredentialStatus }

func (r statusReporter) Resolve(context.Context, config.EffectiveConfig) (config.SecretString, config.CredentialStatus, error) {
	if r.status.Present {
		return config.NewSecretString("test-token"), r.status, nil
	}
	return config.SecretString{}, r.status, nil
}

func (r statusReporter) Status(context.Context, config.EffectiveConfig) config.CredentialStatus {
	return r.status
}

type cliRAGRuntime struct {
	executablePath string
	live           bool
	models         []string
	pullCalls      int
	smokeCalls     int
}

func (r *cliRAGRuntime) LookPath(string) (string, error) {
	if r.executablePath == "" {
		return "", errors.New("not found")
	}
	return r.executablePath, nil
}
func (r *cliRAGRuntime) IsLive(context.Context, string, time.Duration) (bool, string) {
	if r.live {
		return true, ""
	}
	return false, "not live"
}
func (r *cliRAGRuntime) ListModels(context.Context, string, time.Duration) ([]string, error) {
	return append([]string(nil), r.models...), nil
}
func (r *cliRAGRuntime) PullModel(_ context.Context, _, model string, _ time.Duration) error {
	r.pullCalls++
	r.models = append(r.models, model)
	return nil
}
func (r *cliRAGRuntime) EmbeddingSmoke(context.Context, string, string, time.Duration) error {
	r.smokeCalls++
	return nil
}
func (r *cliRAGRuntime) Start(context.Context, config.RAGProviderConfig) (string, error) {
	r.live = true
	return "started", nil
}

var _ rag.Runtime = (*cliRAGRuntime)(nil)

func TestConfigAuthCommandsRedactedUX(t *testing.T) {
	t.Run("SCN-CONFIG-INIT-YAML-ONLY", func(t *testing.T) {
		src := newCLIConfigSource(t)
		path := filepath.Join(t.TempDir(), "config.yaml")
		src.env[config.EnvMCPConfigPath] = path
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"config", "init"}, &stdout, &stderr, nil, localCommandDeps{Source: src})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		if !strings.Contains(stdout.String(), "config_format: yaml") {
			t.Fatalf("missing yaml output: %q", stdout.String())
		}
		if _, err := os.Stat(path); err != nil {
			t.Fatalf("config not written: %v", err)
		}
		if _, err := os.Stat(strings.TrimSuffix(path, ".yaml") + ".json"); !os.IsNotExist(err) {
			t.Fatalf("json config should not be written")
		}
		stdout.Reset()
		stderr.Reset()
		if code := executeWithFactoryAndDeps([]string{"config", "init"}, &stdout, &stderr, nil, localCommandDeps{Source: src}); code == 0 {
			t.Fatalf("overwrite without flag succeeded")
		}
	})

	t.Run("SCN-CONFIG-LOCATE-GITCODE-MCP-CONFIG", func(t *testing.T) {
		src := newCLIConfigSource(t)
		path := filepath.Join(t.TempDir(), "active.yaml")
		if err := os.WriteFile(path, []byte("gitcode_base_url: https://example.invalid\n"), 0o600); err != nil {
			t.Fatal(err)
		}
		src.env[config.EnvMCPConfigPath] = path
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"config", "locate"}, &stdout, &stderr, nil, localCommandDeps{Source: src})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		for _, want := range []string{path, "config_source: explicit-yaml", "config_format: yaml", "config_exists: true"} {
			if !strings.Contains(stdout.String(), want) {
				t.Fatalf("locate missing %q in %q", want, stdout.String())
			}
		}
	})

	t.Run("SCN-CONFIG-ENV-OVERRIDES-WIN", func(t *testing.T) {
		src := newCLIConfigSource(t)
		path := filepath.Join(t.TempDir(), "active.yaml")
		secret := "file-contained-secret"
		if err := os.WriteFile(path, []byte("cache_path: /tmp/file-cache.db\ngitcode_base_url: "+secret+"\ncredential:\n  store: env\n"), 0o600); err != nil {
			t.Fatal(err)
		}
		src.env[config.EnvMCPConfigPath] = path
		src.env[config.EnvMCPCacheDir] = filepath.Join(t.TempDir(), "cache-dir")
		src.env[config.EnvAPIURL] = "https://api.example.invalid"
		src.env[config.EnvToken] = "secret-token-value"
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"config", "show", "--redacted"}, &stdout, &stderr, nil, localCommandDeps{Source: src})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String() + stderr.String()
		for _, want := range []string{"config_source: explicit-yaml", "cache_path_source: env:GITCODE_MCP_CACHE_DIR", "gitcode_base_url_source: env:GITCODE_API_URL", "credential_store_mode: env", "token_present: true"} {
			if !strings.Contains(out, want) {
				t.Fatalf("show missing %q in %q", want, out)
			}
		}
		for _, forbidden := range []string{"secret-token-value", secret} {
			if strings.Contains(out, forbidden) {
				t.Fatalf("leaked %q in %q", forbidden, out)
			}
		}
	})

	t.Run("SCN-AUTH-KEYRING-UNAVAILABLE", func(t *testing.T) {
		src := newCLIConfigSource(t)
		rawErr := "raw dbus failure details"
		reporter := statusReporter{status: config.CredentialStatus{Source: "keyring", Present: false, StoreMode: "auto", ErrorClass: "credential-store-unavailable", Remediation: "Use GITCODE_TOKEN or credential.store env."}}
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"auth", "status"}, &stdout, &stderr, nil, localCommandDeps{Source: src, CredentialReporter: reporter})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String() + stderr.String()
		for _, want := range []string{"credential_source: keyring", "token_present: false", "credential_error_class: credential-store-unavailable", "remediation:"} {
			if !strings.Contains(out, want) {
				t.Fatalf("auth missing %q in %q", want, out)
			}
		}
		if strings.Contains(out, rawErr) {
			t.Fatalf("raw provider error leaked: %q", out)
		}
	})

	t.Run("SCN-AUTH-STATUS-ENV", func(t *testing.T) {
		src := newCLIConfigSource(t)
		src.env[config.EnvToken] = "secret-token-value"
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"auth", "status"}, &stdout, &stderr, nil, localCommandDeps{Source: src})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String() + stderr.String()
		for _, want := range []string{"credential_source: env:GITCODE_TOKEN", "token_present: true", "available_sources: env:GITCODE_TOKEN"} {
			if !strings.Contains(out, want) {
				t.Fatalf("auth missing %q in %q", want, out)
			}
		}
		if strings.Contains(out, "secret-token-value") {
			t.Fatalf("auth status leaked token: %q", out)
		}
	})

	t.Run("SCN-AUTH-STATUS-REDACTS-DIAGNOSTIC-SURFACES", func(t *testing.T) {
		src := newCLIConfigSource(t)
		src.env[config.EnvToken] = "secret-token-value"
		src.env["GITCODE_E2E_OWNER"] = "private-owner"
		src.env["GITCODE_E2E_REPO"] = "private-repo"
		reporter := statusReporter{status: config.CredentialStatus{Source: "env:GITCODE_TOKEN", Present: true, StoreMode: "env", Remediation: "Authorization: Bearer secret-token-value for private-owner/private-repo"}}
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"auth", "status"}, &stdout, &stderr, nil, localCommandDeps{Source: src, CredentialReporter: reporter})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String() + stderr.String()
		for _, forbidden := range []string{"secret-token-value", "private-owner", "private-repo", "Bearer secret-token-value"} {
			if strings.Contains(out, forbidden) {
				t.Fatalf("auth status leaked %q: %q", forbidden, out)
			}
		}
		if !strings.Contains(out, "[REDACTED]") {
			t.Fatalf("auth status missing redaction marker: %q", out)
		}
	})

	t.Run("SCN-AUTH-STATUS-NO-TOKEN", func(t *testing.T) {
		src := newCLIConfigSource(t)
		reporter := statusReporter{status: config.CredentialStatus{Source: "missing", Present: false, StoreMode: "auto", ErrorClass: "token-missing", AvailableSources: []string{"env:GITCODE_TOKEN", "keyring", "none"}}}
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"auth", "status"}, &stdout, &stderr, nil, localCommandDeps{Source: src, CredentialReporter: reporter})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String() + stderr.String()
		for _, want := range []string{"credential_source: missing", "token_present: false", "available_sources:", "env:GITCODE_TOKEN", "keyring", "credential_error_class: token-missing"} {
			if !strings.Contains(out, want) {
				t.Fatalf("auth missing %q in %q", want, out)
			}
		}
	})

	t.Run("SCN-AUTH-STATUS-JSON", func(t *testing.T) {
		src := newCLIConfigSource(t)
		src.env[config.EnvToken] = "secret-token-value"
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"auth", "status", "--format", "json"}, &stdout, &stderr, nil, localCommandDeps{Source: src})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String() + stderr.String()
		for _, want := range []string{"\"source\": \"env:GITCODE_TOKEN\"", "\"present\": true", "\"available_sources\""} {
			if !strings.Contains(out, want) {
				t.Fatalf("auth json missing %q in %q", want, out)
			}
		}
		if strings.Contains(out, "secret-token-value") {
			t.Fatalf("auth json leaked token: %q", out)
		}
	})
}

func TestRAGSetupCommand(t *testing.T) {
	t.Run("SCN-RAG-SETUP-DRY-RUN-MISSING-MODEL", func(t *testing.T) {
		src := newCLIConfigSource(t)
		runtime := &cliRAGRuntime{executablePath: "/usr/local/bin/ollama", live: true}
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"rag", "setup", "--dry-run"}, &stdout, &stderr, nil, localCommandDeps{Source: src, RAGRuntime: runtime})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String()
		for _, want := range []string{"status: missing_model", "profile: qwen3-ollama-0_6b-1024", "provider: ollama", "actions: run gitcode-mcp rag setup --yes"} {
			if !strings.Contains(out, want) {
				t.Fatalf("rag setup output missing %q in %q", want, out)
			}
		}
		if runtime.pullCalls != 0 || runtime.smokeCalls != 0 {
			t.Fatalf("dry-run mutated runtime: %#v", runtime)
		}
	})

	t.Run("SCN-RAG-SETUP-YES-PULLS-AND-SMOKES", func(t *testing.T) {
		src := newCLIConfigSource(t)
		runtime := &cliRAGRuntime{executablePath: "/usr/local/bin/ollama", live: true}
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"rag", "setup", "--yes"}, &stdout, &stderr, nil, localCommandDeps{Source: src, RAGRuntime: runtime})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q stdout=%q", code, stderr.String(), stdout.String())
		}
		if runtime.pullCalls != 1 || runtime.smokeCalls != 1 || !strings.Contains(stdout.String(), "status: ready") {
			t.Fatalf("runtime=%#v stdout=%q", runtime, stdout.String())
		}
		if !strings.Contains(stderr.String(), "pulling model") {
			t.Fatalf("stderr=%q", stderr.String())
		}
		if !strings.Contains(stdout.String(), "next_actions:") || !strings.Contains(stdout.String(), "--repo OWNER/REPO") {
			t.Fatalf("stdout=%q", stdout.String())
		}
	})

	t.Run("SCN-RAG-SETUP-JSON-REMAINS-SINGLE-DOCUMENT", func(t *testing.T) {
		src := newCLIConfigSource(t)
		runtime := &cliRAGRuntime{executablePath: "/usr/local/bin/ollama", live: true}
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"rag", "setup", "--yes", "--format", "json"}, &stdout, &stderr, nil, localCommandDeps{Source: src, RAGRuntime: runtime})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q stdout=%q", code, stderr.String(), stdout.String())
		}
		if stderr.Len() != 0 {
			t.Fatalf("json progress leaked to stderr: %q", stderr.String())
		}
		var payload map[string]any
		if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil {
			t.Fatalf("invalid json: %v output=%q", err, stdout.String())
		}
		if payload["status"] != "ready" {
			t.Fatalf("payload=%#v", payload)
		}
	})
}

func TestRuntimeAuditDoctorCommand(t *testing.T) {
	t.Run("SCN-RUNTIME-AUDIT-CLI-TEXT", func(t *testing.T) {
		src := newCLIConfigSource(t)
		path := filepath.Join(t.TempDir(), "active.yaml")
		if err := os.WriteFile(path, []byte("cache_path: /tmp/runtime-cache.db\ncredential:\n  store: env\n"), 0o600); err != nil {
			t.Fatal(err)
		}
		src.env[config.EnvMCPConfigPath] = path
		src.env[config.EnvToken] = "secret-token-value"
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"doctor", "--runtime-audit", "--repo", "fixture-repo"}, &stdout, &stderr, nil, localCommandDeps{Source: src})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String() + stderr.String()
		for _, want := range []string{"repo_id: fixture-repo", "config:", "version: 0.1.0", "config_source: explicit-yaml", "config_format: yaml", "config_exists: true", "cache_path: /tmp/runtime-cache.db", "credential_source: env:GITCODE_TOKEN", "token_present: true", "handoff_fields:", "cache: not_reported_by_owner", "repo: not_reported_by_owner", "mcp: not_reported_by_owner", "index: not_reported_by_owner"} {
			if !strings.Contains(out, want) {
				t.Fatalf("doctor output missing %q in %q", want, out)
			}
		}
		if strings.Contains(out, "secret-token-value") || strings.Contains(out, "cache: ok") || strings.Contains(out, "repo: ok") || strings.Contains(out, "mcp: ok") || strings.Contains(out, "index: ok") {
			t.Fatalf("doctor output leaked or synthesized success: %q", out)
		}
	})

	t.Run("SCN-RUNTIME-AUDIT-CLI-JSON", func(t *testing.T) {
		src := newCLIConfigSource(t)
		reporter := statusReporter{status: config.CredentialStatus{Source: "keyring", Present: false, StoreMode: "auto", ErrorClass: "credential-store-unavailable", Remediation: "Use GITCODE_TOKEN or credential.store env."}}
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"doctor", "--runtime-audit", "--repo", "fixture-repo", "--format", "json"}, &stdout, &stderr, nil, localCommandDeps{Source: src, CredentialReporter: reporter})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String()
		for _, want := range []string{"\"repo_id\": \"fixture-repo\"", "\"config\"", "\"handoff_fields\"", "\"credential-store-unavailable\"", "\"token_present\": false"} {
			if !strings.Contains(out, want) {
				t.Fatalf("doctor json missing %q in %q", want, out)
			}
		}
		for _, forbidden := range []string{"\"cache\":", "\"repo\":", "\"mcp\":", "\"index\":", "raw dbus failure details"} {
			if strings.Contains(out, forbidden) {
				t.Fatalf("doctor json contained forbidden %q in %q", forbidden, out)
			}
		}
	})
}

func TestConfigCommandDoesNotOpenService(t *testing.T) {
	src := newCLIConfigSource(t)
	called := false
	factory := func(context.Context, string) (queryService, func() error, error) {
		called = true
		return nil, nil, nil
	}
	var stdout, stderr bytes.Buffer
	code := executeWithFactoryAndDeps([]string{"auth", "status"}, &stdout, &stderr, factory, localCommandDeps{Source: src})
	if code != 0 {
		t.Fatalf("code=%d stderr=%q", code, stderr.String())
	}
	stdout.Reset()
	stderr.Reset()
	code = executeWithFactoryAndDeps([]string{"doctor", "--runtime-audit", "--repo", "fixture-repo"}, &stdout, &stderr, factory, localCommandDeps{Source: src})
	if code != 0 {
		t.Fatalf("doctor code=%d stderr=%q", code, stderr.String())
	}
	if called {
		t.Fatalf("local command opened service")
	}
}

func TestFeedbackStatusAndTrustedSetupFlow(t *testing.T) {
	src := newCLIConfigSource(t)
	configPath := filepath.Join(src.configDir, "gitcode-mcp", "config.yaml")
	cachePath := filepath.Join(src.cacheDir, "gitcode-mcp", "cache.db")
	if err := os.MkdirAll(filepath.Dir(configPath), 0o700); err != nil {
		t.Fatal(err)
	}
	if err := os.MkdirAll(filepath.Dir(cachePath), 0o700); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(configPath, []byte("# retained operator note\ncache_path: "+cachePath+"\nformat: text\n"), 0o600); err != nil {
		t.Fatal(err)
	}
	src.env[config.EnvMCPConfigPath] = configPath
	store, err := cache.NewSQLiteStore(context.Background(), cachePath)
	if err != nil {
		t.Fatal(err)
	}
	if err := store.AddRepository(context.Background(), cache.RepositoryBinding{RepoID: "example/feedback", Owner: "example", Name: "feedback", APIBaseURL: "https://api.gitcode.com/api/v5", Scopes: []cache.RepositoryScope{cache.RepositoryScopeIssues}}); err != nil {
		t.Fatal(err)
	}
	_ = store.Close()
	reporter := statusReporter{status: config.CredentialStatus{Source: "keyring", Present: true, StoreMode: "auto"}}
	deps := localCommandDeps{Source: src, CredentialReporter: reporter}
	var stdout, stderr bytes.Buffer
	if code := executeWithFactoryAndDeps([]string{"feedback", "status", "--format", "json"}, &stdout, &stderr, nil, deps); code != 0 || !strings.Contains(stdout.String(), `"state": "disabled"`) || !strings.Contains(stdout.String(), `"prepare_available": true`) {
		t.Fatalf("status code=%d stdout=%s stderr=%s", code, stdout.String(), stderr.String())
	}
	stdout.Reset()
	stderr.Reset()
	if code := executeWithFactoryAndDeps([]string{"feedback", "setup", "--repo", "example/feedback", "--format", "json"}, &stdout, &stderr, nil, deps); code != 0 || !strings.Contains(stdout.String(), `"status": "confirmation_required"`) {
		t.Fatalf("plan code=%d stdout=%s stderr=%s", code, stdout.String(), stderr.String())
	}
	var renderedPlan config.FeedbackSetupPlan
	if err := json.Unmarshal(stdout.Bytes(), &renderedPlan); err != nil || renderedPlan.PlanID == "" {
		t.Fatalf("plan decode=%#v err=%v", renderedPlan, err)
	}
	beforeApply, _ := os.ReadFile(configPath)
	if strings.Contains(string(beforeApply), "feedback:") {
		t.Fatalf("plan mutated config: %s", beforeApply)
	}
	stdout.Reset()
	stderr.Reset()
	if code := executeWithFactoryAndDeps([]string{"feedback", "setup", "--repo", "example/feedback", "--yes", "--plan-id", renderedPlan.PlanID, "--idempotency-key", "feedback-setup-example", "--format", "json"}, &stdout, &stderr, nil, deps); code != 0 || !strings.Contains(stdout.String(), `"status": "configured"`) || !strings.Contains(stdout.String(), `"state": "ready"`) {
		t.Fatalf("apply code=%d stdout=%s stderr=%s", code, stdout.String(), stderr.String())
	}
	after, err := os.ReadFile(configPath)
	if err != nil || !strings.Contains(string(after), "# retained operator note") || !strings.Contains(string(after), "repo_id: example/feedback") {
		t.Fatalf("config=%s err=%v", after, err)
	}
	stdout.Reset()
	stderr.Reset()
	replayPlan, err := config.PlanFeedbackSetup(src, "example/feedback")
	if err != nil {
		t.Fatal(err)
	}
	if code := executeWithFactoryAndDeps([]string{"feedback", "setup", "--repo", "example/feedback", "--yes", "--plan-id", replayPlan.PlanID, "--idempotency-key", "feedback-setup-example", "--format", "json"}, &stdout, &stderr, nil, deps); code != 0 || !strings.Contains(stdout.String(), `"status": "configured"`) || !strings.Contains(stdout.String(), `"replayed": true`) {
		t.Fatalf("replay code=%d stdout=%s stderr=%s", code, stdout.String(), stderr.String())
	}
	stdout.Reset()
	stderr.Reset()
	prepareArgs := []string{"feedback", "prepare", "--title", "Readiness remains consistent", "--category", "ux_friction", "--surface", "cli", "--reporter-type", "agent", "--observed", "status and prepare disagreed", "--expected", "one readiness state", "--impact", "agent selected the wrong handoff", "--format", "json"}
	if code := executeWithFactoryAndDeps(prepareArgs, &stdout, &stderr, nil, deps); code != 0 || !strings.Contains(stdout.String(), `"status": "prepared"`) || !strings.Contains(stdout.String(), `"configured": true`) || !strings.Contains(stdout.String(), `"state": "ready"`) {
		t.Fatalf("prepare readiness code=%d stdout=%s stderr=%s", code, stdout.String(), stderr.String())
	}
	stdout.Reset()
	stderr.Reset()
	if code := executeWithFactoryAndDeps([]string{"feedback", "--format", "json", "--offline", "status"}, &stdout, &stderr, nil, deps); code != 0 || !strings.Contains(stdout.String(), `"state": "provider_unavailable"`) {
		t.Fatalf("reordered offline status code=%d stdout=%s stderr=%s", code, stdout.String(), stderr.String())
	}
	stdout.Reset()
	stderr.Reset()
	missingCredentialDeps := deps
	missingCredentialDeps.CredentialReporter = statusReporter{status: config.CredentialStatus{Source: "none", Present: false}}
	if code := executeWithFactoryAndDeps([]string{"feedback", "status", "--format", "json"}, &stdout, &stderr, nil, missingCredentialDeps); code != 0 || !strings.Contains(stdout.String(), `"state": "credential_missing"`) {
		t.Fatalf("missing credential status code=%d stdout=%s stderr=%s", code, stdout.String(), stderr.String())
	}
}

func TestFeedbackSetupRejectsUnboundTargetAndWrongConfirmationWithoutMutation(t *testing.T) {
	src := newCLIConfigSource(t)
	configPath := filepath.Join(src.configDir, "gitcode-mcp", "config.yaml")
	cachePath := filepath.Join(src.cacheDir, "gitcode-mcp", "cache.db")
	if err := os.MkdirAll(filepath.Dir(configPath), 0o700); err != nil {
		t.Fatal(err)
	}
	if err := os.MkdirAll(filepath.Dir(cachePath), 0o700); err != nil {
		t.Fatal(err)
	}
	before := "cache_path: " + cachePath + "\nformat: text\n"
	if err := os.WriteFile(configPath, []byte(before), 0o600); err != nil {
		t.Fatal(err)
	}
	src.env[config.EnvMCPConfigPath] = configPath
	store, err := cache.NewSQLiteStore(context.Background(), cachePath)
	if err != nil {
		t.Fatal(err)
	}
	if err := store.AddRepository(context.Background(), cache.RepositoryBinding{RepoID: "example/bound", Owner: "example", Name: "bound"}); err != nil {
		t.Fatal(err)
	}
	_ = store.Close()
	deps := localCommandDeps{Source: src, CredentialReporter: statusReporter{status: config.CredentialStatus{Present: true}}}
	for _, args := range [][]string{
		{"feedback", "setup", "--repo", "example/unbound", "--format", "json"},
		{"feedback", "setup", "--repo", "example/bound", "--yes", "--plan-id", "wrong-plan", "--idempotency-key", "setup-bound", "--format", "json"},
	} {
		var stdout, stderr bytes.Buffer
		if code := executeWithFactoryAndDeps(args, &stdout, &stderr, nil, deps); code == 0 {
			t.Fatalf("args=%v unexpectedly succeeded: %s", args, stdout.String())
		}
		after, err := os.ReadFile(configPath)
		if err != nil || string(after) != before {
			t.Fatalf("args=%v mutated config: %q err=%v stderr=%s", args, after, err, stderr.String())
		}
	}
}

func TestDoctorCommandFull(t *testing.T) {
	t.Run("SCN-004-001-json-empty-cache", func(t *testing.T) {
		src := newCLIConfigSource(t)
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"doctor", "--format", "json"}, &stdout, &stderr, nil, localCommandDeps{Source: src})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String()
		for _, want := range []string{`"version"`, `"config"`, `"cache"`, `"credential"`, `"repo"`, `"sync"`, `"index"`, `"mcp"`, `"live_provider"`, `"auth_probe"`} {
			if !strings.Contains(out, want) {
				t.Fatalf("doctor json missing %q in %q", want, out)
			}
		}
	})

	t.Run("SCN-004-002-no-binding", func(t *testing.T) {
		src := newCLIConfigSource(t)
		reporter := statusReporter{status: config.CredentialStatus{Source: "missing", Present: false, StoreMode: "auto", ErrorClass: "token-missing", AvailableSources: []string{"env:GITCODE_TOKEN", "keyring", "none"}}}
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"doctor"}, &stdout, &stderr, nil, localCommandDeps{Source: src, CredentialReporter: reporter})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String() + stderr.String()
		if !strings.Contains(out, "no_repo_bound") {
			t.Fatalf("doctor missing no_repo_bound in %q", out)
		}
		if !strings.Contains(out, "bind_hint") {
			t.Fatalf("doctor missing bind_hint in %q", out)
		}
	})

	t.Run("SCN-004-003-no-token", func(t *testing.T) {
		src := newCLIConfigSource(t)
		reporter := statusReporter{status: config.CredentialStatus{Source: "missing", Present: false, StoreMode: "auto", ErrorClass: "token-missing", AvailableSources: []string{"env:GITCODE_TOKEN", "keyring", "none"}}}
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"doctor"}, &stdout, &stderr, nil, localCommandDeps{Source: src, CredentialReporter: reporter})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String() + stderr.String()
		if !strings.Contains(out, "no_token_configured") {
			t.Fatalf("doctor missing no_token_configured in %q", out)
		}
		if !strings.Contains(out, "available_sources") {
			t.Fatalf("doctor missing available_sources in %q", out)
		}
	})

	t.Run("SCN-004-004-token-redacted", func(t *testing.T) {
		src := newCLIConfigSource(t)
		src.env[config.EnvToken] = "secret-token-value"
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"doctor", "--format", "json"}, &stdout, &stderr, nil, localCommandDeps{Source: src})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String()
		if !strings.Contains(out, `"token_present": true`) {
			t.Fatalf("doctor json missing token_present in %q", out)
		}
		if !strings.Contains(out, `"status": "token_configured"`) {
			t.Fatalf("doctor json missing token_configured in %q", out)
		}
		if strings.Contains(out, "secret-token-value") {
			t.Fatalf("doctor leaked token value: %q", out)
		}
	})

	t.Run("SCN-004-005-runtime-audit-compat", func(t *testing.T) {
		src := newCLIConfigSource(t)
		src.env[config.EnvToken] = "secret-token-value"
		path := filepath.Join(t.TempDir(), "active.yaml")
		if err := os.WriteFile(path, []byte("cache_path: /tmp/runtime-cache.db\ncredential:\n  store: env\n"), 0o600); err != nil {
			t.Fatal(err)
		}
		src.env[config.EnvMCPConfigPath] = path
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"doctor", "--runtime-audit", "--repo", "fixture-repo"}, &stdout, &stderr, nil, localCommandDeps{Source: src})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String() + stderr.String()
		for _, want := range []string{"repo_id: fixture-repo", "version: 0.1.0", "token_present: true", "cache: not_reported_by_owner", "repo: not_reported_by_owner", "mcp: not_reported_by_owner", "index: not_reported_by_owner"} {
			if !strings.Contains(out, want) {
				t.Fatalf("doctor --runtime-audit missing %q in %q", want, out)
			}
		}
		if strings.Contains(out, "secret-token-value") {
			t.Fatalf("doctor --runtime-audit leaked token: %q", out)
		}
	})

	t.Run("SCN-004-006-full-text-output", func(t *testing.T) {
		src := newCLIConfigSource(t)
		src.env[config.EnvToken] = "secret-token-value"
		path := filepath.Join(t.TempDir(), "active.yaml")
		if err := os.WriteFile(path, []byte("cache_path: /tmp/full-doctor-cache.db\ncredential:\n  store: env\n"), 0o600); err != nil {
			t.Fatal(err)
		}
		src.env[config.EnvMCPConfigPath] = path
		var stdout, stderr bytes.Buffer
		code := executeWithFactoryAndDeps([]string{"doctor"}, &stdout, &stderr, nil, localCommandDeps{Source: src})
		if code != 0 {
			t.Fatalf("code=%d stderr=%q", code, stderr.String())
		}
		out := stdout.String() + stderr.String()
		for _, want := range []string{
			"version: 0.1.0",
			"config:",
			"cache:",
			"credential:",
			"repo:",
			"sync:",
			"index:",
			"mcp:",
			"live_provider:",
			"auth_probe:",
			"status:",
			"token_configured",
			"transport_stdio: supported",
			"transport_http: supported",
			"server_version: 0.1.0",
		} {
			if !strings.Contains(out, want) {
				t.Fatalf("doctor text missing %q in %q", want, out)
			}
		}
		if strings.Contains(out, "secret-token-value") {
			t.Fatalf("doctor leaked token: %q", out)
		}
	})
}