package chat

import (
	"duckduckgo-chat-cli/internal/config"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/AlecAivazis/survey/v2"
	"github.com/fatih/color"
)

// HandlePMPCommand processes the /pmp command for project prompt generation
func HandlePMPCommand(c *Chat, input string, cfg *config.Config) {
	commandInput := strings.TrimPrefix(input, "/pmp")
	commandInput = strings.TrimSpace(commandInput)

	// Check if PMP is installed
	if !isPMPInstalled() {
		color.Yellow("⚠️ PMP (Prompt My Project) is not installed.")
		color.White("PMP is a tool to generate structured prompts from your source code.")

		if offerPMPInstallation() {
			if installPMP() {
				color.Green("✅ PMP installed successfully!")
			} else {
				color.Red("❌ Failed to install PMP. Please install manually.")
				showManualInstallInstructions()
				return
			}
		} else {
			color.Yellow("Installation cancelled. You can install PMP manually:")
			showManualInstallInstructions()
			return
		}
	}

	// Parse the command arguments
	if commandInput == "" || commandInput == "help" {
		showPMPHelp()
		return
	}

	var path, userRequest string
	var args []string

	// Check if there's a -- separator for user request
	if strings.Contains(commandInput, " -- ") {
		parts := strings.SplitN(commandInput, " -- ", 2)
		commandInput = strings.TrimSpace(parts[0])
		if len(parts) > 1 {
			userRequest = strings.TrimSpace(parts[1])
		}
	}

	// Parse path and additional arguments
	cmdParts := strings.Fields(commandInput)
	if len(cmdParts) > 0 {
		path = cmdParts[0]
		if len(cmdParts) > 1 {
			args = cmdParts[1:]
		}
	}

	if path == "" {
		path = "." // Default to current directory
	}

	// Validate path exists
	if _, err := os.Stat(path); os.IsNotExist(err) {
		color.Red("❌ Path does not exist: %s", path)
		return
	}

	color.Yellow("🔄 Generating project prompt for: %s", path)

	// Execute PMP command
	prompt, err := executePMP(path, args)
	if err != nil {
		color.Red("❌ Error executing PMP: %v", err)
		return
	}

	if prompt == "" {
		color.Yellow("⚠️ No prompt generated (empty output)")
		return
	}

	// Add the generated prompt to context
	c.Messages = append(c.Messages, Message{
		Role:    "user",
		Content: fmt.Sprintf("[Project Context - Generated by PMP]\nPath: %s\n\n%s", path, prompt),
	})

	color.Green("✅ Project prompt added to context (%d characters)", len(prompt))

	// If user provided a specific request, process it with the project context
	if userRequest != "" {
		color.Cyan("Processing your request about the project...")
		ProcessInput(c, userRequest, cfg)
	} else {
		color.White("Project structure and code added to context. You can now ask questions about the project.")
	}
}

// isPMPInstalled checks if PMP is available in the system PATH
func isPMPInstalled() bool {
	_, err := exec.LookPath("pmp")
	return err == nil
}

// offerPMPInstallation asks the user if they want to install PMP
func offerPMPInstallation() bool {
	var install bool
	prompt := &survey.Confirm{
		Message: "PMP (Prompt My Project) is not installed. Would you like to install it automatically?",
		Help:    "PMP is a tool to generate structured prompts from your source code.",
		Default: true,
	}
	err := survey.AskOne(prompt, &install, survey.WithStdio(os.Stdin, os.Stdout, os.Stderr))
	if err != nil {
		return false // Default to no on any error
	}
	return install
}

// installPMP attempts to install PMP using the appropriate method for the OS
func installPMP() bool {
	color.Yellow("📦 Installing PMP...")

	// Try go install first (if Go is available)
	if isGoInstalled() {
		color.Yellow("Installing via Go...")
		// #nosec G204 - Static command with fixed arguments for Go package installation
		cmd := exec.Command("go", "install", "github.com/benoitpetit/prompt-my-project@latest")
		if err := cmd.Run(); err == nil {
			// After go install, check if PMP is now accessible
			if isPMPInstalled() {
				return true
			}
			// If not in PATH, try to find it in common Go locations
			if findAndSetPMPPath() {
				return true
			}
			color.Yellow("Go install completed but PMP not found in PATH. Trying script installation...")
		} else {
			color.Yellow("Go install failed, trying script installation...")
		}
	}

	// Try script installation
	switch runtime.GOOS {
	case "linux", "darwin":
		return installPMPUnix()
	case "windows":
		return installPMPWindows()
	default:
		color.Red("Unsupported operating system for automatic installation")
		return false
	}
}

// isGoInstalled checks if Go is available
func isGoInstalled() bool {
	_, err := exec.LookPath("go")
	return err == nil
}

// installPMPUnix installs PMP on Unix-like systems
func installPMPUnix() bool {
	script := `curl -fsSL https://raw.githubusercontent.com/benoitpetit/prompt-my-project/master/scripts/install.sh | bash`
	// #nosec G204 - Static script for PMP installation from trusted source
	cmd := exec.Command("bash", "-c", script)
	err := cmd.Run()
	return err == nil
}

// installPMPWindows installs PMP on Windows
func installPMPWindows() bool {
	script := `irm https://raw.githubusercontent.com/benoitpetit/prompt-my-project/master/scripts/install.ps1 | iex`
	// #nosec G204 - Static PowerShell script for PMP installation from trusted source
	cmd := exec.Command("powershell", "-Command", script)
	err := cmd.Run()
	return err == nil
}

// findAndSetPMPPath tries to locate PMP in common Go installation directories
func findAndSetPMPPath() bool {
	goBinPaths := getGoBinPaths()

	for _, binPath := range goBinPaths {
		pmpPath := fmt.Sprintf("%s/pmp", binPath)
		if runtime.GOOS == "windows" {
			pmpPath = fmt.Sprintf("%s\\pmp.exe", binPath)
		}

		// Check if PMP exists at this location
		if _, err := os.Stat(pmpPath); err == nil {
			color.Yellow("Found PMP at: %s", pmpPath)
			// Store the full path for later use
			setPMPExecutablePath(pmpPath)
			return true
		}
	}

	return false
}

// getGoBinPaths returns common Go binary installation paths
func getGoBinPaths() []string {
	var paths []string

	// Try to get GOPATH and GOBIN from environment
	if gobin := os.Getenv("GOBIN"); gobin != "" {
		paths = append(paths, gobin)
	}

	if gopath := os.Getenv("GOPATH"); gopath != "" {
		paths = append(paths, fmt.Sprintf("%s/bin", gopath))
	}

	// Default Go installation paths
	if home, err := os.UserHomeDir(); err == nil {
		paths = append(paths, fmt.Sprintf("%s/go/bin", home))
	}

	// Try to get Go's default GOPATH using go env
	// #nosec G204 - Static command to get Go environment variable
	if cmd := exec.Command("go", "env", "GOPATH"); cmd != nil {
		if output, err := cmd.Output(); err == nil {
			gopath := strings.TrimSpace(string(output))
			if gopath != "" {
				paths = append(paths, fmt.Sprintf("%s/bin", gopath))
			}
		}
	}

	return paths
}

var pmpExecutablePath string

// setPMPExecutablePath stores the full path to the PMP executable
func setPMPExecutablePath(path string) {
	pmpExecutablePath = path
}

// getPMPExecutable returns the PMP executable path (full path if set, otherwise "pmp")
func getPMPExecutable() string {
	if pmpExecutablePath != "" {
		return pmpExecutablePath
	}
	return "pmp"
}

// validatePMPExecutable validates that the PMP executable is safe to run
func validatePMPExecutable(pmpExe string) error {
	// If it's just "pmp", rely on PATH lookup which is safer
	if pmpExe == "pmp" {
		if _, err := exec.LookPath("pmp"); err != nil {
			return fmt.Errorf("pmp not found in PATH: %v", err)
		}
		return nil
	}

	// For full paths, validate the executable
	if !filepath.IsAbs(pmpExe) {
		return fmt.Errorf("executable path must be absolute: %s", pmpExe)
	}

	// Check if the file exists and is executable
	if _, err := os.Stat(pmpExe); err != nil {
		return fmt.Errorf("executable not found: %s", pmpExe)
	}

	// Basic validation: ensure the executable name contains "pmp"
	executableName := filepath.Base(pmpExe)
	if !strings.Contains(strings.ToLower(executableName), "pmp") {
		return fmt.Errorf("executable name does not appear to be PMP: %s", executableName)
	}

	return nil
}

// executePMP runs the PMP command and returns the generated prompt
func executePMP(path string, args []string) (string, error) {
	// Build the command arguments
	cmdArgs := []string{"--format", "stdout"}
	cmdArgs = append(cmdArgs, args...)
	cmdArgs = append(cmdArgs, path)

	// Get and validate the PMP executable path
	pmpExe := getPMPExecutable()
	if err := validatePMPExecutable(pmpExe); err != nil {
		return "", fmt.Errorf("invalid PMP executable: %v", err)
	}

	// Execute PMP using the validated executable path
	// #nosec G204 - pmpExe is validated above to ensure it's a legitimate PMP executable
	cmd := exec.Command(pmpExe, cmdArgs...)
	output, err := cmd.Output()
	if err != nil {
		return "", fmt.Errorf("pmp execution failed: %v", err)
	}

	return string(output), nil
}

// showPMPHelp displays usage information for the PMP command
func showPMPHelp() {
	color.Yellow("📄 PMP (Prompt My Project) Command Help")
	color.White("")
	color.White("Usage: /pmp [path] [options] [-- request]")
	color.White("")
	color.White("Examples:")
	color.White("  /pmp                                    - Generate prompt for current directory")
	color.White("  /pmp ./src                              - Generate prompt for src directory")
	color.White("  /pmp . -i \"*.go\" -i \"*.md\"            - Include only Go and Markdown files")
	color.White("  /pmp . -e \"test/*\" -e \"vendor/*\"      - Exclude test and vendor directories")
	color.White("  /pmp . -- analyze this codebase        - Generate prompt and ask about it")
	color.White("")
	color.White("Common PMP Options:")
	color.White("  -i, --include <pattern>     Include only files matching pattern")
	color.White("  -e, --exclude <pattern>     Exclude files matching pattern")
	color.White("  --min-size <size>           Minimum file size (default: 1KB)")
	color.White("  --max-size <size>           Maximum file size (default: 100MB)")
	color.White("  --max-files <count>         Maximum number of files (default: 500)")
	color.White("  --no-gitignore             Ignore .gitignore file")
	color.White("")
	color.White("The generated project structure and code will be added to the chat context.")
}

// showManualInstallInstructions displays manual installation instructions
func showManualInstallInstructions() {
	color.White("")
	color.Yellow("Manual Installation Instructions:")
	color.White("")

	switch runtime.GOOS {
	case "linux", "darwin":
		color.White("Using script:")
		color.Cyan("  curl -fsSL https://raw.githubusercontent.com/benoitpetit/prompt-my-project/master/scripts/install.sh | bash")
		color.White("")
		if isGoInstalled() {
			color.White("Using Go (if you have Go installed):")
			color.Cyan("  go install github.com/benoitpetit/prompt-my-project@latest")
			color.Yellow("  # Make sure ~/go/bin is in your PATH:")
			color.Cyan("  export PATH=$PATH:$(go env GOPATH)/bin")
			color.White("")
		}
	case "windows":
		color.White("Using PowerShell:")
		color.Cyan("  irm https://raw.githubusercontent.com/benoitpetit/prompt-my-project/master/scripts/install.ps1 | iex")
		color.White("")
		if isGoInstalled() {
			color.White("Using Go (if you have Go installed):")
			color.Cyan("  go install github.com/benoitpetit/prompt-my-project@latest")
			color.Yellow("  # Make sure Go bin directory is in your PATH")
			color.White("")
		}
	}

	color.White("Manual download:")
	color.White("1. Download from: https://github.com/benoitpetit/prompt-my-project/releases")
	color.White("2. Extract and add to your PATH")
	color.White("")

	if isGoInstalled() {
		color.Yellow("💡 Tip: If 'go install' was used but PMP is not found:")
		goBinPaths := getGoBinPaths()
		for _, path := range goBinPaths {
			color.White("   Check: %s", path)
		}
		color.White("   Add the correct path to your shell's PATH variable")
		color.White("")
	}

	color.White("After installation, use '/pmp' again to generate project prompts.")
}
