package main

import (
	"context"
	"fmt"
	"os/exec"
	"strings"
	"time"
)

const gitTimeoutSeconds = 120

// gitRun ejecuta git con argumentos en cwd. Devuelve stdout, stderr, exitCode.
func gitRun(cwd string, args ...string) (string, string, int, error) {
	if _, err := exec.LookPath("git"); err != nil {
		return "", "", -1, fmt.Errorf("git binary not in PATH")
	}
	ctx, cancel := context.WithTimeout(context.Background(), gitTimeoutSeconds*time.Second)
	defer cancel()
	c := exec.CommandContext(ctx, "git", args...) // #nosec G204 — args controlled
	if cwd != "" {
		c.Dir = cwd
	}
	var stdout, stderr strings.Builder
	c.Stdout = &stdout
	c.Stderr = &stderr
	err := c.Run()
	exitCode := 0
	if err != nil {
		if ee, ok := err.(*exec.ExitError); ok {
			exitCode = ee.ExitCode()
		} else {
			return stdout.String(), stderr.String(), -1, err
		}
	}
	return stdout.String(), stderr.String(), exitCode, nil
}

// runGitClone clona un repo a dest. Valida dest contra cap.PathsAllowed.
func runGitClone(cap *Capability, args map[string]any) (any, int, error) {
	url := mapStringField(args, "url")
	dest := mapStringField(args, "dest")
	if url == "" || dest == "" {
		return nil, -1, fmt.Errorf("url and dest required")
	}
	if !isPathAllowed(dest, cap.PathsAllowed) {
		return nil, -1, fmt.Errorf("dest not allowed by manifest: %s", dest)
	}
	stdout, stderr, code, err := gitRun("", "clone", url, dest)
	if err != nil {
		return nil, -1, fmt.Errorf("git clone: %w (%s)", err, stderr)
	}
	if code != 0 {
		return map[string]any{
			"stdout":    stdout,
			"stderr":    stderr,
			"exit_code": code,
		}, code, fmt.Errorf("git clone exit=%d: %s", code, stderr)
	}
	// recoge HEAD commit + branch
	sha, _, _, _ := gitRun(dest, "rev-parse", "HEAD")
	branch, _, _, _ := gitRun(dest, "rev-parse", "--abbrev-ref", "HEAD")
	return map[string]any{
		"dest":       dest,
		"commit_sha": strings.TrimSpace(sha),
		"branch":     strings.TrimSpace(branch),
		"stdout":     stdout,
		"exit_code":  0,
	}, 0, nil
}

// runGitCommit hace git add + commit en repo. files opcional; vacio = -am.
func runGitCommit(cap *Capability, args map[string]any) (any, int, error) {
	repo := mapStringField(args, "repo")
	msg := mapStringField(args, "message")
	if repo == "" || msg == "" {
		return nil, -1, fmt.Errorf("repo and message required")
	}
	if !isPathAllowed(repo, cap.PathsAllowed) {
		return nil, -1, fmt.Errorf("repo not allowed by manifest: %s", repo)
	}
	var files []string
	if raw, ok := args["files"]; ok && raw != nil {
		if arr, ok := raw.([]any); ok {
			for _, v := range arr {
				if s, ok := v.(string); ok {
					files = append(files, s)
				}
			}
		}
	}
	if len(files) > 0 {
		addArgs := append([]string{"add", "--"}, files...)
		_, addStderr, code, err := gitRun(repo, addArgs...)
		if err != nil || code != 0 {
			return nil, code, fmt.Errorf("git add: %s", addStderr)
		}
		_, cStderr, code, err := gitRun(repo, "commit", "-m", msg)
		if err != nil || code != 0 {
			return map[string]any{"stderr": cStderr, "exit_code": code}, code, fmt.Errorf("git commit exit=%d: %s", code, cStderr)
		}
	} else {
		_, cStderr, code, err := gitRun(repo, "commit", "-am", msg)
		if err != nil || code != 0 {
			return map[string]any{"stderr": cStderr, "exit_code": code}, code, fmt.Errorf("git commit exit=%d: %s", code, cStderr)
		}
	}
	sha, _, _, _ := gitRun(repo, "rev-parse", "HEAD")
	return map[string]any{
		"repo":       repo,
		"commit_sha": strings.TrimSpace(sha),
		"exit_code":  0,
	}, 0, nil
}

// runGitPush push del repo. remote default "origin", branch default "HEAD".
func runGitPush(cap *Capability, args map[string]any) (any, int, error) {
	repo := mapStringField(args, "repo")
	if repo == "" {
		return nil, -1, fmt.Errorf("repo required")
	}
	if !isPathAllowed(repo, cap.PathsAllowed) {
		return nil, -1, fmt.Errorf("repo not allowed by manifest: %s", repo)
	}
	remote := mapStringField(args, "remote")
	if remote == "" {
		remote = "origin"
	}
	branch := mapStringField(args, "branch")
	if branch == "" {
		branch = "HEAD"
	}
	stdout, stderr, code, err := gitRun(repo, "push", remote, branch)
	if err != nil {
		return nil, -1, fmt.Errorf("git push: %w (%s)", err, stderr)
	}
	if code != 0 {
		return map[string]any{
			"stdout":    stdout,
			"stderr":    stderr,
			"exit_code": code,
		}, code, fmt.Errorf("git push exit=%d: %s", code, stderr)
	}
	return map[string]any{
		"ok":        true,
		"remote":    remote,
		"branch":    branch,
		"stdout":    stdout,
		"stderr":    stderr,
		"exit_code": 0,
	}, 0, nil
}
