//go:build integration

package cmd

import (
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

// cleanGTEnv returns os.Environ() with all GT_* variables removed.
// This ensures tests don't inherit stale role environment from CI or previous tests.
func cleanGTEnv() []string {
	var clean []string
	for _, env := range os.Environ() {
		if !strings.HasPrefix(env, "GT_") {
			clean = append(clean, env)
		}
	}
	return clean
}

// TestRoleHomeE2E validates that gt role home returns correct paths
// for all role types after a full gt install.
func TestRoleHomeE2E(t *testing.T) {
	tmpDir := t.TempDir()
	hqPath := filepath.Join(tmpDir, "test-hq")
	gtBinary := buildGT(t)

	cmd := exec.Command(gtBinary, "install", hqPath, "--no-beads")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
	if output, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("gt install failed: %v\nOutput: %s", err, output)
	}

	rigName := "testrig"

	tests := []struct {
		name     string
		args     []string
		expected string
	}{
		{
			name:     "mayor",
			args:     []string{"role", "home", "mayor"},
			expected: filepath.Join(hqPath, "mayor"),
		},
		{
			name:     "deacon",
			args:     []string{"role", "home", "deacon"},
			expected: filepath.Join(hqPath, "deacon"),
		},
		{
			name:     "witness",
			args:     []string{"role", "home", "witness", "--rig", rigName},
			expected: filepath.Join(hqPath, rigName, "witness"),
		},
		{
			name:     "refinery",
			args:     []string{"role", "home", "refinery", "--rig", rigName},
			expected: filepath.Join(hqPath, rigName, "refinery", "rig"),
		},
		{
			name:     "polecat",
			args:     []string{"role", "home", "polecat", "--rig", rigName, "--polecat", "Toast"},
			expected: filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
		},
		{
			name:     "crew",
			args:     []string{"role", "home", "crew", "--rig", rigName, "--polecat", "worker1"},
			expected: filepath.Join(hqPath, rigName, "crew", "worker1", "rig"),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cmd := exec.Command(gtBinary, tt.args...)
			cmd.Dir = hqPath
			cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)

			// Use Output() to only capture stdout (warnings go to stderr)
			output, err := cmd.Output()
			if err != nil {
				t.Fatalf("gt %v failed: %v\nOutput: %s", tt.args, err, output)
			}

			got := strings.TrimSpace(string(output))
			if got != tt.expected {
				t.Errorf("gt %v = %q, want %q", tt.args, got, tt.expected)
			}
		})
	}
}

// TestRoleHomeMissingFlags validates that gt role home fails when required flags are missing.
func TestRoleHomeMissingFlags(t *testing.T) {
	tmpDir := t.TempDir()
	hqPath := filepath.Join(tmpDir, "test-hq")
	gtBinary := buildGT(t)

	cmd := exec.Command(gtBinary, "install", hqPath, "--no-beads")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
	if output, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("gt install failed: %v\nOutput: %s", err, output)
	}

	tests := []struct {
		name string
		args []string
	}{
		{
			name: "witness without --rig",
			args: []string{"role", "home", "witness"},
		},
		{
			name: "refinery without --rig",
			args: []string{"role", "home", "refinery"},
		},
		{
			name: "polecat without --rig",
			args: []string{"role", "home", "polecat", "--polecat", "Toast"},
		},
		{
			name: "polecat without --polecat",
			args: []string{"role", "home", "polecat", "--rig", "testrig"},
		},
		{
			name: "crew without --rig",
			args: []string{"role", "home", "crew", "--polecat", "worker1"},
		},
		{
			name: "crew without --polecat",
			args: []string{"role", "home", "crew", "--rig", "testrig"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cmd := exec.Command(gtBinary, tt.args...)
			cmd.Dir = hqPath
			// Use cleanGTEnv to ensure no stale GT_* vars affect the test
			cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)

			output, err := cmd.CombinedOutput()
			if err == nil {
				t.Errorf("gt %v should have failed but succeeded with output: %s", tt.args, output)
			}
		})
	}
}


// TestRoleHomeCwdDetection validates gt role home without arguments detects role from cwd.
func TestRoleHomeCwdDetection(t *testing.T) {
	tmpDir := t.TempDir()
	hqPath := filepath.Join(tmpDir, "test-hq")
	gtBinary := buildGT(t)

	cmd := exec.Command(gtBinary, "install", hqPath, "--no-beads")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
	if output, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("gt install failed: %v\nOutput: %s", err, output)
	}

	rigName := "testrig"

	// Create rig directory structure for cwd detection
	dirs := []string{
		filepath.Join(hqPath, rigName, "witness"),
		filepath.Join(hqPath, rigName, "refinery", "rig"),
		filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
		filepath.Join(hqPath, rigName, "crew", "worker1", "rig"),
	}
	for _, dir := range dirs {
		if err := os.MkdirAll(dir, 0755); err != nil {
			t.Fatalf("mkdir %s: %v", dir, err)
		}
	}

	tests := []struct {
		name     string
		cwd      string
		expected string
	}{
		{
			name:     "mayor from mayor dir",
			cwd:      filepath.Join(hqPath, "mayor"),
			expected: filepath.Join(hqPath, "mayor"),
		},
		{
			name:     "deacon from deacon dir",
			cwd:      filepath.Join(hqPath, "deacon"),
			expected: filepath.Join(hqPath, "deacon"),
		},
		{
			name:     "witness from witness dir",
			cwd:      filepath.Join(hqPath, rigName, "witness"),
			expected: filepath.Join(hqPath, rigName, "witness"),
		},
		{
			name:     "refinery from refinery/rig dir",
			cwd:      filepath.Join(hqPath, rigName, "refinery", "rig"),
			expected: filepath.Join(hqPath, rigName, "refinery", "rig"),
		},
		{
			name:     "polecat from polecats/Toast/rig dir",
			cwd:      filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
			expected: filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
		},
		{
			name:     "crew from crew/worker1/rig dir",
			cwd:      filepath.Join(hqPath, rigName, "crew", "worker1", "rig"),
			expected: filepath.Join(hqPath, rigName, "crew", "worker1", "rig"),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cmd := exec.Command(gtBinary, "role", "home")
			cmd.Dir = tt.cwd
			cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)

			output, err := cmd.CombinedOutput()
			if err != nil {
				t.Fatalf("gt role home failed: %v\nOutput: %s", err, output)
			}

			got := strings.TrimSpace(string(output))
			if got != tt.expected {
				t.Errorf("gt role home = %q, want %q", got, tt.expected)
			}
		})
	}
}

// TestRoleEnvCwdDetection validates gt role env without arguments detects role from cwd.
func TestRoleEnvCwdDetection(t *testing.T) {
	tmpDir := t.TempDir()
	hqPath := filepath.Join(tmpDir, "test-hq")
	gtBinary := buildGT(t)

	cmd := exec.Command(gtBinary, "install", hqPath, "--no-beads")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
	if output, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("gt install failed: %v\nOutput: %s", err, output)
	}

	rigName := "testrig"

	// Create rig directory structure for cwd detection
	dirs := []string{
		filepath.Join(hqPath, rigName, "witness"),
		filepath.Join(hqPath, rigName, "refinery", "rig"),
		filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
		filepath.Join(hqPath, rigName, "crew", "worker1", "rig"),
	}
	for _, dir := range dirs {
		if err := os.MkdirAll(dir, 0755); err != nil {
			t.Fatalf("mkdir %s: %v", dir, err)
		}
	}

	tests := []struct {
		name string
		cwd  string
		want []string
	}{
		{
			name: "mayor from mayor dir",
			cwd:  filepath.Join(hqPath, "mayor"),
			want: []string{
				"export GT_ROLE=mayor",
				"export GT_ROLE_HOME=" + filepath.Join(hqPath, "mayor"),
			},
		},
		{
			name: "deacon from deacon dir",
			cwd:  filepath.Join(hqPath, "deacon"),
			want: []string{
				"export GT_ROLE=deacon",
				"export GT_ROLE_HOME=" + filepath.Join(hqPath, "deacon"),
			},
		},
		{
			name: "witness from witness dir",
			cwd:  filepath.Join(hqPath, rigName, "witness"),
			want: []string{
				"export GT_ROLE=witness",
				"export GT_RIG=" + rigName,
				"export BD_ACTOR=" + rigName + "/witness",
				"export GT_ROLE_HOME=" + filepath.Join(hqPath, rigName, "witness"),
			},
		},
		{
			name: "refinery from refinery/rig dir",
			cwd:  filepath.Join(hqPath, rigName, "refinery", "rig"),
			want: []string{
				"export GT_ROLE=refinery",
				"export GT_RIG=" + rigName,
				"export BD_ACTOR=" + rigName + "/refinery",
				"export GT_ROLE_HOME=" + filepath.Join(hqPath, rigName, "refinery", "rig"),
			},
		},
		{
			name: "polecat from polecats/Toast/rig dir",
			cwd:  filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
			want: []string{
				"export GT_ROLE=polecat",
				"export GT_RIG=" + rigName,
				"export GT_POLECAT=Toast",
				"export BD_ACTOR=" + rigName + "/polecats/Toast",
				"export GT_ROLE_HOME=" + filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
			},
		},
		{
			name: "crew from crew/worker1/rig dir",
			cwd:  filepath.Join(hqPath, rigName, "crew", "worker1", "rig"),
			want: []string{
				"export GT_ROLE=crew",
				"export GT_RIG=" + rigName,
				"export GT_CREW=worker1",
				"export BD_ACTOR=" + rigName + "/crew/worker1",
				"export GT_ROLE_HOME=" + filepath.Join(hqPath, rigName, "crew", "worker1", "rig"),
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cmd := exec.Command(gtBinary, "role", "env")
			cmd.Dir = tt.cwd
			cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)

			output, err := cmd.CombinedOutput()
			if err != nil {
				t.Fatalf("gt role env failed: %v\nOutput: %s", err, output)
			}

			got := string(output)
			for _, w := range tt.want {
				if !strings.Contains(got, w) {
					t.Errorf("output missing %q\ngot: %s", w, got)
				}
			}
		})
	}
}

// TestRoleListE2E validates gt role list shows all roles.
func TestRoleListE2E(t *testing.T) {
	tmpDir := t.TempDir()
	hqPath := filepath.Join(tmpDir, "test-hq")
	gtBinary := buildGT(t)

	cmd := exec.Command(gtBinary, "install", hqPath, "--no-beads")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
	if output, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("gt install failed: %v\nOutput: %s", err, output)
	}

	cmd = exec.Command(gtBinary, "role", "list")
	cmd.Dir = hqPath
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)

	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("gt role list failed: %v\nOutput: %s", err, output)
	}

	got := string(output)

	// Check header
	if !strings.Contains(got, "Available roles:") {
		t.Errorf("output missing 'Available roles:' header\ngot: %s", got)
	}

	// Check all roles are listed
	roles := []string{"mayor", "deacon", "witness", "refinery", "polecat", "crew"}
	for _, role := range roles {
		if !strings.Contains(got, role) {
			t.Errorf("output missing role %q\ngot: %s", role, got)
		}
	}
}

// TestRoleShowE2E validates gt role show displays correct role info.
func TestRoleShowE2E(t *testing.T) {
	tmpDir := t.TempDir()
	hqPath := filepath.Join(tmpDir, "test-hq")
	gtBinary := buildGT(t)

	cmd := exec.Command(gtBinary, "install", hqPath, "--no-beads")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
	if output, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("gt install failed: %v\nOutput: %s", err, output)
	}

	rigName := "testrig"

	// Create rig directory structure for cwd detection
	dirs := []string{
		filepath.Join(hqPath, rigName, "witness"),
		filepath.Join(hqPath, rigName, "refinery", "rig"),
		filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
		filepath.Join(hqPath, rigName, "crew", "worker1", "rig"),
	}
	for _, dir := range dirs {
		if err := os.MkdirAll(dir, 0755); err != nil {
			t.Fatalf("mkdir %s: %v", dir, err)
		}
	}

	tests := []struct {
		name       string
		cwd        string
		wantRole   string
		wantSource string
		wantHome   string
		wantRig    string
		wantWorker string
	}{
		{
			name:       "mayor from mayor dir",
			cwd:        filepath.Join(hqPath, "mayor"),
			wantRole:   "mayor",
			wantSource: "cwd",
			wantHome:   filepath.Join(hqPath, "mayor"),
		},
		{
			name:       "deacon from deacon dir",
			cwd:        filepath.Join(hqPath, "deacon"),
			wantRole:   "deacon",
			wantSource: "cwd",
			wantHome:   filepath.Join(hqPath, "deacon"),
		},
		{
			name:       "witness from witness dir",
			cwd:        filepath.Join(hqPath, rigName, "witness"),
			wantRole:   "witness",
			wantSource: "cwd",
			wantHome:   filepath.Join(hqPath, rigName, "witness"),
			wantRig:    rigName,
		},
		{
			name:       "polecat from polecats/Toast/rig dir",
			cwd:        filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
			wantRole:   "polecat",
			wantSource: "cwd",
			wantHome:   filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
			wantRig:    rigName,
			wantWorker: "Toast",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cmd := exec.Command(gtBinary, "role", "show")
			cmd.Dir = tt.cwd
			cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)

			output, err := cmd.CombinedOutput()
			if err != nil {
				t.Fatalf("gt role show failed: %v\nOutput: %s", err, output)
			}

			got := string(output)

			if !strings.Contains(got, tt.wantRole) {
				t.Errorf("output missing role %q\ngot: %s", tt.wantRole, got)
			}

			if !strings.Contains(got, "Source: "+tt.wantSource) {
				t.Errorf("output missing 'Source: %s'\ngot: %s", tt.wantSource, got)
			}

			if !strings.Contains(got, "Home: "+tt.wantHome) {
				t.Errorf("output missing 'Home: %s'\ngot: %s", tt.wantHome, got)
			}

			if tt.wantRig != "" {
				if !strings.Contains(got, "Rig: "+tt.wantRig) {
					t.Errorf("output missing 'Rig: %s'\ngot: %s", tt.wantRig, got)
				}
			}

			if tt.wantWorker != "" {
				if !strings.Contains(got, "Worker: "+tt.wantWorker) {
					t.Errorf("output missing 'Worker: %s'\ngot: %s", tt.wantWorker, got)
				}
			}
		})
	}
}

// TestRoleShowMismatch validates gt role show displays mismatch warning.
func TestRoleShowMismatch(t *testing.T) {
	tmpDir := t.TempDir()
	hqPath := filepath.Join(tmpDir, "test-hq")
	gtBinary := buildGT(t)

	cmd := exec.Command(gtBinary, "install", hqPath, "--no-beads")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
	if output, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("gt install failed: %v\nOutput: %s", err, output)
	}

	// Run from mayor dir but set GT_ROLE to deacon
	cmd = exec.Command(gtBinary, "role", "show")
	cmd.Dir = filepath.Join(hqPath, "mayor")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir, "GT_ROLE=deacon")

	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("gt role show failed: %v\nOutput: %s", err, output)
	}

	got := string(output)

	// GT_ROLE takes precedence, so role should be deacon
	if !strings.Contains(got, "deacon") {
		t.Errorf("should show 'deacon' from GT_ROLE, got: %s", got)
	}

	// Source should be env
	if !strings.Contains(got, "Source: env") {
		t.Errorf("source should be 'env', got: %s", got)
	}

	// Should show mismatch warning
	if !strings.Contains(got, "ROLE MISMATCH") {
		t.Errorf("should show ROLE MISMATCH warning\ngot: %s", got)
	}

	// Should show both the env value and cwd suggestion
	if !strings.Contains(got, "GT_ROLE=deacon") {
		t.Errorf("should show GT_ROLE value\ngot: %s", got)
	}

	if !strings.Contains(got, "cwd suggests: mayor") {
		t.Errorf("should show cwd suggestion\ngot: %s", got)
	}
}

// TestRoleDetectE2E validates gt role detect uses cwd and ignores GT_ROLE.
func TestRoleDetectE2E(t *testing.T) {
	tmpDir := t.TempDir()
	hqPath := filepath.Join(tmpDir, "test-hq")
	gtBinary := buildGT(t)

	cmd := exec.Command(gtBinary, "install", hqPath, "--no-beads")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
	if output, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("gt install failed: %v\nOutput: %s", err, output)
	}

	rigName := "testrig"

	// Create rig directory structure for cwd detection
	dirs := []string{
		filepath.Join(hqPath, rigName, "witness"),
		filepath.Join(hqPath, rigName, "refinery", "rig"),
		filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
		filepath.Join(hqPath, rigName, "crew", "worker1", "rig"),
	}
	for _, dir := range dirs {
		if err := os.MkdirAll(dir, 0755); err != nil {
			t.Fatalf("mkdir %s: %v", dir, err)
		}
	}

	tests := []struct {
		name        string
		cwd         string
		wantRole    string
		wantRig     string
		wantWorker  string
	}{
		{
			name:     "mayor from mayor dir",
			cwd:      filepath.Join(hqPath, "mayor"),
			wantRole: "mayor",
		},
		{
			name:     "deacon from deacon dir",
			cwd:      filepath.Join(hqPath, "deacon"),
			wantRole: "deacon",
		},
		{
			name:     "witness from witness dir",
			cwd:      filepath.Join(hqPath, rigName, "witness"),
			wantRole: "witness",
			wantRig:  rigName,
		},
		{
			name:     "refinery from refinery/rig dir",
			cwd:      filepath.Join(hqPath, rigName, "refinery", "rig"),
			wantRole: "refinery",
			wantRig:  rigName,
		},
		{
			name:       "polecat from polecats/Toast/rig dir",
			cwd:        filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
			wantRole:   "polecat",
			wantRig:    rigName,
			wantWorker: "Toast",
		},
		{
			name:       "crew from crew/worker1/rig dir",
			cwd:        filepath.Join(hqPath, rigName, "crew", "worker1", "rig"),
			wantRole:   "crew",
			wantRig:    rigName,
			wantWorker: "worker1",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cmd := exec.Command(gtBinary, "role", "detect")
			cmd.Dir = tt.cwd
			cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)

			output, err := cmd.CombinedOutput()
			if err != nil {
				t.Fatalf("gt role detect failed: %v\nOutput: %s", err, output)
			}

			got := string(output)

			// Check role is detected
			if !strings.Contains(got, tt.wantRole) {
				t.Errorf("output missing role %q\ngot: %s", tt.wantRole, got)
			}

			// Check "(from cwd)" marker
			if !strings.Contains(got, "(from cwd)") {
				t.Errorf("output missing '(from cwd)' marker\ngot: %s", got)
			}

			// Check rig if expected
			if tt.wantRig != "" {
				if !strings.Contains(got, "Rig: "+tt.wantRig) {
					t.Errorf("output missing 'Rig: %s'\ngot: %s", tt.wantRig, got)
				}
			}

			// Check worker if expected
			if tt.wantWorker != "" {
				if !strings.Contains(got, "Worker: "+tt.wantWorker) {
					t.Errorf("output missing 'Worker: %s'\ngot: %s", tt.wantWorker, got)
				}
			}
		})
	}
}

// TestRoleDetectIgnoresGTRole validates gt role detect ignores GT_ROLE env var.
func TestRoleDetectIgnoresGTRole(t *testing.T) {
	tmpDir := t.TempDir()
	hqPath := filepath.Join(tmpDir, "test-hq")
	gtBinary := buildGT(t)

	cmd := exec.Command(gtBinary, "install", hqPath, "--no-beads")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
	if output, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("gt install failed: %v\nOutput: %s", err, output)
	}

	// Run from mayor dir but set GT_ROLE to deacon
	cmd = exec.Command(gtBinary, "role", "detect")
	cmd.Dir = filepath.Join(hqPath, "mayor")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir, "GT_ROLE=deacon")

	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("gt role detect failed: %v\nOutput: %s", err, output)
	}

	got := string(output)

	// Should detect mayor from cwd, not deacon from env
	if !strings.Contains(got, "mayor") {
		t.Errorf("should detect 'mayor' from cwd, got: %s", got)
	}

	// Should show mismatch warning
	if !strings.Contains(got, "Mismatch") {
		t.Errorf("should show mismatch warning when GT_ROLE disagrees\ngot: %s", got)
	}

	if !strings.Contains(got, "GT_ROLE=deacon") {
		t.Errorf("should show conflicting GT_ROLE value\ngot: %s", got)
	}
}

// TestRoleDetectInvalidPaths validates detection behavior for incomplete/invalid paths.
func TestRoleDetectInvalidPaths(t *testing.T) {
	tmpDir := t.TempDir()
	hqPath := filepath.Join(tmpDir, "test-hq")
	gtBinary := buildGT(t)

	cmd := exec.Command(gtBinary, "install", hqPath, "--no-beads")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
	if output, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("gt install failed: %v\nOutput: %s", err, output)
	}

	rigName := "testrig"

	// Create incomplete directory structures
	dirs := []string{
		filepath.Join(hqPath, rigName),                        // rig root
		filepath.Join(hqPath, rigName, "polecats"),            // polecats without name
		filepath.Join(hqPath, rigName, "crew"),                // crew without name
		filepath.Join(hqPath, rigName, "refinery"),            // refinery without /rig
		filepath.Join(hqPath, rigName, "witness"),             // witness (valid - no /rig needed)
	}
	for _, dir := range dirs {
		if err := os.MkdirAll(dir, 0755); err != nil {
			t.Fatalf("mkdir %s: %v", dir, err)
		}
	}

	tests := []struct {
		name     string
		cwd      string
		wantRole string
	}{
		{
			name:     "rig root returns unknown",
			cwd:      filepath.Join(hqPath, rigName),
			wantRole: "unknown",
		},
		{
			name:     "polecats without name returns unknown",
			cwd:      filepath.Join(hqPath, rigName, "polecats"),
			wantRole: "unknown",
		},
		{
			name:     "crew without name returns unknown",
			cwd:      filepath.Join(hqPath, rigName, "crew"),
			wantRole: "unknown",
		},
		{
			name:     "refinery without /rig still detects refinery",
			cwd:      filepath.Join(hqPath, rigName, "refinery"),
			wantRole: "refinery",
		},
		{
			name:     "witness without /rig detects witness",
			cwd:      filepath.Join(hqPath, rigName, "witness"),
			wantRole: "witness",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cmd := exec.Command(gtBinary, "role", "detect")
			cmd.Dir = tt.cwd
			cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)

			output, err := cmd.CombinedOutput()
			if err != nil {
				t.Fatalf("gt role detect failed: %v\nOutput: %s", err, output)
			}

			got := string(output)
			if !strings.Contains(got, tt.wantRole) {
				t.Errorf("expected role %q\ngot: %s", tt.wantRole, got)
			}
		})
	}
}

// TestRoleEnvIncompleteEnvVars validates gt role env fills gaps from cwd with warning.
func TestRoleEnvIncompleteEnvVars(t *testing.T) {
	tmpDir := t.TempDir()
	hqPath := filepath.Join(tmpDir, "test-hq")
	gtBinary := buildGT(t)

	cmd := exec.Command(gtBinary, "install", hqPath, "--no-beads")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
	if output, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("gt install failed: %v\nOutput: %s", err, output)
	}

	rigName := "testrig"

	// Create rig directory structure for cwd detection
	dirs := []string{
		filepath.Join(hqPath, rigName, "witness"),
		filepath.Join(hqPath, rigName, "refinery", "rig"),
		filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
		filepath.Join(hqPath, rigName, "crew", "worker1", "rig"),
	}
	for _, dir := range dirs {
		if err := os.MkdirAll(dir, 0755); err != nil {
			t.Fatalf("mkdir %s: %v", dir, err)
		}
	}

	tests := []struct {
		name       string
		cwd        string
		envVars    []string
		wantExport []string // Expected exports in stdout
		wantStderr string   // Expected warning in stderr
	}{
		{
			name: "GT_ROLE=witness without GT_RIG, filled from cwd",
			cwd:  filepath.Join(hqPath, rigName, "witness"),
			envVars: []string{"GT_ROLE=witness"},
			wantExport: []string{
				"export GT_ROLE=witness",
				"export GT_RIG=" + rigName,
				"export BD_ACTOR=" + rigName + "/witness",
			},
			wantStderr: "env vars incomplete",
		},
		{
			name: "GT_ROLE=refinery without GT_RIG, filled from cwd",
			cwd:  filepath.Join(hqPath, rigName, "refinery", "rig"),
			envVars: []string{"GT_ROLE=refinery"},
			wantExport: []string{
				"export GT_ROLE=refinery",
				"export GT_RIG=" + rigName,
				"export BD_ACTOR=" + rigName + "/refinery",
			},
			wantStderr: "env vars incomplete",
		},
		{
			name: "GT_ROLE=polecat without GT_RIG or GT_POLECAT, filled from cwd",
			cwd:  filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
			envVars: []string{"GT_ROLE=polecat"},
			wantExport: []string{
				"export GT_ROLE=polecat",
				"export GT_RIG=" + rigName,
				"export GT_POLECAT=Toast",
				"export BD_ACTOR=" + rigName + "/polecats/Toast",
			},
			wantStderr: "env vars incomplete",
		},
		{
			name: "GT_ROLE=polecat with GT_RIG but no GT_POLECAT, filled from cwd",
			cwd:  filepath.Join(hqPath, rigName, "polecats", "Toast", "rig"),
			envVars: []string{"GT_ROLE=polecat", "GT_RIG=" + rigName},
			wantExport: []string{
				"export GT_ROLE=polecat",
				"export GT_RIG=" + rigName,
				"export GT_POLECAT=Toast",
				"export BD_ACTOR=" + rigName + "/polecats/Toast",
			},
			wantStderr: "env vars incomplete",
		},
		{
			name: "GT_ROLE=crew without GT_RIG or GT_CREW, filled from cwd",
			cwd:  filepath.Join(hqPath, rigName, "crew", "worker1", "rig"),
			envVars: []string{"GT_ROLE=crew"},
			wantExport: []string{
				"export GT_ROLE=crew",
				"export GT_RIG=" + rigName,
				"export GT_CREW=worker1",
				"export BD_ACTOR=" + rigName + "/crew/worker1",
			},
			wantStderr: "env vars incomplete",
		},
		{
			name: "Complete env vars - no warning",
			cwd:  filepath.Join(hqPath, rigName, "witness"),
			envVars: []string{"GT_ROLE=witness", "GT_RIG=" + rigName},
			wantExport: []string{
				"export GT_ROLE=witness",
				"export GT_RIG=" + rigName,
				"export BD_ACTOR=" + rigName + "/witness",
			},
			wantStderr: "", // No warning expected
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cmd := exec.Command(gtBinary, "role", "env")
			cmd.Dir = tt.cwd
			cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
			cmd.Env = append(cmd.Env, tt.envVars...)

			// Use CombinedOutput to see stderr for debugging, but separate stdout/stderr
			stdout, _ := cmd.Output() // Only stdout
			// Re-run to get stderr
			cmd2 := exec.Command(gtBinary, "role", "env")
			cmd2.Dir = tt.cwd
			cmd2.Env = append(cleanGTEnv(), "HOME="+tmpDir)
			cmd2.Env = append(cmd2.Env, tt.envVars...)
			combined, _ := cmd2.CombinedOutput()
			stderr := strings.TrimPrefix(string(combined), string(stdout))

			// Check expected exports in stdout
			gotStdout := string(stdout)
			for _, w := range tt.wantExport {
				if !strings.Contains(gotStdout, w) {
					t.Errorf("stdout missing %q\ngot: %s", w, gotStdout)
				}
			}

			// Check expected warning in stderr
			if tt.wantStderr != "" {
				if !strings.Contains(stderr, tt.wantStderr) {
					t.Errorf("stderr should contain %q\ngot: %s\ncombined: %s", tt.wantStderr, stderr, combined)
				}
			} else {
				if strings.Contains(stderr, "incomplete") {
					t.Errorf("stderr should not contain 'incomplete' warning\ngot: %s", stderr)
				}
			}
		})
	}
}

// TestRoleEnvCwdMismatchFromIncompleteDir validates warnings when in incomplete directories.
func TestRoleEnvCwdMismatchFromIncompleteDir(t *testing.T) {
	tmpDir := t.TempDir()
	hqPath := filepath.Join(tmpDir, "test-hq")
	gtBinary := buildGT(t)

	cmd := exec.Command(gtBinary, "install", hqPath, "--no-beads")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
	if output, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("gt install failed: %v\nOutput: %s", err, output)
	}

	rigName := "testrig"

	// Create incomplete directory structures (missing /rig)
	dirs := []string{
		filepath.Join(hqPath, rigName, "refinery"),            // refinery without /rig
		filepath.Join(hqPath, rigName, "polecats", "Toast"),   // polecat without /rig
		filepath.Join(hqPath, rigName, "crew", "worker1"),     // crew without /rig
	}
	for _, dir := range dirs {
		if err := os.MkdirAll(dir, 0755); err != nil {
			t.Fatalf("mkdir %s: %v", dir, err)
		}
	}

	tests := []struct {
		name       string
		cwd        string
		envVars    []string
		wantStderr string // Expected warning about cwd mismatch
	}{
		{
			name: "refinery without /rig shows cwd mismatch",
			cwd:  filepath.Join(hqPath, rigName, "refinery"),
			envVars: []string{"GT_ROLE=refinery", "GT_RIG=" + rigName},
			wantStderr: "cwd",
		},
		{
			name: "polecat without /rig shows cwd mismatch",
			cwd:  filepath.Join(hqPath, rigName, "polecats", "Toast"),
			envVars: []string{"GT_ROLE=polecat", "GT_RIG=" + rigName, "GT_POLECAT=Toast"},
			wantStderr: "cwd",
		},
		{
			name: "crew without /rig shows cwd mismatch",
			cwd:  filepath.Join(hqPath, rigName, "crew", "worker1"),
			envVars: []string{"GT_ROLE=crew", "GT_RIG=" + rigName, "GT_CREW=worker1"},
			wantStderr: "cwd",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cmd := exec.Command(gtBinary, "role", "env")
			cmd.Dir = tt.cwd
			cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
			cmd.Env = append(cmd.Env, tt.envVars...)

			combined, err := cmd.CombinedOutput()
			if err != nil {
				t.Fatalf("gt role env failed: %v\nOutput: %s", err, combined)
			}

			// Check for cwd mismatch warning
			if !strings.Contains(string(combined), tt.wantStderr) {
				t.Errorf("output should contain %q warning\ngot: %s", tt.wantStderr, combined)
			}
		})
	}
}

// TestRoleHomeInvalidPaths validates that commands fail gracefully for incomplete paths.
func TestRoleHomeInvalidPaths(t *testing.T) {
	tmpDir := t.TempDir()
	hqPath := filepath.Join(tmpDir, "test-hq")
	gtBinary := buildGT(t)

	cmd := exec.Command(gtBinary, "install", hqPath, "--no-beads")
	cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)
	if output, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("gt install failed: %v\nOutput: %s", err, output)
	}

	rigName := "testrig"

	// Create incomplete directory structures
	dirs := []string{
		filepath.Join(hqPath, rigName),
		filepath.Join(hqPath, rigName, "polecats"),
		filepath.Join(hqPath, rigName, "crew"),
	}
	for _, dir := range dirs {
		if err := os.MkdirAll(dir, 0755); err != nil {
			t.Fatalf("mkdir %s: %v", dir, err)
		}
	}

	tests := []struct {
		name      string
		cwd       string
		shouldErr bool
	}{
		{
			name:      "rig root fails",
			cwd:       filepath.Join(hqPath, rigName),
			shouldErr: true,
		},
		{
			name:      "polecats without name fails",
			cwd:       filepath.Join(hqPath, rigName, "polecats"),
			shouldErr: true,
		},
		{
			name:      "crew without name fails",
			cwd:       filepath.Join(hqPath, rigName, "crew"),
			shouldErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cmd := exec.Command(gtBinary, "role", "home")
			cmd.Dir = tt.cwd
			cmd.Env = append(cleanGTEnv(), "HOME="+tmpDir)

			_, err := cmd.CombinedOutput()
			if tt.shouldErr && err == nil {
				t.Errorf("expected error but command succeeded")
			}
			if !tt.shouldErr && err != nil {
				t.Errorf("expected success but got error: %v", err)
			}
		})
	}
}

