package skills

import (
	"bytes"
	"context"
	"fmt"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

// Executor ejecuta scripts de skills de forma segura con allowlist de interpreters.
type Executor struct {
	allowedInterpreters []string
	timeout             time.Duration
}

// NewExecutor crea un nuevo Executor con la configuracion dada.
// Si allowedInterpreters esta vacio, se usa un default de ["bash", "sh"].
func NewExecutor(allowedInterpreters []string, timeout time.Duration) *Executor {
	if len(allowedInterpreters) == 0 {
		allowedInterpreters = []string{"bash", "sh"}
	}
	if timeout == 0 {
		timeout = 60 * time.Second
	}
	return &Executor{
		allowedInterpreters: allowedInterpreters,
		timeout:             timeout,
	}
}

// Run ejecuta un script de skill con los argumentos dados.
// scriptPath es la ruta absoluta al script.
// args son los argumentos pasados al script.
//
// El script debe tener una extension reconocida (.sh, .bash, .py, etc.) o
// un shebang que indique el interprete.
//
// Retorna stdout+stderr combinados y error si falla.
func (e *Executor) Run(ctx context.Context, scriptPath string, args []string) (string, error) {
	// Inferir interprete desde extension
	interpreter, err := e.inferInterpreter(scriptPath)
	if err != nil {
		return "", err
	}

	// Validar que el interprete esta en la allowlist
	if !e.isAllowed(interpreter) {
		return "", fmt.Errorf("interpreter not allowed: %s (allowed: %v)", interpreter, e.allowedInterpreters)
	}

	// Construir comando
	cmdArgs := append([]string{scriptPath}, args...)
	cmd := exec.CommandContext(ctx, interpreter, cmdArgs...)

	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	// Aplicar timeout
	timeoutCtx, cancel := context.WithTimeout(ctx, e.timeout)
	defer cancel()

	cmd = exec.CommandContext(timeoutCtx, interpreter, cmdArgs...)
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	err = cmd.Run()
	output := stdout.String() + stderr.String()

	if timeoutCtx.Err() == context.DeadlineExceeded {
		return output, fmt.Errorf("script timeout exceeded (%s)", e.timeout)
	}

	if err != nil {
		return output, fmt.Errorf("script failed: %w", err)
	}

	return output, nil
}

// inferInterpreter detecta el interprete a usar desde la extension del archivo.
func (e *Executor) inferInterpreter(path string) (string, error) {
	ext := strings.ToLower(filepath.Ext(path))

	switch ext {
	case ".sh", ".bash":
		return "bash", nil
	case ".py":
		return "python3", nil
	case ".rb":
		return "ruby", nil
	case ".js":
		return "node", nil
	default:
		return "", fmt.Errorf("unsupported script extension: %s", ext)
	}
}

// isAllowed verifica si un interprete esta en la allowlist.
func (e *Executor) isAllowed(interpreter string) bool {
	for _, allowed := range e.allowedInterpreters {
		if allowed == interpreter {
			return true
		}
	}
	return false
}
