package git

import (
	"cmp"
	"context"
	"errors"
	"fmt"
	"io"
	"os/exec"
	"strings"
	"syscall"
)

var gitEnv = []string{
	"GIT_CONFIG_GLOBAL=/dev/null",
	"GIT_CONFIG_SYSTEM=/dev/null",
}

type cmdOpts struct {
	Cmd         []string
	GitProtocol string
	Stdin       io.Reader
	Stdout      io.Writer
	Stderr      io.Writer
}

func (g *Repo) gitCmd(ctx context.Context, opts cmdOpts) error {
	opts.Cmd = append(opts.Cmd, ".")
	cmd := exec.CommandContext(ctx, "git", opts.Cmd...)
	cmd.Dir = g.path
	cmd.Env = append(gitEnv, fmt.Sprintf("GIT_PROTOCOL=%s", opts.GitProtocol))
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
	cmd.Stdin = cmp.Or[io.Reader](opts.Stdin, strings.NewReader(""))
	cmd.Stdout = cmp.Or(opts.Stdout, io.Discard)
	cmd.Stderr = cmp.Or(opts.Stderr, io.Discard)
	return cmd.Run()
}

func (g *Repo) runGitCmd(cmd string, args ...string) ([]byte, error) {
	var gitArgs []string
	gitArgs = append(gitArgs, cmd)
	gitArgs = append(gitArgs, args...)
	gitCmd := exec.Command("git", gitArgs...)
	gitCmd.Dir = g.path
	gitCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
	gitCmd.Env = gitEnv

	out, err := gitCmd.Output()
	if err != nil {
		if err, ok := errors.AsType[*exec.ExitError](err); ok {
			return nil, fmt.Errorf("%w, stderr: %s", err, err.Stderr)
		}
		return nil, err
	}
	return out, nil
}

func (g *Repo) streamingGitLog(ctx context.Context, extraArgs ...string) (io.ReadCloser, error) {
	args := []string{"log", g.h.String()}
	args = append(args, extraArgs...)

	cmd := exec.CommandContext(ctx, "git", args...)
	cmd.Dir = g.path

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	return &processReader{
		Reader: stdout,
		cmd:    cmd,
		stdout: stdout,
	}, nil
}

// processReader wraps a reader and ensures the associated process is cleaned up
type processReader struct {
	io.Reader
	cmd    *exec.Cmd
	stdout io.ReadCloser
}

func (pr *processReader) Close() error {
	if err := pr.stdout.Close(); err != nil {
		return err
	}
	return pr.cmd.Wait()
}
