//go:build !release
// +build !release

package mock

import (
	"fmt"
	"io"
	"os"
)

type baseRunner interface {
	SetDir(d string)
	SetEnv(e []string)
	Stdout(out io.Writer)
	Stderr(err io.Writer)
	RunExecutable(e string, p ...string) error
}

// DockerExecConfig is the configuration for an individual tool that shall be executed in docker.
type DockerExecConfig struct {
	// Image is the fully qualified docker image name that is passed to docker for this tool.
	Image string
	// Workspace is the (optional) directory to which the current working directory is mapped
	// within the docker container.
	Workspace string
}

// DockerExecRunner can be used "in place" of another ExecRunner in order to transparently
// execute commands within a docker container. One use-case is to test locally with tools
// that are not available on the current platform. When entering the run*() function of a
// step implementation, a DockerExecRunner can be wrapped around a command.Command{}
// an be configured to run certain executables within docker.
type DockerExecRunner struct {
	// Runner is the ExecRunner to which all executions are forwarded in the end.
	Runner            baseRunner
	executablesToWrap map[string]DockerExecConfig
}

// SetDir directly forwards to the provided BaseRunner.
func (d *DockerExecRunner) SetDir(dir string) {
	d.Runner.SetDir(dir)
}

// SetEnv directly forwards to the provided BaseRunner.
func (d *DockerExecRunner) SetEnv(env []string) {
	d.Runner.SetEnv(env)
}

// Stdout directly forwards to the provided BaseRunner.
func (d *DockerExecRunner) Stdout(out io.Writer) {
	d.Runner.Stdout(out)
}

// Stderr directly forwards to the provided BaseRunner.
func (d *DockerExecRunner) Stderr(err io.Writer) {
	d.Runner.Stderr(err)
}

// AddExecConfig needs to be called to store a configuration for a specific executable, in order
// to run this executable within docker.
func (d *DockerExecRunner) AddExecConfig(executable string, config DockerExecConfig) error {
	if executable == "" {
		return fmt.Errorf("'executable' needs to be provided")
	}
	if config.Image == "" {
		return fmt.Errorf("the DockerExecConfig must specify a docker image")
	}
	if d.executablesToWrap == nil {
		d.executablesToWrap = map[string]DockerExecConfig{}
	}
	d.executablesToWrap[executable] = config
	return nil
}

// RunExecutable runs the provided executable within docker, if a DockerExecConfig has been previously
// associated with this executable via AddExecConfig(). Otherwise runs it directly. The BaseRunner is
// used for execution in any case.
func (d *DockerExecRunner) RunExecutable(executable string, parameters ...string) error {
	if config, ok := d.executablesToWrap[executable]; ok {
		wrappedParameters := []string{"run", "--entrypoint=" + executable}
		if config.Workspace != "" {
			currentDir, err := os.Getwd()
			if err != nil {
				return fmt.Errorf("failed to get current directory for mounting in docker: %w", err)
			}
			wrappedParameters = append(wrappedParameters, "-v", currentDir+":"+config.Workspace)
		}
		wrappedParameters = append(wrappedParameters, config.Image)
		wrappedParameters = append(wrappedParameters, parameters...)
		executable = "docker"
		parameters = wrappedParameters
	}
	return d.Runner.RunExecutable(executable, parameters...)
}
