// Package bootstrap is used by the bootstrap command as a quick-start of
// Atlantis.
package bootstrap

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"runtime"
	"strings"
	"syscall"
	"time"

	"github.com/briandowns/spinner"
	"github.com/google/go-github/github"
	"github.com/mitchellh/colorstring"
	"github.com/pkg/errors"
)

var terraformExampleRepoOwner = "hootsuite"
var terraformExampleRepo = "atlantis-example"
var bootstrapDescription = `[white]Welcome to Atlantis bootstrap!

This mode walks you through setting up and using Atlantis. We will
- fork an example terraform project to your username
- install terraform (if not already in your PATH)
- install ngrok so we can expose Atlantis to GitHub
- start Atlantis

[bold]Press Ctrl-c at any time to exit
`
var pullRequestBody = "In this pull request we will learn how to use atlantis. There are various commands that are available to you:\n" +
	"* Start by typing `atlantis help` in the comments.\n" +
	"* Next, lets plan by typing `atlantis plan` in the comments. That will run a `terraform plan`.\n" +
	"* Now lets apply that plan. Type `atlantis apply` in the comments. This will run a `terraform apply`.\n" +
	"\nThank you for trying out atlantis. For more info on running atlantis in production see https://github.com/hootsuite/atlantis"

// Start begins the bootstrap process.
// nolint: errcheck
func Start() error {
	s := spinner.New(spinner.CharSets[14], 100*time.Millisecond)
	colorstring.Println(bootstrapDescription)
	colorstring.Print("\n[white][bold]GitHub username: ")
	fmt.Scanln(&githubUsername)
	if githubUsername == "" {
		return fmt.Errorf("please enter a valid github username")
	}
	colorstring.Println(`
[white]To continue, we need you to create a GitHub personal access token
with [green]"repo" [white]scope so we can fork an example terraform project.

Follow these instructions to create a token (we don't store any tokens):
[green]https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/#creating-a-token
[white]- use "atlantis" for the token description
- add "repo" scope
- copy the access token
`)
	// Read github token, check for error later.
	colorstring.Print("[white][bold]GitHub access token (will be hidden): ")
	githubToken, _ = readPassword()
	tp := github.BasicAuthTransport{
		Username: strings.TrimSpace(githubUsername),
		Password: strings.TrimSpace(githubToken),
	}
	githubClient := &Client{client: github.NewClient(tp.Client()), ctx: context.Background()}

	// Fork terraform example repo.
	colorstring.Printf("\n[white]=> forking repo ")
	s.Start()
	if err := githubClient.CreateFork(terraformExampleRepoOwner, terraformExampleRepo); err != nil {
		return errors.Wrapf(err, "forking repo %s/%s", terraformExampleRepoOwner, terraformExampleRepo)
	}
	if !githubClient.CheckForkSuccess(terraformExampleRepoOwner, terraformExampleRepo) {
		return fmt.Errorf("didn't find forked repo %s/%s. fork unsuccessful", terraformExampleRepoOwner, terraformExampleRepoOwner)
	}
	s.Stop()
	colorstring.Println("\n[green]=> fork completed!")

	// Detect terraform and install it if not installed.
	_, err := exec.LookPath("terraform")
	if err != nil {
		colorstring.Println("[yellow]=> terraform not found in $PATH.")
		colorstring.Printf("[white]=> downloading terraform ")
		s.Start()
		terraformDownloadURL := fmt.Sprintf("%s/terraform/%s/terraform_%s_%s_%s.zip", hashicorpReleasesURL, terraformVersion, terraformVersion, runtime.GOOS, runtime.GOARCH)
		if err = downloadAndUnzip(terraformDownloadURL, "/tmp/terraform.zip", "/tmp"); err != nil {
			return errors.Wrapf(err, "downloading and unzipping terraform")
		}
		colorstring.Println("\n[green]=> downloaded terraform successfully!")
		s.Stop()

		var terraformCmd *exec.Cmd
		terraformCmd, err = executeCmd("mv", []string{"/tmp/terraform", "/usr/local/bin/"})
		if err != nil {
			return errors.Wrapf(err, "moving terraform binary into /usr/local/bin")
		}
		terraformCmd.Wait()
		colorstring.Println("[green]=> installed terraform successfully at /usr/local/bin")
	} else {
		colorstring.Println("[green]=> terraform found in $PATH!")
	}

	// Download ngrok.
	colorstring.Printf("[white]=> downloading ngrok  ")
	s.Start()
	ngrokURL := fmt.Sprintf("%s/ngrok-stable-%s-%s.zip", ngrokDownloadURL, runtime.GOOS, runtime.GOARCH)
	if err = downloadAndUnzip(ngrokURL, "/tmp/ngrok.zip", "/tmp"); err != nil {
		return errors.Wrapf(err, "downloading and unzipping ngrok")
	}
	s.Stop()
	colorstring.Println("\n[green]=> downloaded ngrok successfully!")

	// Create ngrok tunnel.
	colorstring.Printf("[white]=> creating secure tunnel ")
	s.Start()
	ngrokCmd, err := executeCmd("/tmp/ngrok", []string{"http", "4141"})
	if err != nil {
		return errors.Wrapf(err, "creating ngrok tunnel")
	}

	ngrokErrChan := make(chan error, 10)
	go func() {
		ngrokErrChan <- ngrokCmd.Wait()
	}()
	// When this function returns, ngrok tunnel should be stopped.
	defer ngrokCmd.Process.Kill()

	// Wait for the tunnel to be up.
	time.Sleep(2 * time.Second)
	s.Stop()
	colorstring.Println("\n[green]=> started tunnel!")
	tunnelURL, err := getTunnelAddr()
	if err != nil {
		return errors.Wrapf(err, "getting tunnel url")
	}
	s.Stop()

	// Start atlantis server.
	colorstring.Printf("[white]=> starting atlantis server ")
	s.Start()
	atlantisCmd, err := executeCmd(os.Args[0], []string{"server", "--gh-user", githubUsername, "--gh-token", githubToken, "--data-dir", "/tmp/atlantis/data", "--atlantis-url", tunnelURL})
	if err != nil {
		return errors.Wrapf(err, "creating atlantis server")
	}

	atlantisErrChan := make(chan error, 10)
	go func() {
		atlantisErrChan <- atlantisCmd.Wait()
	}()
	// When this function returns atlantis server should be stopped.
	defer atlantisCmd.Process.Kill()
	colorstring.Printf("\n[green]=> atlantis server is now securely exposed at [bold][underline]%s", tunnelURL)
	fmt.Println("")

	// Create atlantis webhook.
	colorstring.Printf("[white]=> creating atlantis webhook ")
	s.Start()
	err = githubClient.CreateWebhook(githubUsername, terraformExampleRepo, fmt.Sprintf("%s/events", tunnelURL))
	if err != nil {
		return errors.Wrapf(err, "creating atlantis webhook")
	}
	s.Stop()
	colorstring.Println("\n[green]=> atlantis webhook created!")

	// Create a new pr in the example repo.
	colorstring.Printf("[white]=> creating a new pull request ")
	s.Start()
	pullRequestURL, err := githubClient.CreatePullRequest(githubUsername, terraformExampleRepo, "example", "master")
	if err != nil {
		return errors.Wrapf(err, "creating new pull request for repo %s/%s", githubUsername, terraformExampleRepo)
	}
	s.Stop()
	colorstring.Println("\n[green]=> pull request created!")

	// Open new pull request in the browser.
	colorstring.Printf("[white]=> opening pull request ")
	s.Start()
	time.Sleep(2 * time.Second)
	_, err = executeCmd("open", []string{pullRequestURL})
	if err != nil {
		colorstring.Printf("[red]=> opening pull request failed. please go to: %s on the browser", pullRequestURL)
	}
	s.Stop()

	// Wait for ngrok and atlantis server process to finish.
	colorstring.Printf("\n[_green_][light_green]atlantis is running ")
	s.Start()
	colorstring.Println("[green] [press Ctrl-c to exit]")

	// Wait for SIGINT or SIGTERM signals meaning the user has Ctrl-C'd the
	// bootstrap process and want's to stop.
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
	<-signalChan
	colorstring.Println("\n[red]shutdown signal received, exiting....")
	colorstring.Println("\n[green]Thank you for using atlantis :) \n[white]For more information about how to use atlantis in production go to: https://github.com/hootsuite/atlantis")
	return nil
}
