// Package gitutil wraps the few git operations the agent needs. Credentials
// are passed in per-call and never written to disk — every command sets them
// via -c credential helpers for the lifetime of the subprocess.
package gitutil

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

// Creds is a username/password. We pass them through GIT_ASKPASS so they
// never appear on the command line or in process listings.
type Creds struct {
	Username string
	Password string
}

// Fetch runs `git fetch --prune origin`. Uses the per-command credential
// helper to inject creds without touching the repo's stored config.
func Fetch(ctx context.Context, repoDir string, c Creds) (string, error) {
	return runGit(ctx, repoDir, c, "fetch", "--prune", "origin")
}

// Checkout switches to branch and updates the working tree.
func Checkout(ctx context.Context, repoDir, branch string, c Creds) (string, error) {
	return runGit(ctx, repoDir, c, "checkout", "-f", branch)
}

// Pull fast-forwards the branch to match origin. Safe no-op if up to date.
func Pull(ctx context.Context, repoDir string, c Creds) (string, error) {
	return runGit(ctx, repoDir, c, "pull", "--ff-only")
}

// Probe validates that the credentials work for a given remote. Used at
// login. Tries `git ls-remote <origin> HEAD`; succeeds even on an empty repo.
func Probe(ctx context.Context, repoDir string, c Creds) error {
	_, err := runGit(ctx, repoDir, c, "ls-remote", "--heads", "origin", "HEAD")
	return err
}

// ListBranches lists local branches. Cheap; no network.
func ListBranches(ctx context.Context, repoDir string) ([]string, error) {
	cmd := exec.CommandContext(ctx, "git", "for-each-ref", "--format=%(refname:short)", "refs/heads/")
	cmd.Dir = repoDir
	out, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("git for-each-ref: %w: %s", err, out)
	}
	s := strings.TrimSpace(string(out))
	if s == "" {
		return nil, nil
	}
	return strings.Split(s, "\n"), nil
}

func runGit(ctx context.Context, repoDir string, c Creds, args ...string) (string, error) {
	cmd := exec.CommandContext(ctx, "git", args...)
	cmd.Dir = repoDir
	// GIT_ASKPASS gives us a per-command credential helper. We just echo the
	// creds back. The "username" / "password" args are sent to the script's
	// argv by git.
	askpass := fmt.Sprintf(`#!/bin/sh
case "$1" in
  username) echo %q ;;
  password) echo %q ;;
esac`, c.Username, c.Password)
	cmd.Env = append(cmd.Environ(),
		"GIT_ASKPASS=/dev/stdin",
		"GIT_TERMINAL_PROMPT=0",
	)
	// ponytail: passing askpass via stdin is portable across Linux/macOS.
	cmd.Stdin = strings.NewReader(askpass)
	out, err := cmd.CombinedOutput()
	if err != nil {
		return string(out), fmt.Errorf("git %s: %w: %s", strings.Join(args, " "), err, out)
	}
	return string(out), nil
}
