package exec

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"os/exec"
)

type CommandStructuredArgs struct {
	Subcmd []string
	Opts   []string
	Args   []string
}

func WithStructuredArgs(args CommandStructuredArgs) CommandMutate {
	return func(cmd *exec.Cmd) {
		if len(args.Subcmd) != 0 {
			cmd.Args = append(cmd.Args, args.Subcmd...)
		}
		if len(args.Opts) != 0 {
			cmd.Args = append(cmd.Args, args.Opts...)
		}
		if len(args.Args) != 0 {
			cmd.Args = append(cmd.Args, args.Args...)
		}
	}
}

func WithEnv(envs map[string]string) CommandMutate {
	return func(cmd *exec.Cmd) {
		for k, v := range envs {
			cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v))
		}
	}
}

type CommandMutate func(cmd *exec.Cmd)

type CommandOutput struct {
	Stdout   string
	Stderr   string
	ExitCode int32
}

func (o CommandOutput) String() string {
	return fmt.Sprintf("exitcode: %d, stdout: %s, stderr: %s, ", o.ExitCode, o.Stdout, o.Stderr)
}

type CommandBuilder struct {
	cmd *exec.Cmd
}

func NewCommandBuilder(ctx context.Context, command string, m ...CommandMutate) *CommandBuilder {
	cmd := exec.CommandContext(ctx, command)
	for _, mutate := range m {
		mutate(cmd)
	}

	return &CommandBuilder{
		cmd,
	}
}

func (e *CommandBuilder) Mutate(m CommandMutate) {
	m(e.cmd)
}

func (e *CommandBuilder) GetFullCommand() string {
	return e.cmd.String()
}

func (e *CommandBuilder) GetCommand() *exec.Cmd {
	return e.cmd
}

func (e *CommandBuilder) Run() (*CommandOutput, error) {
	var stdout, stderr bytes.Buffer
	e.cmd.Stdout = &stdout
	e.cmd.Stderr = &stderr

	err := e.cmd.Run()
	exitErr := &exec.ExitError{}
	isExitErr := errors.As(err, &exitErr)
	if err != nil && !isExitErr {
		return nil, fmt.Errorf("run command failed: %w", err)
	}

	return &CommandOutput{
		Stdout:   stdout.String(),
		Stderr:   stderr.String(),
		ExitCode: int32(e.cmd.ProcessState.ExitCode()),
	}, nil
}
