// Copyright 2016-2018, 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 integration

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
)

// RunCommand executes the specified command and additional arguments, wrapping any output in the
// specialized test output streams that list the location the test is running in.
func RunCommand(t *testing.T, name string, args []string, wd string, opts *ProgramTestOptions) error {
	t.Helper()

	path := args[0]
	command := strings.Join(args, " ")
	t.Logf("**** Invoke '%v' in '%v'", command, wd)

	env := os.Environ()
	if opts.Env != nil {
		env = append(env, opts.Env...)
	}
	env = append(env, "PULUMI_DEBUG_COMMANDS=true")
	env = append(env, "PULUMI_RETAIN_CHECKPOINTS=true")
	env = append(env, "PULUMI_CONFIG_PASSPHRASE=correct horse battery staple")

	cmd := exec.Cmd{
		Path: path,
		Dir:  wd,
		Args: args,
		Env:  env,
	}

	startTime := time.Now()

	var runout []byte
	var runerr error
	if opts.Verbose || os.Getenv("PULUMI_VERBOSE_TEST") != "" {
		cmd.Stdout = opts.Stdout
		cmd.Stderr = opts.Stderr
		runerr = cmd.Run()
	} else {
		runout, runerr = cmd.CombinedOutput()
	}

	endTime := time.Now()

	if opts.ReportStats != nil {
		// Note: This data is archived and used by external analytics tools.  Take care if changing the schema or format
		// of this data.
		opts.ReportStats.ReportCommand(TestCommandStats{
			StartTime:      startTime.Format("2006/01/02 15:04:05"),
			EndTime:        endTime.Format("2006/01/02 15:04:05"),
			ElapsedSeconds: float64((endTime.Sub(startTime)).Nanoseconds()) / 1000000000,
			StepName:       name,
			CommandLine:    command,
			StackName:      string(opts.GetStackName()),
			TestID:         wd,
			TestName:       filepath.Base(opts.Dir),
			IsError:        runerr != nil,
			CloudURL:       opts.CloudURL,
		})
	}

	if runerr != nil {
		t.Logf("Invoke '%v' failed: %s\n", command, cmdutil.DetailedError(runerr))

		if !opts.Verbose {
			stderr := opts.Stderr

			if stderr == nil {
				stderr = os.Stderr
			}

			// Make sure we write the output in case of a failure to stderr so
			// tests can assert the shape of the error message.
			_, _ = fmt.Fprintf(stderr, "%s\n", string(runout))
		}
	}

	// If we collected any program output, write it to a log file -- success or failure.
	if len(runout) > 0 {
		if logFile, err := writeCommandOutput(name, wd, runout); err != nil {
			t.Logf("Failed to write output: %v", err)
		} else {
			t.Logf("Wrote output to %s", logFile)
		}
	} else {
		t.Log("Command completed without output")
	}

	return runerr
}

func withOptionalYarnFlags(args []string) []string {
	flags := os.Getenv("YARNFLAGS")

	if flags != "" {
		return append(args, flags)
	}

	return args
}

// addFlagIfNonNil will take a set of command-line flags, and add a new one if the provided flag value is not empty.
func addFlagIfNonNil(args []string, flag, flagValue string) []string {
	if flagValue != "" {
		args = append(args, flag, flagValue)
	}
	return args
}