// 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/pkg/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 {
	path := args[0]
	command := strings.Join(args, " ")
	fprintf(opts.Stdout, "**** Invoke '%v' in '%v'\n", command, wd)

	// Spawn a goroutine to print out "still running..." messages.
	finished := false
	defer func() { finished = true }()

	go func() {
		for !finished {
			time.Sleep(120 * time.Second)
			if !finished {
				fprintf(opts.Stderr, "Still running command '%s' (%s)...\n", 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 {
		fprintf(opts.Stderr, "Invoke '%v' failed: %s\n", command, cmdutil.DetailedError(runerr))

		if !opts.Verbose {
			// We've seen long fprintf's fail on Travis, so avoid panicing.
			if _, err := fmt.Fprintf(opts.Stderr, "%s\n", string(runout)); err != nil {
				fprintf(opts.Stderr, "\n\nOutput truncated: %v\n", err)
			}
		}
	}

	// 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 {
			fprintf(opts.Stderr, "Failed to write output: %v\n", err)
		} else {
			fprintf(opts.Stderr, "Wrote output to %s\n", logFile)
		}
	} else {
		fprintf(opts.Stderr, "Command completed without output\n")
	}

	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
}
