package go_http

import (
	"fmt"
	"go/format"
	"log"
	"math/rand"
	"os"
	"os/exec"
	"path"
	"strings"
	"time"

	"code.justin.tv/dta/twitch-create-service/internal/bootstrap/structs"
	gh "code.justin.tv/dta/twitch-create-service/internal/github"
	"code.justin.tv/dta/twitch-create-service/internal/templates"
	"github.com/google/go-github/github"
)

type App struct {
	client  *github.Client
	repo    *github.Repository
	verbose bool
}

var _ structs.AppType = (*App)(nil)

func (a *App) SetClient(client *github.Client, repo *github.Repository, verbose bool) {
	a.client = client
	a.repo = repo
	a.verbose = verbose
}

func (a *App) PreflightCheck() error {
	return nil
}

func (a *App) MakeUsable() error {
	commit, err := gh.PrepareTemplatesForCommit([]gh.TemplateToCommit{
		{
			TemplatePath: "internal/templates/go_http/project/Vagrantfile.tmpl",
			RepoPath:     "Vagrantfile",
		},
		{
			TemplatePath: "internal/templates/go_http/project/vagrant.sh.tmpl",
			RepoPath:     "scripts/vagrant.sh",
		},
		{
			TemplatePath: "internal/templates/go_http/project/gitignore.tmpl",
			RepoPath:     ".gitignore",
		},
	}, a.repo)
	if err != nil {
		return err
	}

	err = gh.CommitFilesToBranch(a.client, a.repo, "master", "Setup dev env", commit)
	if err != nil {
		return err
	}

	return nil
}

func (a *App) MakeBuildable() error {
	commit, err := gh.PrepareTemplatesForCommit([]gh.TemplateToCommit{
		{
			TemplatePath: "internal/templates/go_http/project/jenkins.groovy.tmpl",
			RepoPath:     "jenkins.groovy",
		},
		{
			TemplatePath: "internal/templates/go_http/project/build.json.tmpl",
			RepoPath:     "build.json",
		},
		{
			TemplatePath: "internal/templates/go_http/project/deploy.json.tmpl",
			RepoPath:     "deploy.json",
		},
	}, a.repo)
	if err != nil {
		return err
	}

	err = gh.CommitFilesToBranch(a.client, a.repo, "master", "Define build job", commit)
	if err != nil {
		return err
	}

	wait := 90 * time.Second

	log.Printf("Waiting for Jenkins job to exist (%v)", (wait * 2).String())

	time.Sleep(wait)

	jenkinsTmpl, err := templates.Asset("internal/templates/go_http/project/jenkins.groovy.tmpl")
	if err != nil {
		return err
	}

	err = gh.CommitFilesToBranch(a.client, a.repo, "master", "noop to trigger dsl job again", []gh.CommitTemplate{
		{
			Path: "jenkins.groovy",
			T:    string(jenkinsTmpl) + " ",
			Data: a.repo,
		},
	})
	if err != nil {
		return err
	}

	time.Sleep(wait)

	return nil
}

func (a *App) CreateApp(user, token string, tmpRoot string) error {
	commit, err := gh.PrepareTemplatesForCommit([]gh.TemplateToCommit{
		{
			TemplatePath:      "internal/templates/go_http/app/main.go.tmpl",
			RepoPath:          "main.go",
			PostTemplatedFunc: format.Source,
		},

		{
			TemplatePath:      "internal/templates/go_http/app/api.go.tmpl",
			RepoPath:          "api/api.go",
			PostTemplatedFunc: format.Source,
		},
	}, a.repo)
	if err != nil {
		return err
	}

	err = gh.CommitFilesToBranch(a.client, a.repo, "master", "Simple go application", commit)
	if err != nil {
		return err
	}

	checkoutPath, err := gh.CloneLocally(user, token, tmpRoot, a.repo, a.verbose)
	if err != nil {
		return err
	}

	if _, err := os.Stat(path.Join(checkoutPath, "_vendor")); os.IsNotExist(err) {
		if err := a.vendorDeps(checkoutPath, tmpRoot); err != nil {
			return err
		}
	} else {
		log.Printf("Dependencies already vendored")
	}

	log.Printf("common/twitchhttp server ready for %v-specific endpoints", *a.repo.Name)

	return nil
}

func (a *App) vendorDeps(checkoutPath, tmpRoot string) error {
	err := os.Mkdir(path.Join(checkoutPath, "_vendor"), 0755)
	if err != nil {
		return err
	}

	cmd := exec.Command("ln", "-s", "_vendor", "vendor")
	cmd.Dir = checkoutPath
	if a.verbose {
		cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
	}
	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("error creating vendor symlink: %v", err)
	}

	goGets := []*exec.Cmd{
		exec.Command("go", "get", "code.justin.tv/common/twitchhttp"),
		exec.Command("go", "get", "code.justin.tv/release/trace/common"),
		exec.Command("go", "get", "github.com/golang/protobuf/proto"),
		exec.Command("go", "get", "google.golang.org/grpc/metadata"),
		exec.Command("go", "get", "github.com/tools/godep"),
	}
	for _, cmd := range goGets {
		cmd.Env = temporaryGoPath(tmpRoot)
		cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
		err = cmd.Run()
		if err != nil {
			return fmt.Errorf("error installing go package %v: %v", cmd.Args, err)
		}
	}

	if a.verbose {
		cmd = exec.Command(fmt.Sprintf("%v/go/bin/godep", tmpRoot), "version")
		cmd.Dir = checkoutPath
		cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
		cmd.Env = temporaryGoPath(tmpRoot)
		err = cmd.Run()
		if err != nil {
			return fmt.Errorf("error checking godep version: %v", err)
		}
	}

	cmd = exec.Command(fmt.Sprintf("%v/go/bin/godep", tmpRoot), "save", "-v", "./...")
	cmd.Dir = checkoutPath
	if a.verbose {
		cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
	}
	cmd.Env = temporaryGoPath(tmpRoot)
	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("error vendoring packages: %v", err)
	}

	cmd = exec.Command("git", "add", "Godeps/", "vendor", "_vendor/")
	cmd.Dir = checkoutPath
	if a.verbose {
		cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
	}
	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("error adding files: %v", err)
	}

	cmd = exec.Command("git", "commit", "-m", "vendor packages for common/twitchhttp\n\nPrepared by twitch-create-service")
	cmd.Dir = checkoutPath
	if a.verbose {
		cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
	}
	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("error committing vendor: %v", err)
	}

	cmd = exec.Command("git", "push", "origin", "master")
	cmd.Dir = checkoutPath
	if a.verbose {
		cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
	}
	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("error pushing to github: %v", err)
	}

	return nil
}

func (a *App) CreatePuppetModule() error {
	t := structs.PuppetModuleTemplate{
		PuppetModuleName: strings.Replace(*a.repo.Name, "-", "_", -1),
		Name:             *a.repo.Name,
		FullName:         *a.repo.FullName,
	}

	puppetRepo, _, err := a.client.Repositories.Get("systems", "puppet")
	if err != nil {
		return err
	}

	exists := gh.FileExists(a.client, puppetRepo, fmt.Sprintf("modules/%v/manifests/init.pp", t.PuppetModuleName))

	if !exists {
		commit, err := gh.PrepareTemplatesForCommit([]gh.TemplateToCommit{
			{
				TemplatePath: "internal/templates/go_http/puppet/hiera.yaml.tmpl",
				RepoPath:     fmt.Sprintf("hiera/environment/%v/%v.yaml", "production", t.Name),
			},
			{
				TemplatePath: "internal/templates/go_http/puppet/hiera.yaml.tmpl",
				RepoPath:     fmt.Sprintf("hiera/environment/%v/%v.yaml", "staging", t.Name),
			},
			{
				TemplatePath: "internal/templates/go_http/puppet/manifests/init.pp.tmpl",
				RepoPath:     fmt.Sprintf("modules/%v/manifests/init.pp", t.PuppetModuleName),
			},
			{
				TemplatePath: "internal/templates/go_http/puppet/manifests/install.pp.tmpl",
				RepoPath:     fmt.Sprintf("modules/%v/manifests/install.pp", t.PuppetModuleName),
			},
			{
				TemplatePath: "internal/templates/go_http/puppet/manifests/params.pp.tmpl",
				RepoPath:     fmt.Sprintf("modules/%v/manifests/params.pp", t.PuppetModuleName),
			},
		}, t)
		if err != nil {
			return err
		}

		err = gh.CommitViaPullRequest(a.client, puppetRepo, *a.repo.Name, fmt.Sprintf("twitch-create-service-%v", rand.Int()), "new app puppet module", commit)
		if err != nil {
			return err
		}
	}

	return nil
}

func (a *App) CreateTerraformFiles(user, org string, awsAccounts map[string]string, subnetList string) error {
	terra := structs.TerraformTemplate{
		PuppetModuleName: strings.Replace(*a.repo.Name, "-", "_", -1),
		Name:             *a.repo.Name,
		FullName:         *a.repo.FullName,
		User:             user,
		DNSSafeName:      strings.Replace(*a.repo.Name, "_", "-", -1),
		Environment:      "production",
		AWSAccounts:      awsAccounts,
		Org:              org,
		TerraformSubnetListName: subnetList,
	}

	commit, err := gh.PrepareTemplatesForCommit([]gh.TemplateToCommit{
		{
			TemplatePath: "internal/templates/shared/terraform/asg_puppetizer.template.tmpl",
			RepoPath:     fmt.Sprintf("terraform/modules/%v/asg_puppetizer.template", terra.Name),
		},
		{
			TemplatePath: "internal/templates/shared/terraform/canary.tf.tmpl",
			RepoPath:     fmt.Sprintf("terraform/modules/%v/canary.tf", terra.Name),
		},
		{
			TemplatePath: "internal/templates/shared/terraform/cloudwatch.tf.tmpl",
			RepoPath:     fmt.Sprintf("terraform/modules/%v/cloudwatch.tf", terra.Name),
		},
		{
			TemplatePath: "internal/templates/shared/terraform/iam.tf.tmpl",
			RepoPath:     fmt.Sprintf("terraform/modules/%v/iam.tf", terra.Name),
		},
		{
			TemplatePath: "internal/templates/shared/terraform/main.tf.tmpl",
			RepoPath:     fmt.Sprintf("terraform/modules/%v/main.tf", terra.Name),
		},
		{
			TemplatePath: "internal/templates/shared/terraform/outputs.tf.tmpl",
			RepoPath:     fmt.Sprintf("terraform/modules/%v/outputs.tf", terra.Name),
		},
		{
			TemplatePath: "internal/templates/shared/terraform/remotes.tf.tmpl",
			RepoPath:     fmt.Sprintf("terraform/modules/%v/remotes.tf", terra.Name),
		},
		{
			TemplatePath: "internal/templates/shared/terraform/variables.tf.tmpl",
			RepoPath:     fmt.Sprintf("terraform/modules/%v/variables.tf", terra.Name),
		},
	}, terra)
	if err != nil {
		return err
	}

	err = gh.CommitFilesToBranch(a.client, a.repo, "master", "Service module", commit)
	if err != nil {
		return err
	}

	commit, err = gh.PrepareTemplatesForCommit([]gh.TemplateToCommit{
		{
			TemplatePath: "internal/templates/shared/terraform/service.tf.tmpl",
			RepoPath:     "terraform/production/main.tf",
		},
		{
			TemplatePath: "internal/templates/shared/terraform/provider.tf.tmpl",
			RepoPath:     "terraform/production/provider.tf",
		},
	}, terra)
	if err != nil {
		return err
	}

	err = gh.CommitFilesToBranch(a.client, a.repo, "master", "Production infrastructure", commit)
	if err != nil {
		return err
	}

	terra.Environment = "staging"

	commit, err = gh.PrepareTemplatesForCommit([]gh.TemplateToCommit{
		{
			TemplatePath: "internal/templates/shared/terraform/service.tf.tmpl",
			RepoPath:     "terraform/staging/main.tf",
		},
		{
			TemplatePath: "internal/templates/shared/terraform/provider.tf.tmpl",
			RepoPath:     "terraform/staging/provider.tf",
		},
	}, terra)
	if err != nil {
		return err
	}

	err = gh.CommitFilesToBranch(a.client, a.repo, "master", "Staging infrastructure", commit)
	if err != nil {
		return err
	}

	return nil
}

func temporaryGoPath(tmpRoot string) []string {
	env := []string{}
	for _, e := range os.Environ() {
		if strings.HasPrefix(e, "GOPATH") {
			env = append(env, fmt.Sprintf("GOPATH=%v/go", tmpRoot))
		} else if strings.HasPrefix(e, "GO15VENDOREXPERIMENT") {
			// ignore people running the experiment
		} else {
			env = append(env, e)
		}
	}

	return env
}
