package handlers

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/adapter/kubernetes"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/validate"
)

// MockSimpleExecutor mocks the executor for testing ClaudeConfigHandler.
// It focuses on ExecSimple which is the method used by the handler.
type MockSimpleExecutor struct {
	execSimpleCalls   []ExecSimpleCall
	execSimpleResults map[string]ExecSimpleResult
	defaultResult     ExecSimpleResult
}

// ExecSimpleCall records the parameters of an ExecSimple call.
type ExecSimpleCall struct {
	PodName string
	Command string
}

// ExecSimpleResult represents the result of an ExecSimple call.
type ExecSimpleResult struct {
	Output string
	Err    error
}

// NewMockSimpleExecutor creates a new mock executor.
func NewMockSimpleExecutor() *MockSimpleExecutor {
	return &MockSimpleExecutor{
		execSimpleResults: make(map[string]ExecSimpleResult),
	}
}

// ExecSimple mocks command execution.
func (m *MockSimpleExecutor) ExecSimple(podName, command string) (string, error) {
	m.execSimpleCalls = append(m.execSimpleCalls, ExecSimpleCall{
		PodName: podName,
		Command: command,
	})

	// Check for specific command result first
	key := podName + ":" + command
	if result, ok := m.execSimpleResults[key]; ok {
		return result.Output, result.Err
	}

	// Check for pattern match (e.g., for cat commands with any path)
	for pattern, result := range m.execSimpleResults {
		if strings.Contains(command, pattern) {
			return result.Output, result.Err
		}
	}

	return m.defaultResult.Output, m.defaultResult.Err
}

// SetResult sets the result for a specific pod+command combination.
func (m *MockSimpleExecutor) SetResult(podName, command string, output string, err error) {
	key := podName + ":" + command
	m.execSimpleResults[key] = ExecSimpleResult{Output: output, Err: err}
}

// SetPatternResult sets the result for any command containing the pattern.
func (m *MockSimpleExecutor) SetPatternResult(pattern string, output string, err error) {
	m.execSimpleResults[pattern] = ExecSimpleResult{Output: output, Err: err}
}

// SetDefaultResult sets the default result for any command not explicitly configured.
func (m *MockSimpleExecutor) SetDefaultResult(output string, err error) {
	m.defaultResult = ExecSimpleResult{Output: output, Err: err}
}

// GetCalls returns all recorded ExecSimple calls.
func (m *MockSimpleExecutor) GetCalls() []ExecSimpleCall {
	return m.execSimpleCalls
}

// Reset clears all recorded calls and results.
func (m *MockSimpleExecutor) Reset() {
	m.execSimpleCalls = nil
	m.execSimpleResults = make(map[string]ExecSimpleResult)
	m.defaultResult = ExecSimpleResult{}
}

// --- Tests for validate.Name ---

func TestValidateName(t *testing.T) {
	tests := []struct {
		name    string
		input   string
		wantErr bool
	}{
		// Valid names
		{"simple lowercase", "mycommand", false},
		{"with dashes", "my-command", false},
		{"with underscores", "my_command", false},
		{"with numbers", "command123", false},
		{"mixed case", "MyCommand", false},
		{"complex valid", "My-Command_123", false},
		{"single char", "a", false},
		{"numbers only", "123", false},
		{"64 chars", strings.Repeat("a", 64), false},

		// Invalid names
		{"empty string", "", true},
		{"65 chars", strings.Repeat("a", 65), true},
		{"100 chars", strings.Repeat("a", 100), true},
		{"with spaces", "my command", true},
		{"with dots", "my.command", true},
		{"path traversal", "../etc", true},
		{"double path traversal", "../../etc", true},
		{"with slash", "path/to/file", true},
		{"with backslash", "path\\to\\file", true},
		{"with semicolon", "cmd;rm", true},
		{"with pipe", "cmd|cat", true},
		{"with backtick", "cmd`whoami`", true},
		{"with dollar", "$HOME", true},
		{"with ampersand", "cmd&cmd", true},
		{"with newline", "cmd\ncmd", true},
		{"with tab", "cmd\tcmd", true},
		{"with null byte", "cmd\x00cmd", true},
		{"unicode chars", "command\u00e9", true},
		{"emoji", "command\U0001F600", true},
		{"leading dash", "-command", false}, // Actually valid per regex
		{"leading underscore", "_command", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validate.Name(tt.input, "name")
			gotErr := err != nil
			if gotErr != tt.wantErr {
				t.Errorf("validate.Name(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
			}
		})
	}
}

// --- Integration tests for HTTP handlers ---

// setupTestRouter creates a chi router with the handler mounted.
// Since we can't easily mock the executor in the current design,
// these tests will verify the validation and error handling paths.
func setupTestRouter(t *testing.T) (*chi.Mux, *kubernetes.ProjectRepository) {
	t.Helper()

	// Create a repository with test projects
	repo := kubernetes.NewProjectRepository("test-namespace")
	_ = repo.Register(context.Background(), &domain.Project{
		ID:          "test-project",
		Name:        "Test Project",
		Description: "A test project",
		PodName:     "test-pod-0",
		Status:      domain.ProjectStatusRunning,
		Workspace:   "/workspace",
	})

	// Create executor (will fail on actual kubectl calls in tests, but
	// we can test validation logic that happens before executor calls)
	exec := kubernetes.NewExecutor("test-namespace")

	handler := NewClaudeConfigHandler(repo, exec)
	router := chi.NewRouter()
	router.Use(testAdminAuth) // Add auth middleware for tests
	handler.Mount(router)

	return router, repo
}

// --- Tests for project not found scenarios ---

func TestClaudeConfigHandler_ProjectNotFound(t *testing.T) {
	router, _ := setupTestRouter(t)

	tests := []struct {
		name   string
		method string
		path   string
		body   string
	}{
		{"Overview", "GET", "/projects/nonexistent/claude-config", ""},
		{"ListCommands", "GET", "/projects/nonexistent/claude-config/commands", ""},
		{"ListSkills", "GET", "/projects/nonexistent/claude-config/skills", ""},
		{"ListAgents", "GET", "/projects/nonexistent/claude-config/agents", ""},
		{"CreateCommand", "POST", "/projects/nonexistent/claude-config/commands", `{"name":"test","content":"test"}`},
		{"GetCommand", "GET", "/projects/nonexistent/claude-config/commands/test", ""},
		{"UpdateCommand", "PUT", "/projects/nonexistent/claude-config/commands/test", `{"content":"test"}`},
		{"DeleteCommand", "DELETE", "/projects/nonexistent/claude-config/commands/test", ""},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var body *bytes.Reader
			if tt.body != "" {
				body = bytes.NewReader([]byte(tt.body))
			} else {
				body = bytes.NewReader(nil)
			}

			req := httptest.NewRequest(tt.method, tt.path, body)
			if tt.body != "" {
				req.Header.Set("Content-Type", "application/json")
			}
			rec := httptest.NewRecorder()

			router.ServeHTTP(rec, req)

			if rec.Code != http.StatusNotFound {
				t.Errorf("Status = %d, want 404. Body: %s", rec.Code, rec.Body.String())
			}

			if !strings.Contains(rec.Body.String(), "project not found") {
				t.Errorf("Body = %q, want to contain 'project not found'", rec.Body.String())
			}
		})
	}
}

// --- Tests for invalid name validation ---

func TestClaudeConfigHandler_InvalidName(t *testing.T) {
	router, _ := setupTestRouter(t)

	// These invalid names will reach the handler validation
	// (names with slashes or empty get rejected by the router first with 404)
	handlerRejectedNames := []string{
		strings.Repeat("a", 65), // Too long
		"cmd;injection",         // Invalid characters
		"$variable",             // Invalid characters
	}

	endpoints := []struct {
		method      string
		pathPattern string
		needsBody   bool
		body        string
	}{
		{"GET", "/projects/test-project/claude-config/commands/%s", false, ""},
		{"PUT", "/projects/test-project/claude-config/commands/%s", true, `{"content":"test"}`},
		{"DELETE", "/projects/test-project/claude-config/commands/%s", false, ""},
		{"GET", "/projects/test-project/claude-config/skills/%s", false, ""},
		{"PUT", "/projects/test-project/claude-config/skills/%s", true, `{"content":"test"}`},
		{"DELETE", "/projects/test-project/claude-config/skills/%s", false, ""},
		{"GET", "/projects/test-project/claude-config/agents/%s", false, ""},
		{"PUT", "/projects/test-project/claude-config/agents/%s", true, `{"content":"test"}`},
		{"DELETE", "/projects/test-project/claude-config/agents/%s", false, ""},
	}

	for _, invalidName := range handlerRejectedNames {
		for _, ep := range endpoints {
			testName := fmt.Sprintf("%s %s with name=%q", ep.method, strings.Split(ep.pathPattern, "/")[4], invalidName)
			t.Run(testName, func(t *testing.T) {
				path := fmt.Sprintf(ep.pathPattern, invalidName)

				var body *bytes.Reader
				if ep.needsBody {
					body = bytes.NewReader([]byte(ep.body))
				} else {
					body = bytes.NewReader(nil)
				}

				req := httptest.NewRequest(ep.method, path, body)
				if ep.needsBody {
					req.Header.Set("Content-Type", "application/json")
				}
				rec := httptest.NewRecorder()

				router.ServeHTTP(rec, req)

				if rec.Code != http.StatusBadRequest {
					t.Errorf("Status = %d, want 400. Body: %s", rec.Code, rec.Body.String())
				}

				// validate.Name returns errors like "name: must be at most 64 characters" or "name: must be alphanumeric..."
				if !strings.Contains(rec.Body.String(), "name:") {
					t.Errorf("Body = %q, want to contain 'name:'", rec.Body.String())
				}
			})
		}
	}
}

// TestClaudeConfigHandler_RouterRejectedNames tests that names with slashes
// or empty names are rejected at the router level (404 not found).
// This is expected behavior - chi router doesn't match paths with slashes in params.
func TestClaudeConfigHandler_RouterRejectedNames(t *testing.T) {
	router, _ := setupTestRouter(t)

	// These names get rejected by the chi router before reaching the handler
	routerRejectedNames := []string{
		"",               // Empty - doesn't match route
		"../../etc",      // Path traversal with slashes
		"path/traversal", // Contains slash
	}

	for _, name := range routerRejectedNames {
		t.Run("router rejects: "+name, func(t *testing.T) {
			path := fmt.Sprintf("/projects/test-project/claude-config/commands/%s", name)
			req := httptest.NewRequest("GET", path, nil)
			rec := httptest.NewRecorder()

			router.ServeHTTP(rec, req)

			// Router returns 404 for unmatched paths
			if rec.Code != http.StatusNotFound {
				t.Errorf("Status = %d, want 404", rec.Code)
			}
		})
	}
}

// --- Tests for createItem validation ---

func TestClaudeConfigHandler_CreateValidation(t *testing.T) {
	router, _ := setupTestRouter(t)

	tests := []struct {
		name       string
		body       string
		wantStatus int
		wantErr    string
	}{
		{
			name:       "missing name",
			body:       `{"content":"test content"}`,
			wantStatus: http.StatusBadRequest,
			wantErr:    "name: is required",
		},
		{
			name:       "empty name",
			body:       `{"name":"","content":"test content"}`,
			wantStatus: http.StatusBadRequest,
			wantErr:    "name: is required",
		},
		{
			name:       "missing content",
			body:       `{"name":"test-command"}`,
			wantStatus: http.StatusBadRequest,
			wantErr:    "content: is required",
		},
		{
			name:       "empty content",
			body:       `{"name":"test-command","content":""}`,
			wantStatus: http.StatusBadRequest,
			wantErr:    "content: is required",
		},
		{
			name:       "invalid name characters",
			body:       `{"name":"../etc/passwd","content":"test"}`,
			wantStatus: http.StatusBadRequest,
			wantErr:    "alphanumeric",
		},
		{
			name:       "name too long",
			body:       fmt.Sprintf(`{"name":"%s","content":"test"}`, strings.Repeat("a", 65)),
			wantStatus: http.StatusBadRequest,
			wantErr:    "must be at most 64 characters",
		},
		{
			name:       "invalid JSON",
			body:       `{"name": invalid}`,
			wantStatus: http.StatusBadRequest,
			wantErr:    "invalid",
		},
	}

	itemTypes := []string{"commands", "skills", "agents"}

	for _, itemType := range itemTypes {
		for _, tt := range tests {
			testName := fmt.Sprintf("%s/%s", itemType, tt.name)
			t.Run(testName, func(t *testing.T) {
				path := fmt.Sprintf("/projects/test-project/claude-config/%s", itemType)
				req := httptest.NewRequest("POST", path, strings.NewReader(tt.body))
				req.Header.Set("Content-Type", "application/json")
				rec := httptest.NewRecorder()

				router.ServeHTTP(rec, req)

				if rec.Code != tt.wantStatus {
					t.Errorf("Status = %d, want %d. Body: %s", rec.Code, tt.wantStatus, rec.Body.String())
				}

				if !strings.Contains(rec.Body.String(), tt.wantErr) {
					t.Errorf("Body = %q, want to contain %q", rec.Body.String(), tt.wantErr)
				}
			})
		}
	}
}

// --- Tests for updateItem validation ---

func TestClaudeConfigHandler_UpdateValidation(t *testing.T) {
	router, _ := setupTestRouter(t)

	tests := []struct {
		name       string
		itemName   string
		body       string
		wantStatus int
		wantErr    string
	}{
		{
			name:       "missing content",
			itemName:   "valid-name",
			body:       `{}`,
			wantStatus: http.StatusBadRequest,
			wantErr:    "content: is required",
		},
		{
			name:       "empty content",
			itemName:   "valid-name",
			body:       `{"content":""}`,
			wantStatus: http.StatusBadRequest,
			wantErr:    "content: is required",
		},
		{
			name:       "invalid JSON",
			itemName:   "valid-name",
			body:       `{content: invalid}`,
			wantStatus: http.StatusBadRequest,
			wantErr:    "invalid",
		},
	}

	itemTypes := []string{"commands", "skills", "agents"}

	for _, itemType := range itemTypes {
		for _, tt := range tests {
			testName := fmt.Sprintf("%s/%s", itemType, tt.name)
			t.Run(testName, func(t *testing.T) {
				path := fmt.Sprintf("/projects/test-project/claude-config/%s/%s", itemType, tt.itemName)
				req := httptest.NewRequest("PUT", path, strings.NewReader(tt.body))
				req.Header.Set("Content-Type", "application/json")
				rec := httptest.NewRecorder()

				router.ServeHTTP(rec, req)

				if rec.Code != tt.wantStatus {
					t.Errorf("Status = %d, want %d. Body: %s", rec.Code, tt.wantStatus, rec.Body.String())
				}

				if !strings.Contains(rec.Body.String(), tt.wantErr) {
					t.Errorf("Body = %q, want to contain %q", rec.Body.String(), tt.wantErr)
				}
			})
		}
	}
}

// --- Tests for content size limits ---

func TestClaudeConfigHandler_ContentSizeLimit(t *testing.T) {
	router, _ := setupTestRouter(t)

	// Create content just over 1MB
	largeContent := strings.Repeat("x", maxContentSize+1)

	tests := []struct {
		name   string
		method string
		path   string
		body   string
	}{
		{
			name:   "create command with oversized content",
			method: "POST",
			path:   "/projects/test-project/claude-config/commands",
			body:   fmt.Sprintf(`{"name":"test","content":"%s"}`, largeContent),
		},
		{
			name:   "update command with oversized content",
			method: "PUT",
			path:   "/projects/test-project/claude-config/commands/test",
			body:   fmt.Sprintf(`{"content":"%s"}`, largeContent),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req := httptest.NewRequest(tt.method, tt.path, strings.NewReader(tt.body))
			req.Header.Set("Content-Type", "application/json")
			rec := httptest.NewRecorder()

			router.ServeHTTP(rec, req)

			if rec.Code != http.StatusBadRequest {
				t.Errorf("Status = %d, want 400", rec.Code)
			}

			if !strings.Contains(rec.Body.String(), "too large") && !strings.Contains(rec.Body.String(), "invalid") {
				t.Errorf("Body = %q, want to contain 'too large' or 'invalid'", rec.Body.String())
			}
		})
	}
}

// --- Tests for valid names that should be accepted ---

func TestClaudeConfigHandler_ValidNames(t *testing.T) {
	validNames := []string{
		"my-command",
		"skill_123",
		"AgentOne",
		"a",
		"ABC",
		"test-skill-v2",
		"_private",
		"123",
		"cmd-v1_beta",
		strings.Repeat("a", 64), // Max length
	}

	for _, name := range validNames {
		t.Run("valid: "+name, func(t *testing.T) {
			if err := validate.Name(name, "name"); err != nil {
				t.Errorf("validate.Name(%q) returned error: %v, want nil", name, err)
			}
		})
	}
}

// --- Tests for base64 encoding of content ---

func TestBase64Encoding(t *testing.T) {
	tests := []struct {
		name    string
		content string
	}{
		{"simple text", "Hello, world!"},
		{"with quotes", `Say "hello"`},
		{"with single quotes", "It's working"},
		{"with backticks", "Run `command`"},
		{"with dollar sign", "$HOME/path"},
		{"with newlines", "line1\nline2\nline3"},
		{"with shell chars", "echo $(whoami); rm -rf /"},
		{"with heredoc terminator", "EOF\nContent\nEOF"},
		{"with unicode", "Hello \u00e9\u00e8\u00ea"},
		{"with null-like", "before\\x00after"},
		{"complex markdown", "# Title\n\n```bash\necho 'test'\n```\n"},
		{"json content", `{"key": "value", "nested": {"a": 1}}`},
		{"xml-like", "<tag>content</tag>"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Encode
			encoded := base64.StdEncoding.EncodeToString([]byte(tt.content))

			// Verify no shell-dangerous characters in encoded string
			dangerousChars := []string{"`", "$", "(", ")", ";", "|", "&", ">", "<", "\n", "'", "\""}
			for _, c := range dangerousChars {
				if strings.Contains(encoded, c) {
					t.Errorf("Encoded string contains dangerous char %q: %s", c, encoded)
				}
			}

			// Verify we can decode back
			decoded, err := base64.StdEncoding.DecodeString(encoded)
			if err != nil {
				t.Errorf("Failed to decode: %v", err)
			}
			if string(decoded) != tt.content {
				t.Errorf("Decoded = %q, want %q", string(decoded), tt.content)
			}
		})
	}
}

// --- Tests for ConfigItem JSON serialization ---

func TestConfigItem_JSON(t *testing.T) {
	item := ConfigItem{
		Name:    "test-command",
		Type:    "commands",
		Content: "# Test Command\n\nThis is a test.",
	}

	data, err := json.Marshal(item)
	if err != nil {
		t.Fatalf("Failed to marshal: %v", err)
	}

	var decoded ConfigItem
	if err := json.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("Failed to unmarshal: %v", err)
	}

	if decoded.Name != item.Name {
		t.Errorf("Name = %q, want %q", decoded.Name, item.Name)
	}
	if decoded.Type != item.Type {
		t.Errorf("Type = %q, want %q", decoded.Type, item.Type)
	}
	if decoded.Content != item.Content {
		t.Errorf("Content = %q, want %q", decoded.Content, item.Content)
	}
}

// --- Tests for ConfigItemRequest JSON serialization ---

func TestConfigItemRequest_JSON(t *testing.T) {
	tests := []struct {
		name string
		json string
		want ConfigItemRequest
	}{
		{
			name: "full request",
			json: `{"name":"test","content":"# Content"}`,
			want: ConfigItemRequest{Name: "test", Content: "# Content"},
		},
		{
			name: "content only",
			json: `{"content":"# Content"}`,
			want: ConfigItemRequest{Name: "", Content: "# Content"},
		},
		{
			name: "empty strings",
			json: `{"name":"","content":""}`,
			want: ConfigItemRequest{Name: "", Content: ""},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var req ConfigItemRequest
			if err := json.Unmarshal([]byte(tt.json), &req); err != nil {
				t.Fatalf("Failed to unmarshal: %v", err)
			}
			if req.Name != tt.want.Name {
				t.Errorf("Name = %q, want %q", req.Name, tt.want.Name)
			}
			if req.Content != tt.want.Content {
				t.Errorf("Content = %q, want %q", req.Content, tt.want.Content)
			}
		})
	}
}

// --- Tests for ConfigOverview JSON serialization ---

func TestConfigOverview_JSON(t *testing.T) {
	overview := ConfigOverview{
		Project:  "test-project",
		Path:     "/workspace/.claude",
		Commands: []string{"cmd1", "cmd2"},
		Skills:   []string{"skill1"},
		Agents:   []string{},
	}

	data, err := json.Marshal(overview)
	if err != nil {
		t.Fatalf("Failed to marshal: %v", err)
	}

	var decoded ConfigOverview
	if err := json.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("Failed to unmarshal: %v", err)
	}

	if decoded.Project != overview.Project {
		t.Errorf("Project = %q, want %q", decoded.Project, overview.Project)
	}
	if decoded.Path != overview.Path {
		t.Errorf("Path = %q, want %q", decoded.Path, overview.Path)
	}
	if len(decoded.Commands) != len(overview.Commands) {
		t.Errorf("Commands length = %d, want %d", len(decoded.Commands), len(overview.Commands))
	}
	if len(decoded.Skills) != len(overview.Skills) {
		t.Errorf("Skills length = %d, want %d", len(decoded.Skills), len(overview.Skills))
	}
	if len(decoded.Agents) != len(overview.Agents) {
		t.Errorf("Agents length = %d, want %d", len(decoded.Agents), len(overview.Agents))
	}
}

// --- Tests for NewClaudeConfigHandler ---

func TestNewClaudeConfigHandler(t *testing.T) {
	repo := kubernetes.NewProjectRepository("test-namespace")
	exec := kubernetes.NewExecutor("test-namespace")

	handler := NewClaudeConfigHandler(repo, exec)

	if handler == nil {
		t.Fatal("NewClaudeConfigHandler returned nil")
	}
	if handler.projectRepo != repo {
		t.Error("Handler projectRepo not set correctly")
	}
	if handler.executor != exec {
		t.Error("Handler executor not set correctly")
	}
}

// --- Tests for route mounting ---

func TestClaudeConfigHandler_Mount(t *testing.T) {
	router, _ := setupTestRouter(t)

	// Define all expected routes
	routes := []struct {
		method string
		path   string
	}{
		// Overview
		{"GET", "/projects/test-project/claude-config"},
		// Commands
		{"GET", "/projects/test-project/claude-config/commands"},
		{"POST", "/projects/test-project/claude-config/commands"},
		{"GET", "/projects/test-project/claude-config/commands/test"},
		{"PUT", "/projects/test-project/claude-config/commands/test"},
		{"DELETE", "/projects/test-project/claude-config/commands/test"},
		// Skills
		{"GET", "/projects/test-project/claude-config/skills"},
		{"POST", "/projects/test-project/claude-config/skills"},
		{"GET", "/projects/test-project/claude-config/skills/test"},
		{"PUT", "/projects/test-project/claude-config/skills/test"},
		{"DELETE", "/projects/test-project/claude-config/skills/test"},
		// Agents
		{"GET", "/projects/test-project/claude-config/agents"},
		{"POST", "/projects/test-project/claude-config/agents"},
		{"GET", "/projects/test-project/claude-config/agents/test"},
		{"PUT", "/projects/test-project/claude-config/agents/test"},
		{"DELETE", "/projects/test-project/claude-config/agents/test"},
	}

	for _, rt := range routes {
		t.Run(rt.method+" "+rt.path, func(t *testing.T) {
			var body *strings.Reader
			if rt.method == "POST" || rt.method == "PUT" {
				body = strings.NewReader(`{"name":"test","content":"test content"}`)
			} else {
				body = strings.NewReader("")
			}

			req := httptest.NewRequest(rt.method, rt.path, body)
			if rt.method == "POST" || rt.method == "PUT" {
				req.Header.Set("Content-Type", "application/json")
			}
			rec := httptest.NewRecorder()

			router.ServeHTTP(rec, req)

			// We expect either success, not found (project/item), or internal error (executor fails)
			// but NOT 404 for route not found or 405 method not allowed
			if rec.Code == http.StatusMethodNotAllowed {
				t.Errorf("Route not mounted: %s %s", rt.method, rt.path)
			}
		})
	}
}

// --- Tests for maxContentSize constant ---

func TestMaxContentSize(t *testing.T) {
	// Verify maxContentSize is 1MB
	expectedSize := 1 << 20 // 1MB = 1048576 bytes
	if maxContentSize != expectedSize {
		t.Errorf("maxContentSize = %d, want %d", maxContentSize, expectedSize)
	}
}

// --- Tests for validate.AlphanumericDashUnderscore pattern ---

func TestAlphanumericDashUnderscorePattern(t *testing.T) {
	// Test that the regex is compiled and available in validate package
	if validate.AlphanumericDashUnderscore == nil {
		t.Fatal("validate.AlphanumericDashUnderscore is nil")
	}

	// Test pattern matching directly
	tests := []struct {
		input string
		want  bool
	}{
		{"abc", true},
		{"ABC", true},
		{"a1b2c3", true},
		{"a-b-c", true},
		{"a_b_c", true},
		{"", false},
		{"a b", false},
		{"a.b", false},
		{"a/b", false},
	}

	for _, tt := range tests {
		got := validate.AlphanumericDashUnderscore.MatchString(tt.input)
		if got != tt.want {
			t.Errorf("validate.AlphanumericDashUnderscore.MatchString(%q) = %v, want %v", tt.input, got, tt.want)
		}
	}
}

// --- Benchmark tests ---

func BenchmarkValidateName(b *testing.B) {
	names := []string{
		"my-command",
		"skill_123",
		"AgentOne",
		"../../etc/passwd",
		strings.Repeat("a", 64),
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		for _, name := range names {
			_ = validate.Name(name, "name")
		}
	}
}

func BenchmarkBase64Encode(b *testing.B) {
	content := strings.Repeat("Test content with special chars: $()`, ", 100)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		base64.StdEncoding.EncodeToString([]byte(content))
	}
}

// --- Edge case tests ---

func TestClaudeConfigHandler_EdgeCases(t *testing.T) {
	router, _ := setupTestRouter(t)

	t.Run("empty request body", func(t *testing.T) {
		req := httptest.NewRequest("POST", "/projects/test-project/claude-config/commands", strings.NewReader(""))
		req.Header.Set("Content-Type", "application/json")
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("Status = %d, want 400", rec.Code)
		}
	})

	t.Run("null body", func(t *testing.T) {
		req := httptest.NewRequest("POST", "/projects/test-project/claude-config/commands", strings.NewReader("null"))
		req.Header.Set("Content-Type", "application/json")
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("Status = %d, want 400", rec.Code)
		}
	})

	t.Run("array instead of object", func(t *testing.T) {
		req := httptest.NewRequest("POST", "/projects/test-project/claude-config/commands", strings.NewReader(`["test"]`))
		req.Header.Set("Content-Type", "application/json")
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("Status = %d, want 400", rec.Code)
		}
	})

	t.Run("numeric name", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/projects/test-project/claude-config/commands/123", nil)
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		// 123 is a valid name, so we should get past validation
		// (may fail at executor level but not at validation)
		if rec.Code == http.StatusBadRequest {
			t.Errorf("Status = 400, numeric names should be valid")
		}
	})
}

// --- Tests for error messages ---

func TestClaudeConfigHandler_ErrorMessages(t *testing.T) {
	router, _ := setupTestRouter(t)

	tests := []struct {
		name        string
		method      string
		path        string
		body        string
		wantMessage string
	}{
		{
			name:        "project not found message",
			method:      "GET",
			path:        "/projects/xyz/claude-config",
			wantMessage: "project not found: xyz",
		},
		{
			name:        "name required message",
			method:      "POST",
			path:        "/projects/test-project/claude-config/commands",
			body:        `{"content":"test"}`,
			wantMessage: "name: is required",
		},
		{
			name:        "content required message",
			method:      "POST",
			path:        "/projects/test-project/claude-config/commands",
			body:        `{"name":"test"}`,
			wantMessage: "content: is required",
		},
		{
			name:        "invalid name message",
			method:      "GET",
			path:        "/projects/test-project/claude-config/commands/" + strings.Repeat("x", 65),
			wantMessage: "name: must be at most 64 characters",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var body *strings.Reader
			if tt.body != "" {
				body = strings.NewReader(tt.body)
			} else {
				body = strings.NewReader("")
			}

			req := httptest.NewRequest(tt.method, tt.path, body)
			if tt.body != "" {
				req.Header.Set("Content-Type", "application/json")
			}
			rec := httptest.NewRecorder()

			router.ServeHTTP(rec, req)

			if !strings.Contains(rec.Body.String(), tt.wantMessage) {
				t.Errorf("Body = %q, want to contain %q", rec.Body.String(), tt.wantMessage)
			}
		})
	}
}

// --- Security-focused tests ---

func TestClaudeConfigHandler_Security(t *testing.T) {
	t.Run("path traversal in name parameter", func(t *testing.T) {
		attacks := []string{
			"../",
			"..%2f",
			"..%252f",
			"....//",
			"..;/",
			".../",
			"%2e%2e/",
		}

		for _, attack := range attacks {
			// validate.Name should reject all of these
			if err := validate.Name(attack, "name"); err == nil {
				t.Errorf("validate.Name accepted path traversal: %q", attack)
			}
		}
	})

	t.Run("command injection in name", func(t *testing.T) {
		attacks := []string{
			"test; rm -rf /",
			"test && whoami",
			"test | cat /etc/passwd",
			"test`whoami`",
			"$(whoami)",
			"${PATH}",
			"test\nmalicious",
		}

		for _, attack := range attacks {
			if err := validate.Name(attack, "name"); err == nil {
				t.Errorf("validate.Name accepted command injection: %q", attack)
			}
		}
	})

	t.Run("base64 prevents heredoc injection", func(t *testing.T) {
		// This content would break heredoc if not base64 encoded
		maliciousContent := `EOF
rm -rf /
EOF`
		encoded := base64.StdEncoding.EncodeToString([]byte(maliciousContent))

		// Verify encoded content is safe for shell command
		if strings.Contains(encoded, "EOF") {
			t.Error("Base64 encoded content still contains 'EOF'")
		}
		if strings.Contains(encoded, "\n") {
			t.Error("Base64 encoded content contains newline")
		}
	})
}

// --- MockableClaudeConfigHandler for testing with mock executor ---

// Since the actual handler uses *kubernetes.Executor which calls kubectl,
// we create a version that can use a mock for comprehensive testing.

// MockExecSimpler is an interface for the ExecSimple method only.
type MockExecSimpler interface {
	ExecSimple(podName, command string) (string, error)
}

// testableClaudeConfigHandler wraps the logic for testing with a mock.
type testableClaudeConfigHandler struct {
	projectRepo *kubernetes.ProjectRepository
	execFn      func(podName, command string) (string, error)
}

func (h *testableClaudeConfigHandler) listItems(pod, itemType string) []string {
	cmd := fmt.Sprintf("ls -1 /workspace/.claude/%s 2>/dev/null | sed 's/\\.md$//'", itemType)
	output, err := h.execFn(pod, cmd)
	if err != nil {
		return []string{}
	}

	items := []string{}
	for _, line := range strings.Split(strings.TrimSpace(output), "\n") {
		if line != "" {
			items = append(items, line)
		}
	}
	return items
}

func TestListItems_WithMock(t *testing.T) {
	repo := kubernetes.NewProjectRepository("test")
	_ = repo.Register(context.Background(), &domain.Project{ID: "test", PodName: "test-pod"})

	tests := []struct {
		name     string
		output   string
		err      error
		expected []string
	}{
		{
			name:     "empty directory",
			output:   "",
			err:      nil,
			expected: []string{},
		},
		{
			name:     "single item",
			output:   "command1",
			err:      nil,
			expected: []string{"command1"},
		},
		{
			name:     "multiple items",
			output:   "cmd1\ncmd2\ncmd3",
			err:      nil,
			expected: []string{"cmd1", "cmd2", "cmd3"},
		},
		{
			name:     "with empty lines",
			output:   "cmd1\n\ncmd2\n",
			err:      nil,
			expected: []string{"cmd1", "cmd2"},
		},
		{
			name:     "executor error",
			output:   "",
			err:      errors.New("pod not found"),
			expected: []string{},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			h := &testableClaudeConfigHandler{
				projectRepo: repo,
				execFn: func(podName, command string) (string, error) {
					return tt.output, tt.err
				},
			}

			result := h.listItems("test-pod", "commands")

			if len(result) != len(tt.expected) {
				t.Errorf("len(result) = %d, want %d", len(result), len(tt.expected))
				return
			}

			for i, item := range result {
				if item != tt.expected[i] {
					t.Errorf("result[%d] = %q, want %q", i, item, tt.expected[i])
				}
			}
		})
	}
}

// --- Additional validation tests ---

func TestClaudeConfigHandler_ContentWithSpecialChars(t *testing.T) {
	// Test that various special characters in content are properly handled
	specialContents := []string{
		"Content with 'single quotes'",
		`Content with "double quotes"`,
		"Content with `backticks`",
		"Content with $variables",
		"Content with $(command substitution)",
		"Content with ${parameter expansion}",
		"Content with\nnewlines\n",
		"Content with\ttabs",
		"Content with ; semicolons",
		"Content with | pipes",
		"Content with & ampersands",
		"Content with > redirects",
		"Content with < input redirects",
		"Content with \\ backslashes",
		"Content with emoji: \U0001F600",
	}

	for _, content := range specialContents {
		t.Run("encoding: "+content[:min(20, len(content))], func(t *testing.T) {
			encoded := base64.StdEncoding.EncodeToString([]byte(content))

			// Verify the encoded string is shell-safe
			unsafeChars := []string{"'", "\"", "`", "$", "(", ")", "{", "}", ";", "|", "&", ">", "<", "\n", "\t"}
			for _, c := range unsafeChars {
				if strings.Contains(encoded, c) {
					t.Errorf("Encoded string contains unsafe char %q", c)
				}
			}

			// Verify round-trip
			decoded, err := base64.StdEncoding.DecodeString(encoded)
			if err != nil {
				t.Errorf("Decode error: %v", err)
			}
			if string(decoded) != content {
				t.Errorf("Round-trip failed: got %q, want %q", string(decoded), content)
			}
		})
	}
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
