// Copyright 2016-2023, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/blang/semver"
	"github.com/opentracing/opentracing-go"
	"golang.org/x/mod/modfile"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/types/known/emptypb"

	"github.com/pulumi/pulumi/sdk/go/pulumi-language-go/v3/buildutil"
	"github.com/pulumi/pulumi/sdk/go/pulumi-language-go/v3/goversion"
	"github.com/pulumi/pulumi/sdk/v3/go/common/constant"
	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
	"github.com/pulumi/pulumi/sdk/v3/go/common/slice"
	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
	"github.com/pulumi/pulumi/sdk/v3/go/common/util/executable"
	"github.com/pulumi/pulumi/sdk/v3/go/common/util/fsutil"
	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
	"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil"
	"github.com/pulumi/pulumi/sdk/v3/go/common/version"
	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
	pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"

	codegen "github.com/pulumi/pulumi/pkg/v3/codegen/go"
	hclsyntax "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax"
	"github.com/pulumi/pulumi/pkg/v3/codegen/pcl"
	"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
)

// This function takes a file target to specify where to compile to.
// If `outfile` is "", the binary is compiled to a new temporary file.
// This function returns the path of the file that was produced.
func compileProgram(programDirectory string, outfile string) (string, error) {
	goFileSearchPattern := filepath.Join(programDirectory, "*.go")
	if matches, err := filepath.Glob(goFileSearchPattern); err != nil || len(matches) == 0 {
		return "", fmt.Errorf("Failed to find go files for 'go build' matching %s", goFileSearchPattern)
	}

	if outfile == "" {
		// If no outfile is supplied, write the Go binary to a temporary file.
		f, err := os.CreateTemp("", "pulumi-go.*")
		if err != nil {
			return "", fmt.Errorf("unable to create go program temp file: %w", err)
		}

		if err := f.Close(); err != nil {
			return "", fmt.Errorf("unable to close go program temp file: %w", err)
		}
		outfile = f.Name()
	}

	gobin, err := executable.FindExecutable("go")
	if err != nil {
		return "", fmt.Errorf("unable to find 'go' executable: %w", err)
	}
	logging.V(5).Infof("Attempting to build go program in %s with: %s build -o %s", programDirectory, gobin, outfile)
	buildCmd := exec.Command(gobin, "build", "-o", outfile)
	buildCmd.Dir = programDirectory
	buildCmd.Stdout, buildCmd.Stderr = os.Stdout, os.Stderr

	if err := buildCmd.Run(); err != nil {
		return "", fmt.Errorf("unable to run `go build`: %w", err)
	}

	return outfile, nil
}

// runParams defines the command line arguments accepted by this program.
type runParams struct {
	tracing       string
	engineAddress string
}

// parseRunParams parses the given arguments into a runParams structure,
// using the provided FlagSet.
func parseRunParams(flag *flag.FlagSet, args []string) (*runParams, error) {
	var p runParams
	flag.StringVar(&p.tracing, "tracing", "", "Emit tracing to a Zipkin-compatible tracing endpoint")
	flag.String("binary", "", "[obsolete] Look on path for a binary executable with this name")
	flag.String("buildTarget", "", "[obsolete] Path to use to output the compiled Pulumi Go program")
	flag.String("root", "", "[obsolete] Project root path to use")

	if err := flag.Parse(args); err != nil {
		return nil, err
	}

	// Pluck out the engine so we can do logging, etc.
	args = flag.Args()
	if len(args) == 0 {
		return nil, errors.New("missing required engine RPC address argument")
	}
	p.engineAddress = args[0]

	return &p, nil
}

// Launches the language host, which in turn fires up an RPC server implementing the LanguageRuntimeServer endpoint.
func main() {
	p, err := parseRunParams(flag.CommandLine, os.Args[1:])
	if err != nil {
		cmdutil.Exit(err)
	}

	logging.InitLogging(false, 0, false)
	cmdutil.InitTracing("pulumi-language-go", "pulumi-language-go", p.tracing)

	var cmd mainCmd
	if err := cmd.Run(p); err != nil {
		cmdutil.Exit(err)
	}
}

type mainCmd struct {
	Stdout io.Writer              // == os.Stdout
	Getwd  func() (string, error) // == os.Getwd
}

func (cmd *mainCmd) init() {
	if cmd.Stdout == nil {
		cmd.Stdout = os.Stdout
	}
	if cmd.Getwd == nil {
		cmd.Getwd = os.Getwd
	}
}

func (cmd *mainCmd) Run(p *runParams) error {
	cmd.init()

	cwd, err := cmd.Getwd()
	if err != nil {
		return err
	}

	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
	// map the context Done channel to the rpcutil boolean cancel channel
	cancelChannel := make(chan bool)
	go func() {
		<-ctx.Done()
		cancel() // deregister handler so we don't catch another interrupt
		close(cancelChannel)
	}()
	err = rpcutil.Healthcheck(ctx, p.engineAddress, 5*time.Minute, cancel)
	if err != nil {
		return fmt.Errorf("could not start health check host RPC server: %w", err)
	}

	// Fire up a gRPC server, letting the kernel choose a free port.
	handle, err := rpcutil.ServeWithOptions(rpcutil.ServeOptions{
		Cancel: cancelChannel,
		Init: func(srv *grpc.Server) error {
			host := newLanguageHost(p.engineAddress, cwd, p.tracing)
			pulumirpc.RegisterLanguageRuntimeServer(srv, host)
			return nil
		},
		Options: rpcutil.OpenTracingServerInterceptorOptions(nil),
	})
	if err != nil {
		return fmt.Errorf("could not start language host RPC server: %w", err)
	}

	// Otherwise, print out the port so that the spawner knows how to reach us.
	fmt.Fprintf(cmd.Stdout, "%d\n", handle.Port)

	// And finally wait for the server to stop serving.
	if err := <-handle.Done; err != nil {
		return fmt.Errorf("language host RPC stopped serving: %w", err)
	}

	return nil
}

// goLanguageHost implements the LanguageRuntimeServer interface for use as an API endpoint.
type goLanguageHost struct {
	pulumirpc.UnsafeLanguageRuntimeServer // opt out of forward compat

	cwd           string
	engineAddress string
	tracing       string
}

type goOptions struct {
	// Look on path for a binary executable with this name.
	binary string
	// Path to use to output the compiled Pulumi Go program.
	buildTarget string
}

func parseOptions(root string, options map[string]interface{}) (goOptions, error) {
	var goOptions goOptions
	if binary, ok := options["binary"]; ok {
		if binary, ok := binary.(string); ok {
			goOptions.binary = binary
		} else {
			return goOptions, errors.New("binary option must be a string")
		}
	}

	if buildTarget, ok := options["buildTarget"]; ok {
		if args, ok := buildTarget.(string); ok {
			goOptions.buildTarget = args
		} else {
			return goOptions, errors.New("buildTarget option must be a string")
		}
	}

	if goOptions.binary != "" && goOptions.buildTarget != "" {
		return goOptions, errors.New("binary and buildTarget cannot both be specified")
	}

	return goOptions, nil
}

func newLanguageHost(engineAddress, cwd, tracing string) pulumirpc.LanguageRuntimeServer {
	return &goLanguageHost{
		engineAddress: engineAddress,
		cwd:           cwd,
		tracing:       tracing,
	}
}

// modInfo is the useful portion of the output from
// 'go list -m -json' and 'go mod download -json'
// with respect to plugin acquisition.
// The listed fields are present in both command outputs.
//
// If we add fields that are only present in one or the other,
// we'll need to add a new struct type instead of re-using this.
type modInfo struct {
	// Path is the module import path.
	Path string

	// Version of the module.
	Version string

	// Dir is the directory holding the source code of the module, if any.
	Dir string
}

// findModuleSources finds the source code roots for the given modules.
//
// gobin is the path to the go binary to use.
// rootModuleDir is the path to the root directory of the program that may import the modules.
// It must contain the go.mod file for the program.
// modulePaths is a list of import paths for the modules to find.
//
// If $rootModuleDir/vendor exists, findModuleSources operates in vendor mode.
// In vendor mode, returned paths are inside the vendor directory exclusively.
func findModuleSources(ctx context.Context, gobin, rootModuleDir string, modulePaths []string) ([]modInfo, error) {
	contract.Requiref(gobin != "", "gobin", "must not be empty")
	contract.Requiref(rootModuleDir != "", "rootModuleDir", "must not be empty")
	if len(modulePaths) == 0 {
		return nil, nil
	}

	// To find the source code for a module, we would typically use
	// 'go list -m -json'.
	// Its output includes, among other things:
	//
	//    type Module struct {
	//       ...
	//       Dir string // directory holding local copy of files, if any
	//       ...
	//    }
	//
	// However, whether Dir is set or not depends on a few different factors.
	//
	//  - If the module is not in the local module cache,
	//    then Dir is always empty.
	//  - If the module is imported by the current module, then Dir is set.
	//  - If the module is not imported,
	//    then Dir is set if we run with -mod=mod.
	//  - If the module is not imported and we run without -mod=mod,
	//    then:
	//    - If there's a vendor/ directory, then Dir is not set
	//      because we're running in vendor mode.
	//    - If there's no vendor/ directory, then Dir is set
	//      if we add the module to the module cache
	//      with `go mod download $path`.
	//
	// These are all corner cases that aren't fully specified,
	// and may change between versions of Go.
	//
	// Therefore, the flow we use is:
	//
	//  - Run 'go list -m -json $path1 $path2 ...'
	//    to make a first pass at getting module information.
	//  - If there's a vendor/ directory,
	//    use the module information from the vendor directory,
	//    skipping anything that's missing.
	//    We can't make requests to download modules in vendor mode.
	//  - Otherwise, for modules with missing Dir fields,
	//    run `go mod download -json $path` to download them to the module cache
	//    and get their locations.

	modules, err := goListModules(ctx, gobin, rootModuleDir, modulePaths)
	if err != nil {
		return nil, fmt.Errorf("go list: %w", err)
	}

	// If there's a vendor directory, then we're in vendor mode.
	// In vendor mode, Dir won't be set for any modules.
	// Find these modules in the vendor directory.
	vendorDir := filepath.Join(rootModuleDir, "vendor")
	if _, err := os.Stat(vendorDir); err == nil {
		newModules := modules[:0] // in-place filter
		for _, module := range modules {
			if module.Dir == "" {
				vendoredModule := filepath.Join(vendorDir, module.Path)
				if _, err := os.Stat(vendoredModule); err == nil {
					module.Dir = vendoredModule
				}
			}

			// We can't download modules in vendor mode,
			// so we'll skip any modules that aren't already in the vendor directory.
			if module.Dir != "" {
				newModules = append(newModules, module)
			}
		}
		return newModules, nil
	}

	// We're not in vendor mode, so we can download modules and fill in missing directories.
	var (
		// Import paths of modules with no Dir field.
		missingDirs []string

		// Map from module path to index in modules.
		moduleIndex = make(map[string]int, len(modules))
	)
	for i, module := range modules {
		moduleIndex[module.Path] = i
		if module.Dir == "" {
			missingDirs = append(missingDirs, module.Path)
		}
	}

	// Fill in missing module directories with `go mod download`.
	if len(missingDirs) > 0 {
		missingMods, err := goModDownload(ctx, gobin, rootModuleDir, missingDirs)
		if err != nil {
			return nil, fmt.Errorf("go mod download: %w", err)
		}

		for _, m := range missingMods {
			if m.Dir == "" {
				continue
			}

			// If this was a module we were missing,
			// then we can fill in the directory now.
			if idx, ok := moduleIndex[m.Path]; ok && modules[idx].Dir == "" {
				modules[idx].Dir = m.Dir
			}
		}
	}

	// Any other modules with no Dir field can be discarded;
	// we tried our best to find their source.
	newModules := modules[:0] // in-place filter
	for _, module := range modules {
		if module.Dir != "" {
			newModules = append(newModules, module)
		}
	}
	return newModules, nil
}

// Runs 'go list -m' on the given list of modules
// and reports information about them.
func goListModules(ctx context.Context, gobin, dir string, modulePaths []string) ([]modInfo, error) {
	args := slice.Prealloc[string](len(modulePaths) + 3)
	args = append(args, "list", "-m", "-json")
	args = append(args, modulePaths...)

	span, ctx := opentracing.StartSpanFromContext(ctx,
		gobin+" list -m json",
		opentracing.Tag{Key: "component", Value: "exec.Command"},
		opentracing.Tag{Key: "command", Value: gobin},
		opentracing.Tag{Key: "args", Value: args})
	defer span.Finish()

	cmd := exec.CommandContext(ctx, gobin, args...)
	cmd.Dir = dir
	cmd.Stderr = os.Stderr

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, fmt.Errorf("create stdout pipe: %w", err)
	}
	if err := cmd.Start(); err != nil {
		return nil, fmt.Errorf("start command: %w", err)
	}

	var modules []modInfo
	dec := json.NewDecoder(stdout)
	for dec.More() {
		var info modInfo
		if err := dec.Decode(&info); err != nil {
			return nil, fmt.Errorf("decode module info: %w", err)
		}
		modules = append(modules, info)
	}

	if err := cmd.Wait(); err != nil {
		return nil, fmt.Errorf("wait for command: %w", err)
	}

	return modules, nil
}

// goModDownload downloads the given modules to the module cache,
// reporting information about them in the returned modInfo.
func goModDownload(ctx context.Context, gobin, dir string, modulePaths []string) ([]modInfo, error) {
	args := slice.Prealloc[string](len(modulePaths) + 3)
	args = append(args, "mod", "download", "-json")
	args = append(args, modulePaths...)

	span, ctx := opentracing.StartSpanFromContext(ctx,
		gobin+" mod download -json",
		opentracing.Tag{Key: "component", Value: "exec.Command"},
		opentracing.Tag{Key: "command", Value: gobin},
		opentracing.Tag{Key: "args", Value: args})
	defer span.Finish()

	cmd := exec.CommandContext(ctx, gobin, args...)
	cmd.Dir = dir
	cmd.Stderr = os.Stderr

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, fmt.Errorf("create stdout pipe: %w", err)
	}
	if err := cmd.Start(); err != nil {
		return nil, fmt.Errorf("start command: %w", err)
	}

	var modules []modInfo
	dec := json.NewDecoder(stdout)
	for dec.More() {
		var info modInfo
		if err := dec.Decode(&info); err != nil {
			return nil, fmt.Errorf("decode module info: %w", err)
		}
		modules = append(modules, info)
	}

	if err := cmd.Wait(); err != nil {
		return nil, fmt.Errorf("wait for command: %w", err)
	}

	return modules, nil
}

// Returns the pulumi-plugin.json if found.
// If not found, then returns nil, nil.
//
// The lookup path for pulumi-plugin.json is:
//
//   - m.Dir
//   - m.Dir/go
//   - m.Dir/go/*
//
// moduleRoot is the root directory of the module that imports this plugin.
// It is used to resolve the vendor directory.
// moduleRoot may be empty if unknown.
func (m *modInfo) readPulumiPluginJSON(moduleRoot string) (*plugin.PulumiPluginJSON, error) {
	dir := m.Dir
	contract.Assertf(dir != "", "module directory must be known")

	paths := []string{
		filepath.Join(dir, "pulumi-plugin.json"),
		filepath.Join(dir, "go", "pulumi-plugin.json"),
	}
	if path, err := filepath.Glob(filepath.Join(dir, "go", "*", "pulumi-plugin.json")); err == nil {
		paths = append(paths, path...)
	}

	for _, path := range paths {
		plugin, err := plugin.LoadPulumiPluginJSON(path)
		if err != nil {
			if os.IsNotExist(err) {
				continue
			}
			return nil, err
		}
		return plugin, nil
	}
	return nil, nil
}

func normalizeVersion(version string) (string, error) {
	v, err := semver.ParseTolerant(version)
	if err != nil {
		return "", errors.New("module does not have semver compatible version")
	}

	// psuedoversions are commits that don't have a corresponding tag at the specified git hash
	// https://golang.org/cmd/go/#hdr-Pseudo_versions
	// pulumi-aws v1.29.1-0.20200403140640-efb5e2a48a86 (first commit after 1.29.0 release)
	if buildutil.IsPseudoVersion(version) {
		// no prior tag means there was never a release build
		if v.Major == 0 && v.Minor == 0 && v.Patch == 0 {
			return "", errors.New("invalid pseduoversion with no prior tag")
		}
		// patch is typically bumped from the previous tag when using pseudo version
		// downgrade the patch by 1 to make sure we match a release that exists
		patch := v.Patch
		if patch > 0 {
			patch--
		}
		version = fmt.Sprintf("v%v.%v.%v", v.Major, v.Minor, patch)
	}
	return version, nil
}

// getPlugin loads information about this plugin.
//
// moduleRoot is the root directory of the Go module that imports this plugin.
// It must hold the go.mod file and the vendor directory (if any).
func (m *modInfo) getPlugin(moduleRoot string) (*pulumirpc.PluginDependency, error) {
	pulumiPlugin, err := m.readPulumiPluginJSON(moduleRoot)
	if err != nil {
		return nil, fmt.Errorf("failed to load pulumi-plugin.json: %w", err)
	}

	if (!strings.HasPrefix(m.Path, "github.com/pulumi/pulumi-") && pulumiPlugin == nil) ||
		(pulumiPlugin != nil && !pulumiPlugin.Resource) {
		return nil, errors.New("module is not a pulumi provider")
	}

	var name string
	if pulumiPlugin != nil && pulumiPlugin.Name != "" {
		name = pulumiPlugin.Name
	} else {
		// github.com/pulumi/pulumi-aws/sdk/... => aws
		pluginPart := strings.Split(m.Path, "/")[2]
		name = strings.SplitN(pluginPart, "-", 2)[1]
	}

	version := m.Version
	if pulumiPlugin != nil && pulumiPlugin.Version != "" {
		version = pulumiPlugin.Version
	}
	version, err = normalizeVersion(version)
	if err != nil {
		return nil, err
	}

	var server string

	if pulumiPlugin != nil {
		// There is no way to specify server without using `pulumi-plugin.json`.
		server = pulumiPlugin.Server
	}

	plugin := &pulumirpc.PluginDependency{
		Name:    name,
		Version: version,
		Kind:    "resource",
		Server:  server,
	}

	return plugin, nil
}

// Reads and parses the go.mod file for the program at the given path.
// Returns the parsed go.mod file and the path to the module directory.
// Relies on the 'go' command to find the go.mod file for this program.
func (host *goLanguageHost) loadGomod(gobin, programDir string) (modDir string, modFile *modfile.File, err error) {
	// Get the path to the go.mod file.
	// This may be different from the programDir if the Pulumi program
	// is in a subdirectory of the Go module.
	//
	// The '-f {{.GoMod}}' specifies that the command should print
	// just the path to the go.mod file.
	//
	//	type Module struct {
	//		Path     string // module path
	//		...
	//		GoMod    string // path to go.mod file
	//	}
	//
	// See 'go help list' for the full definition.
	cmd := exec.Command(gobin, "list", "-m", "-f", "{{.GoMod}}")
	// Disable GOWORK, we only want the one module found in this (or a parent) directory. Workspaces being
	// enabled will cause "list -m" to list every module in the workspace.
	cmd.Env = append(os.Environ(), "GOWORK=off")
	cmd.Dir = programDir
	cmd.Stderr = os.Stderr
	out, err := cmd.Output()
	if err != nil {
		return "", nil, fmt.Errorf("go list -m: %w", err)
	}
	out = bytes.TrimSpace(out)
	if len(out) == 0 {
		// The 'go list' command above will exit successfully
		// and return no output if the program is not in a Go module.
		return "", nil, fmt.Errorf("no go.mod file found: %v", programDir)
	}

	modPath := string(out)
	body, err := os.ReadFile(modPath)
	if err != nil {
		return "", nil, err
	}

	f, err := modfile.Parse(modPath, body, nil)
	if err != nil {
		return "", nil, fmt.Errorf("parse: %w", err)
	}

	return filepath.Dir(modPath), f, nil
}

// GetRequiredPlugins computes the complete set of anticipated plugins required by a program.
// We're lenient here as this relies on the `go list` command and the use of modules.
// If the consumer insists on using some other form of dependency management tool like
// dep or glide, the list command fails with "go list -m: not using modules".
// However, we do enforce that go 1.14.0 or higher is installed.
func (host *goLanguageHost) GetRequiredPlugins(ctx context.Context,
	req *pulumirpc.GetRequiredPluginsRequest,
) (*pulumirpc.GetRequiredPluginsResponse, error) {
	logging.V(5).Infof("GetRequiredPlugins: Determining pulumi packages")

	gobin, err := executable.FindExecutable("go")
	if err != nil {
		return nil, fmt.Errorf("couldn't find go binary: %w", err)
	}

	if err = goversion.CheckMinimumGoVersion(gobin); err != nil {
		return nil, err
	}

	moduleDir, gomod, err := host.loadGomod(gobin, req.Info.ProgramDirectory)
	if err != nil {
		// Don't fail if not using Go modules.
		logging.V(5).Infof("GetRequiredPlugins: Error reading go.mod: %v", err)
		return &pulumirpc.GetRequiredPluginsResponse{}, nil
	}

	modulePaths := slice.Prealloc[string](len(gomod.Require))
	for _, req := range gomod.Require {
		modulePaths = append(modulePaths, req.Mod.Path)
	}

	modInfos, err := findModuleSources(ctx, gobin, moduleDir, modulePaths)
	if err != nil {
		logging.V(5).Infof("GetRequiredPlugins: Error finding module sources: %v", err)
		return &pulumirpc.GetRequiredPluginsResponse{}, nil
	}

	plugins := []*pulumirpc.PluginDependency{}
	for _, m := range modInfos {
		plugin, err := m.getPlugin(moduleDir)
		if err != nil {
			logging.V(5).Infof(
				"GetRequiredPlugins: Ignoring dependency: %s, version: %s, error: %s",
				m.Path,
				m.Version,
				err,
			)
			continue
		}

		logging.V(5).Infof("GetRequiredPlugins: Found plugin name: %s, version: %s", plugin.Name, plugin.Version)
		plugins = append(plugins, plugin)
	}

	return &pulumirpc.GetRequiredPluginsResponse{
		Plugins: plugins,
	}, nil
}

func runCmdStatus(cmd *exec.Cmd, env []string) (int, error) {
	cmd.Env = env
	cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr

	err := cmd.Run()
	// The returned error is nil if the command runs, has no problems copying stdin, stdout, and stderr, and
	// exits with a zero exit status.
	if err == nil {
		return 0, nil
	}

	// error handling
	exiterr, ok := err.(*exec.ExitError)
	if !ok {
		return 0, fmt.Errorf("command errored unexpectedly: %w", err)
	}

	// retrieve the status code
	status, ok := exiterr.Sys().(syscall.WaitStatus)
	if !ok {
		return 0, fmt.Errorf("program exited unexpectedly: %w", err)
	}

	return status.ExitStatus(), nil
}

func runProgram(pwd, bin string, env []string) *pulumirpc.RunResponse {
	cmd := exec.Command(bin)
	cmd.Dir = pwd
	status, err := runCmdStatus(cmd, env)
	if err != nil {
		return &pulumirpc.RunResponse{
			Error: err.Error(),
		}
	}

	if status == 0 {
		return &pulumirpc.RunResponse{}
	}

	// If the program ran, but returned an error,
	// the error message should look as nice as possible.
	if status == constant.ExitStatusLoggedError {
		// program failed but sent an error to the engine
		return &pulumirpc.RunResponse{
			Bail: true,
		}
	}

	// If the program ran, but exited with a non-zero and non-reserved error code.
	// indicate to the user which exit code the program returned.
	return &pulumirpc.RunResponse{
		Error: fmt.Sprintf("program exited with non-zero exit code: %d", status),
	}
}

// Run is RPC endpoint for LanguageRuntimeServer::Run
func (host *goLanguageHost) Run(ctx context.Context, req *pulumirpc.RunRequest) (*pulumirpc.RunResponse, error) {
	opts, err := parseOptions(req.Info.RootDirectory, req.Info.Options.AsMap())
	if err != nil {
		return nil, err
	}

	// Create the environment we'll use to run the process.  This is how we pass the RunInfo to the actual
	// Go program runtime, to avoid needing any sort of program interface other than just a main entrypoint.
	env, err := host.constructEnv(req)
	if err != nil {
		return nil, fmt.Errorf("failed to prepare environment: %w", err)
	}

	// the user can explicitly opt in to using a binary executable by specifying
	// runtime.options.binary in the Pulumi.yaml
	if opts.binary != "" {
		bin, err := executable.FindExecutable(opts.binary)
		if err != nil {
			return nil, fmt.Errorf("unable to find '%s' executable: %w", opts.binary, err)
		}
		return runProgram(req.Pwd, bin, env), nil
	}

	// feature flag to enable deprecated old behavior and use `go run`
	if os.Getenv("PULUMI_GO_USE_RUN") != "" {
		gobin, err := executable.FindExecutable("go")
		if err != nil {
			return nil, fmt.Errorf("unable to find 'go' executable: %w", err)
		}

		cmd := exec.Command(gobin, "run", req.Info.ProgramDirectory)
		cmd.Dir = host.cwd
		status, err := runCmdStatus(cmd, env)
		if err != nil {
			return &pulumirpc.RunResponse{
				Error: err.Error(),
			}, nil
		}

		// `go run` does not return the actual exit status of a program
		// it only returns 2 non-zero exit statuses {1, 2}
		// and it emits the exit status to stderr
		if status != 0 {
			return &pulumirpc.RunResponse{
				Bail: true,
			}, nil
		}

		return &pulumirpc.RunResponse{}, nil
	}

	// user did not specify a binary and we will compile and run the binary on-demand
	logging.V(5).Infof("No prebuilt executable specified, attempting invocation via compilation")

	program, err := compileProgram(req.Info.ProgramDirectory, opts.buildTarget)
	if err != nil {
		return nil, fmt.Errorf("error in compiling Go: %w", err)
	}
	if opts.buildTarget == "" {
		// If there is no specified buildTarget, delete the temporary program after running it.
		defer os.Remove(program)
	}

	return runProgram(req.Pwd, program, env), nil
}

// constructEnv constructs an environment for a Go progam by enumerating all of the optional and non-optional
// arguments present in a RunRequest.
func (host *goLanguageHost) constructEnv(req *pulumirpc.RunRequest) ([]string, error) {
	config, err := host.constructConfig(req)
	if err != nil {
		return nil, err
	}
	configSecretKeys, err := host.constructConfigSecretKeys(req)
	if err != nil {
		return nil, err
	}

	env := os.Environ()
	maybeAppendEnv := func(k, v string) {
		if v != "" {
			env = append(env, fmt.Sprintf("%s=%s", k, v))
		}
	}

	maybeAppendEnv(pulumi.EnvOrganization, req.GetOrganization())
	maybeAppendEnv(pulumi.EnvProject, req.GetProject())
	maybeAppendEnv(pulumi.EnvStack, req.GetStack())
	maybeAppendEnv(pulumi.EnvConfig, config)
	maybeAppendEnv(pulumi.EnvConfigSecretKeys, configSecretKeys)
	maybeAppendEnv(pulumi.EnvDryRun, strconv.FormatBool(req.GetDryRun()))
	maybeAppendEnv(pulumi.EnvParallel, strconv.Itoa(int(req.GetParallel())))
	maybeAppendEnv(pulumi.EnvMonitor, req.GetMonitorAddress())
	maybeAppendEnv(pulumi.EnvEngine, host.engineAddress)

	return env, nil
}

// constructConfig JSON-serializes the configuration data given as part of a RunRequest.
func (host *goLanguageHost) constructConfig(req *pulumirpc.RunRequest) (string, error) {
	configMap := req.GetConfig()
	if configMap == nil {
		return "", nil
	}

	configJSON, err := json.Marshal(configMap)
	if err != nil {
		return "", err
	}

	return string(configJSON), nil
}

// constructConfigSecretKeys JSON-serializes the list of keys that contain secret values given as part of
// a RunRequest.
func (host *goLanguageHost) constructConfigSecretKeys(req *pulumirpc.RunRequest) (string, error) {
	configSecretKeys := req.GetConfigSecretKeys()
	if configSecretKeys == nil {
		return "[]", nil
	}

	configSecretKeysJSON, err := json.Marshal(configSecretKeys)
	if err != nil {
		return "", err
	}

	return string(configSecretKeysJSON), nil
}

func (host *goLanguageHost) GetPluginInfo(ctx context.Context, req *emptypb.Empty) (*pulumirpc.PluginInfo, error) {
	return &pulumirpc.PluginInfo{
		Version: version.Version,
	}, nil
}

func (host *goLanguageHost) InstallDependencies(
	req *pulumirpc.InstallDependenciesRequest, server pulumirpc.LanguageRuntime_InstallDependenciesServer,
) error {
	closer, stdout, stderr, err := rpcutil.MakeInstallDependenciesStreams(server, req.IsTerminal)
	if err != nil {
		return err
	}
	// best effort close, but we try an explicit close and error check at the end as well
	defer closer.Close()

	stdout.Write([]byte("Installing dependencies...\n\n"))

	gobin, err := executable.FindExecutable("go")
	if err != nil {
		return err
	}

	if err = goversion.CheckMinimumGoVersion(gobin); err != nil {
		return err
	}

	cmd := exec.Command(gobin, "mod", "tidy", "-compat=1.18")
	cmd.Dir = req.Info.ProgramDirectory
	cmd.Env = os.Environ()
	cmd.Stdout, cmd.Stderr = stdout, stderr

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("`go mod tidy` failed to install dependencies: %w", err)
	}

	stdout.Write([]byte("Finished installing dependencies\n\n"))

	return closer.Close()
}

func (host *goLanguageHost) RuntimeOptionsPrompts(ctx context.Context,
	req *pulumirpc.RuntimeOptionsRequest,
) (*pulumirpc.RuntimeOptionsResponse, error) {
	return &pulumirpc.RuntimeOptionsResponse{}, nil
}

func (host *goLanguageHost) About(ctx context.Context, req *pulumirpc.AboutRequest) (*pulumirpc.AboutResponse, error) {
	getResponse := func(execString string, args ...string) (string, string, error) {
		ex, err := executable.FindExecutable(execString)
		if err != nil {
			return "", "", fmt.Errorf("could not find executable '%s': %w", execString, err)
		}
		cmd := exec.Command(ex, args...)
		cmd.Dir = host.cwd
		var out []byte
		if out, err = cmd.Output(); err != nil {
			cmd := ex
			if len(args) != 0 {
				cmd += " " + strings.Join(args, " ")
			}
			return "", "", fmt.Errorf("failed to execute '%s'", cmd)
		}
		return ex, strings.TrimSpace(string(out)), nil
	}

	goexe, version, err := getResponse("go", "version")
	if err != nil {
		return nil, err
	}

	return &pulumirpc.AboutResponse{
		Executable: goexe,
		Version:    version,
	}, nil
}

func (host *goLanguageHost) GetProgramDependencies(
	ctx context.Context, req *pulumirpc.GetProgramDependenciesRequest,
) (*pulumirpc.GetProgramDependenciesResponse, error) {
	gobin, err := executable.FindExecutable("go")
	if err != nil {
		return nil, fmt.Errorf("couldn't find go binary: %w", err)
	}

	_, gomod, err := host.loadGomod(gobin, req.Info.ProgramDirectory)
	if err != nil {
		return nil, fmt.Errorf("load go.mod: %w", err)
	}

	// If a module is locally replaced it doesn't really have a version anymore.
	replaced := make(map[string]bool)
	for _, r := range gomod.Replace {
		replaced[strings.TrimRight(r.Old.Path, "/")] = true
	}

	result := slice.Prealloc[*pulumirpc.DependencyInfo](len(gomod.Require))
	for _, d := range gomod.Require {
		if !d.Indirect || req.TransitiveDependencies {
			version := d.Mod.Version
			if replaced[strings.TrimRight(d.Mod.Path, "/")] {
				version = ""
			}
			datum := pulumirpc.DependencyInfo{
				Name:    d.Mod.Path,
				Version: version,
			}
			result = append(result, &datum)
		}
	}
	return &pulumirpc.GetProgramDependenciesResponse{
		Dependencies: result,
	}, nil
}

func (host *goLanguageHost) RunPlugin(
	req *pulumirpc.RunPluginRequest, server pulumirpc.LanguageRuntime_RunPluginServer,
) error {
	logging.V(5).Infof("Attempting to run go plugin in %s", req.Info.ProgramDirectory)

	program, err := compileProgram(req.Info.ProgramDirectory, "")
	if err != nil {
		return fmt.Errorf("error in compiling Go: %w", err)
	}
	defer os.Remove(program)

	closer, stdout, stderr, err := rpcutil.MakeRunPluginStreams(server, false)
	if err != nil {
		return err
	}
	// best effort close, but we try an explicit close and error check at the end as well
	defer closer.Close()

	cmd := exec.Command(program, req.Args...)
	cmd.Dir = req.Pwd
	cmd.Env = req.Env
	cmd.Stdout, cmd.Stderr = stdout, stderr

	if err = cmd.Run(); err != nil {
		if exiterr, ok := err.(*exec.ExitError); ok {
			if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
				err = server.Send(&pulumirpc.RunPluginResponse{
					Output: &pulumirpc.RunPluginResponse_Exitcode{Exitcode: int32(status.ExitStatus())},
				})
			} else {
				err = fmt.Errorf("program exited unexpectedly: %w", exiterr)
			}
		} else {
			return fmt.Errorf("problem executing plugin program (could not run language executor): %w", err)
		}
	}

	if err != nil {
		return err
	}

	return closer.Close()
}

func (host *goLanguageHost) GenerateProject(
	ctx context.Context, req *pulumirpc.GenerateProjectRequest,
) (*pulumirpc.GenerateProjectResponse, error) {
	loader, err := schema.NewLoaderClient(req.LoaderTarget)
	if err != nil {
		return nil, err
	}

	var extraOptions []pcl.BindOption
	if !req.Strict {
		extraOptions = append(extraOptions, pcl.NonStrictBindOptions()...)
	}

	program, diags, err := pcl.BindDirectory(req.SourceDirectory, loader, extraOptions...)
	if err != nil {
		return nil, err
	}

	rpcDiagnostics := plugin.HclDiagnosticsToRPCDiagnostics(diags)
	if diags.HasErrors() {
		return &pulumirpc.GenerateProjectResponse{
			Diagnostics: rpcDiagnostics,
		}, nil
	}
	if program == nil {
		return nil, errors.New("internal error: program was nil")
	}

	var project workspace.Project
	if err := json.Unmarshal([]byte(req.Project), &project); err != nil {
		return nil, err
	}

	err = codegen.GenerateProject(req.TargetDirectory, project, program, req.LocalDependencies)
	if err != nil {
		return nil, err
	}

	return &pulumirpc.GenerateProjectResponse{
		Diagnostics: rpcDiagnostics,
	}, nil
}

func (host *goLanguageHost) GenerateProgram(
	ctx context.Context, req *pulumirpc.GenerateProgramRequest,
) (*pulumirpc.GenerateProgramResponse, error) {
	loader, err := schema.NewLoaderClient(req.LoaderTarget)
	if err != nil {
		return nil, err
	}

	parser := hclsyntax.NewParser()
	// Load all .pp files in the directory
	for path, contents := range req.Source {
		err = parser.ParseFile(strings.NewReader(contents), path)
		if err != nil {
			return nil, err
		}
		diags := parser.Diagnostics
		if diags.HasErrors() {
			return nil, diags
		}
	}

	program, diags, err := pcl.BindProgram(parser.Files, pcl.Loader(loader))
	if err != nil {
		return nil, err
	}

	rpcDiagnostics := plugin.HclDiagnosticsToRPCDiagnostics(diags)
	if diags.HasErrors() {
		return &pulumirpc.GenerateProgramResponse{
			Diagnostics: rpcDiagnostics,
		}, nil
	}
	if program == nil {
		return nil, errors.New("internal error: program was nil")
	}

	files, diags, err := codegen.GenerateProgram(program)
	if err != nil {
		return nil, err
	}
	rpcDiagnostics = append(rpcDiagnostics, plugin.HclDiagnosticsToRPCDiagnostics(diags)...)

	return &pulumirpc.GenerateProgramResponse{
		Source:      files,
		Diagnostics: rpcDiagnostics,
	}, nil
}

func (host *goLanguageHost) GeneratePackage(
	ctx context.Context, req *pulumirpc.GeneratePackageRequest,
) (*pulumirpc.GeneratePackageResponse, error) {
	if len(req.ExtraFiles) > 0 {
		return nil, errors.New("overlays are not supported for Go")
	}

	loader, err := schema.NewLoaderClient(req.LoaderTarget)
	if err != nil {
		return nil, err
	}

	var spec schema.PackageSpec
	err = json.Unmarshal([]byte(req.Schema), &spec)
	if err != nil {
		return nil, err
	}

	pkg, diags, err := schema.BindSpec(spec, loader)
	if err != nil {
		return nil, err
	}
	rpcDiagnostics := plugin.HclDiagnosticsToRPCDiagnostics(diags)
	if diags.HasErrors() {
		return &pulumirpc.GeneratePackageResponse{
			Diagnostics: rpcDiagnostics,
		}, nil
	}
	files, err := codegen.GeneratePackage("pulumi-language-go", pkg)
	if err != nil {
		return nil, err
	}

	for filename, data := range files {
		outPath := filepath.Join(req.Directory, filename)

		err := os.MkdirAll(filepath.Dir(outPath), 0o700)
		if err != nil {
			return nil, fmt.Errorf("could not create output directory %s: %w", filepath.Dir(filename), err)
		}

		err = os.WriteFile(outPath, data, 0o600)
		if err != nil {
			return nil, fmt.Errorf("could not write output file %s: %w", filename, err)
		}
	}

	return &pulumirpc.GeneratePackageResponse{
		Diagnostics: rpcDiagnostics,
	}, nil
}

func (host *goLanguageHost) Pack(ctx context.Context, req *pulumirpc.PackRequest) (*pulumirpc.PackResponse, error) {
	// Go is very simple, there's nothing it can do to pack so it just copies the source to the target.

	// First read the modfile in the package directory to decide the folder name.
	data, err := os.ReadFile(filepath.Join(req.PackageDirectory, "go.mod"))
	if err != nil {
		return nil, fmt.Errorf("read go.mod: %w", err)
	}
	mod, err := modfile.Parse("go.mod", data, nil)
	if err != nil {
		return nil, fmt.Errorf("parse go.mod: %w", err)
	}

	// The folder name is the module name, made into a valid directory name.
	folderName := strings.ReplaceAll(mod.Module.Mod.Path, "/", "_")
	artifactPath := filepath.Join(req.DestinationDirectory, folderName)

	err = fsutil.CopyFile(artifactPath, req.PackageDirectory, nil)
	if err != nil {
		return nil, fmt.Errorf("copy package: %w", err)
	}

	return &pulumirpc.PackResponse{
		ArtifactPath: artifactPath,
	}, nil
}