package worker

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"os/exec"
	"strings"
	"time"

	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/logging"
)

// SDLCTaskExecutor handles WorkTaskTypeSDLC tasks by executing sdlc CLI commands
// inside worker pods via kubectl exec. This enables SDLC operations on skeleton
// projects that don't have dedicated pods.
type SDLCTaskExecutor struct {
	podGitOps *PodGitOperations
	namespace string
}

// SDLCTaskExecutorConfig holds configuration for the SDLC task executor.
type SDLCTaskExecutorConfig struct {
	// Namespace is the Kubernetes namespace for kubectl exec.
	Namespace string

	// PodGitOps provides git clone/commit/push operations.
	PodGitOps *PodGitOperations
}

// NewSDLCTaskExecutor creates a new SDLC task executor.
func NewSDLCTaskExecutor(cfg SDLCTaskExecutorConfig) *SDLCTaskExecutor {
	return &SDLCTaskExecutor{
		podGitOps: cfg.PodGitOps,
		namespace: cfg.Namespace,
	}
}

// Execute runs an SDLC task by cloning the repo, executing the sdlc CLI command,
// and optionally committing/pushing changes.
func (e *SDLCTaskExecutor) Execute(ctx context.Context, task *domain.WorkTask) *domain.BuildResult {
	start := time.Now()
	log := logging.FromContext(ctx).WithWorker("sdlc-task-executor")

	spec, err := e.parseSpec(task.Spec)
	if err != nil {
		return &domain.BuildResult{
			Success:    false,
			Error:      fmt.Sprintf("invalid SDLC spec: %v", err),
			DurationMs: time.Since(start).Milliseconds(),
		}
	}

	// Use the first available claudebox worker pod
	podName := "claudebox-0"

	// Working directory in the pod
	workDir := "/workspace"

	log.Info("executing SDLC task",
		"task_id", task.ID,
		logging.FieldProjectID, task.ProjectID,
		"command", spec.Command,
		logging.FieldPodName, podName,
	)

	// 1. Clone repo to worker pod
	if e.podGitOps == nil {
		return &domain.BuildResult{
			Success:    false,
			Error:      "pod git operations not configured",
			DurationMs: time.Since(start).Milliseconds(),
		}
	}

	cloneResult := e.podGitOps.CloneRepo(ctx, podName, workDir, spec.GitCloneURL)
	if cloneResult.Error != nil {
		return &domain.BuildResult{
			Success:    false,
			Error:      fmt.Sprintf("git clone failed: %v", cloneResult.Error),
			DurationMs: time.Since(start).Milliseconds(),
		}
	}

	// 1b. Reset workspace to main to ensure a known-good starting state.
	// Worker pods are reused across tasks and may be left on a feature branch
	// from a previous command (e.g., `sdlc branch create` switches branches).
	if err := e.resetToMain(ctx, podName, workDir); err != nil {
		log.Warn("failed to reset workspace to main, continuing",
			"task_id", task.ID,
			logging.FieldError, err,
		)
	}

	// 2. Ensure .sdlc/ is initialized (auto-init for skeleton projects)
	if err := e.ensureSDLCInit(ctx, podName, workDir); err != nil {
		log.Warn("sdlc init check failed, continuing anyway",
			"task_id", task.ID,
			logging.FieldError, err,
		)
	}

	// 3. Run SDLC CLI command
	output, err := e.runSDLCCommand(ctx, podName, workDir, spec.Command, spec.Args)
	if err != nil {
		return &domain.BuildResult{
			Success:    false,
			Error:      fmt.Sprintf("sdlc command failed: %v", err),
			Output:     output,
			DurationMs: time.Since(start).Milliseconds(),
		}
	}

	result := &domain.BuildResult{
		Success:    true,
		Output:     output,
		DurationMs: time.Since(start).Milliseconds(),
	}

	// 4. Commit and push if enabled
	if spec.AutoCommit {
		commitMsg := fmt.Sprintf("sdlc: %s", spec.Command)
		gitResult := e.podGitOps.CommitAndPush(ctx, podName, workDir, commitMsg, spec.AutoPush)
		if gitResult.Error != nil {
			result.Success = false
			result.Error = fmt.Sprintf("git operations failed: %v", gitResult.Error)
			return result
		}
		if gitResult.HasChanges {
			result.CommitSHA = gitResult.CommitSHA
			result.FilesChanged = gitResult.FilesChanged
			log.Info("SDLC changes committed",
				"task_id", task.ID,
				"commit", gitResult.CommitSHA,
				"files", len(gitResult.FilesChanged),
				"pushed", gitResult.Pushed,
			)
		}
	}

	log.Info("SDLC task completed",
		"task_id", task.ID,
		"command", spec.Command,
		logging.FieldDuration, result.DurationMs,
	)

	return result
}

// resetToMain resets the workspace to the main branch with a clean state.
// This ensures each SDLC task starts from a known-good state regardless of
// what previous tasks may have done (e.g., switched to a feature branch).
func (e *SDLCTaskExecutor) resetToMain(ctx context.Context, podName, workDir string) error {
	resetScript := fmt.Sprintf(
		"cd %s && git fetch origin && git checkout main && git reset --hard origin/main",
		workDir,
	)

	args := []string{
		"exec", "-n", e.namespace, podName, "--",
		"sh", "-c", resetScript,
	}

	cmd := exec.CommandContext(ctx, "kubectl", args...)
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("reset to main: %s: %w", stderr.String(), err)
	}

	return nil
}

// ensureSDLCInit checks if .sdlc/ exists and runs `sdlc init` if it doesn't.
// This enables SDLC operations on skeleton projects that don't have .sdlc/ pre-initialized.
func (e *SDLCTaskExecutor) ensureSDLCInit(ctx context.Context, podName, workDir string) error {
	log := logging.FromContext(ctx).WithWorker("sdlc-task-executor")

	// Check if .sdlc/ directory exists
	checkArgs := []string{
		"exec", "-n", e.namespace, podName, "--",
		"sh", "-c",
		fmt.Sprintf("test -d %s/.sdlc && echo exists || echo missing", workDir),
	}

	cmd := exec.CommandContext(ctx, "kubectl", checkArgs...)
	var stdout bytes.Buffer
	cmd.Stdout = &stdout

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("check .sdlc failed: %w", err)
	}

	if strings.TrimSpace(stdout.String()) == "exists" {
		return nil // Already initialized
	}

	// Run sdlc init
	log.Info("initializing .sdlc directory", logging.FieldPodName, podName, "workDir", workDir)

	initArgs := []string{
		"exec", "-n", e.namespace, podName, "--",
		"sh", "-c",
		fmt.Sprintf("cd %s && sdlc init --json", workDir),
	}

	initCmd := exec.CommandContext(ctx, "kubectl", initArgs...)
	var initStdout, initStderr bytes.Buffer
	initCmd.Stdout = &initStdout
	initCmd.Stderr = &initStderr

	if err := initCmd.Run(); err != nil {
		return fmt.Errorf("sdlc init failed: %w: %s", err, initStderr.String())
	}

	log.Info("sdlc initialized", logging.FieldPodName, podName, "output", initStdout.String())
	return nil
}

// runSDLCCommand executes the sdlc CLI command in the worker pod.
func (e *SDLCTaskExecutor) runSDLCCommand(ctx context.Context, podName, workDir, command string, args []string) (string, error) {
	// Build the full command: sdlc {command} {args...} --json
	// Each argument is quoted to handle values with spaces (e.g., --title "My Feature")
	sdlcArgs := []string{shellQuote(command)}
	for _, arg := range args {
		sdlcArgs = append(sdlcArgs, shellQuote(arg))
	}
	sdlcArgs = append(sdlcArgs, "--json")

	// Build kubectl exec command
	kubectlArgs := []string{
		"exec", "-n", e.namespace, podName, "--",
		"sh", "-c",
		fmt.Sprintf("cd %s && sdlc %s", workDir, strings.Join(sdlcArgs, " ")),
	}

	cmd := exec.CommandContext(ctx, "kubectl", kubectlArgs...)
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	if err := cmd.Run(); err != nil {
		return stdout.String(), fmt.Errorf("%s: %s", err, stderr.String())
	}

	return stdout.String(), nil
}

// parsedSDLCSpec holds typed fields extracted from the task spec map.
type parsedSDLCSpec struct {
	Command     string
	Args        []string
	GitCloneURL string
	AutoCommit  bool
	AutoPush    bool
}

// parseSpec extracts typed SDLCTaskSpec fields from the generic map[string]any.
func (e *SDLCTaskExecutor) parseSpec(spec map[string]any) (*parsedSDLCSpec, error) {
	command, _ := spec["command"].(string)
	if command == "" {
		return nil, fmt.Errorf("command is required")
	}

	gitCloneURL, _ := spec["git_clone_url"].(string)
	if gitCloneURL == "" {
		return nil, fmt.Errorf("git_clone_url is required")
	}

	autoCommit, _ := spec["auto_commit"].(bool)
	autoPush, _ := spec["auto_push"].(bool)

	// Parse args (can be []string or []any from JSON)
	var args []string
	if argsRaw, ok := spec["args"]; ok {
		switch v := argsRaw.(type) {
		case []string:
			args = v
		case []any:
			for _, a := range v {
				if s, ok := a.(string); ok {
					args = append(args, s)
				}
			}
		}
	}

	return &parsedSDLCSpec{
		Command:     command,
		Args:        args,
		GitCloneURL: gitCloneURL,
		AutoCommit:  autoCommit,
		AutoPush:    autoPush,
	}, nil
}

// SDLCResult represents the parsed JSON output from an SDLC command.
// Used by WorkerSDLCExecutor to parse results.
type SDLCResult struct {
	Success bool            `json:"success"`
	Data    json.RawMessage `json:"data,omitempty"`
	Error   string          `json:"error,omitempty"`
}

// shellQuote escapes a string for safe use in a shell command.
// It wraps the string in single quotes and escapes any single quotes within.
func shellQuote(s string) string {
	// If the string contains no special characters, return as-is
	if !strings.ContainsAny(s, " \t\n'\"\\$`!*?[]{}|&;<>()") {
		return s
	}
	// Escape single quotes by ending the quoted section, adding an escaped quote, and restarting
	// 'foo'bar' becomes 'foo'"'"'bar'
	escaped := strings.ReplaceAll(s, "'", "'\"'\"'")
	return "'" + escaped + "'"
}
