package profile

import (
	"bytes"
	"context"
	"errors"
	"os/exec"
	"strings"
	"time"
)

var ErrSudoRequired = errors.New("sudo required")

type Runner struct {
	UseSudo bool
	Timeout time.Duration
}

func (r Runner) run(name string, args ...string) (string, error) {
	return r.runWithSudo(r.UseSudo, name, args...)
}

func (r Runner) runRaw(name string, args ...string) (string, error) {
	return r.runWithSudo(false, name, args...)
}

func (r Runner) runWithSudo(useSudo bool, name string, args ...string) (string, error) {
	ctx := context.Background()
	if r.Timeout <= 0 {
		r.Timeout = 2 * time.Second
	}
	ctx, cancel := context.WithTimeout(ctx, r.Timeout)
	defer cancel()

	cmdName := name
	cmdArgs := args
	if useSudo {
		// Non-interactive sudo so GUI does not freeze.
		cmdName = "sudo"
		cmdArgs = append([]string{"-n", name}, args...)
	}

	cmd := exec.CommandContext(ctx, cmdName, cmdArgs...)
	var out bytes.Buffer
	var stderr bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &stderr
	if err := cmd.Run(); err != nil {
		se := strings.TrimSpace(stderr.String())
		// Common sudo errors when credentials are not cached.
		if useSudo && (strings.Contains(se, "a password is required") || strings.Contains(se, "may not run sudo") || strings.Contains(se, "sudo:")) {
			return "", ErrSudoRequired
		}
		if ctx.Err() != nil {
			return "", ctx.Err()
		}
		// Return stderr as part of error context, but keep output string clean.
		if se != "" {
			return "", errors.New(se)
		}
		return "", err
	}
	return strings.TrimSpace(out.String()), nil
}

// try runs a command and returns either its stdout or a formatted error line.
func (r Runner) try(title, name string, args ...string) string {
	out, err := r.run(name, args...)
	if err == nil {
		if out == "" {
			return title + ": (no output)"
		}
		return out
	}
	if errors.Is(err, ErrSudoRequired) {
		return title + ": sudo required (run `sudo -v` in a terminal, then Refresh)"
	}
	return title + ": ERROR: " + err.Error()
}

func (r Runner) tryRaw(title, name string, args ...string) string {
	out, err := r.runRaw(name, args...)
	if err == nil {
		if out == "" {
			return title + ": (no output)"
		}
		return out
	}
	if errors.Is(err, ErrSudoRequired) {
		return title + ": sudo required (run `sudo -v` in a terminal, then Refresh)"
	}
	return title + ": ERROR: " + err.Error()
}
