package ruby_http

import (
	"fmt"
	"log"
	"math/rand"
	"os"
	"os/exec"
	"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
}

type appTemplate struct {
	Owner      string
	Name       string
	ModuleName string
}

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 {
	err := a.isInstalled("ruby", "-v")
	if err != nil {
		return fmt.Errorf("ruby is needed, but not installed")
	}

	err = a.isInstalled("bundler", "version")
	if err != nil {
		return fmt.Errorf("bundler is needed, but not installed")
	}

	err = a.isInstalled("bison", "--version")
	if err != nil {
		return fmt.Errorf("bison is needed for chitin-ruby, but not installed")
	}

	err = a.isInstalled("flex", "--version")
	if err != nil {
		return fmt.Errorf("flex is needed for chitin-ruby, but not installed")
	}

	return nil
}

func (a *App) isInstalled(args ...string) error {
	cmd := exec.Command(args[0], args[1:]...)
	if a.verbose {
		cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
	}
	return cmd.Run()
}

func (a *App) MakeUsable() error {
	commit, err := gh.PrepareTemplatesForCommit([]gh.TemplateToCommit{
		{
			TemplatePath: "internal/templates/ruby_http/project/Vagrantfile.tmpl",
			RepoPath:     "Vagrantfile",
		},
		{
			TemplatePath: "internal/templates/ruby_http/project/vagrant.sh.tmpl",
			RepoPath:     "scripts/vagrant.sh",
		},
		{
			TemplatePath: "internal/templates/ruby_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/ruby_http/project/jenkins.groovy.tmpl",
			RepoPath:     "jenkins.groovy",
		},
		{
			TemplatePath: "internal/templates/ruby_http/project/build.json.tmpl",
			RepoPath:     "build.json",
		},
		{
			TemplatePath: "internal/templates/ruby_http/project/deploy.json.tmpl",
			RepoPath:     "deploy.json",
		},
		{
			TemplatePath: "internal/templates/ruby_http/project/post-install.sh.tmpl",
			RepoPath:     "courier/post-install.sh",
		},
	}, 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/ruby_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) prepareAppTemplate() appTemplate {
	name := *a.repo.Name
	capsName := strings.Replace(name, "-", "", -1)
	upper := strings.ToUpper(string(capsName[0]))
	capsName = upper + capsName[1:]

	return appTemplate{
		Owner:      *a.repo.Owner.Login,
		Name:       *a.repo.Name,
		ModuleName: capsName,
	}
}

func (a *App) CreateApp(user, token string, tmpRoot string) error {
	data := a.prepareAppTemplate()

	commit, err := gh.PrepareTemplatesForCommit([]gh.TemplateToCommit{
		{
			TemplatePath: "internal/templates/ruby_http/app/config.ru.tmpl",
			RepoPath:     "config.ru",
		},
		{
			TemplatePath: "internal/templates/ruby_http/app/.rspec.tmpl",
			RepoPath:     ".rspec",
		},
		{
			TemplatePath: "internal/templates/ruby_http/app/.rbenv-version.tmpl",
			RepoPath:     ".rbenv-version",
		},
		{
			TemplatePath: "internal/templates/ruby_http/app/gemspec.tmpl",
			RepoPath:     fmt.Sprintf("%v.gemspec", data.Name),
		},
		{
			TemplatePath: "internal/templates/ruby_http/app/Gemfile.tmpl",
			RepoPath:     "Gemfile",
		},
		{
			TemplatePath: "internal/templates/ruby_http/app/spec/spec_helper.rb.tmpl",
			RepoPath:     "spec/spec_helper.rb",
		},
		{
			TemplatePath: "internal/templates/ruby_http/app/spec/server_spec.rb.tmpl",
			RepoPath:     fmt.Sprintf("spec/lib/%v/server_spec.rb", data.Name),
		},
		{
			TemplatePath: "internal/templates/ruby_http/app/lib/app.rb.tmpl",
			RepoPath:     fmt.Sprintf("lib/%v.rb", data.Name),
		},
		{
			TemplatePath: "internal/templates/ruby_http/app/lib/server.rb.tmpl",
			RepoPath:     fmt.Sprintf("lib/%v/server.rb", data.Name),
		},
		{
			TemplatePath: "internal/templates/ruby_http/app/lib/routes.rb.tmpl",
			RepoPath:     fmt.Sprintf("lib/%v/server/routes.rb", data.Name),
		},
		{
			TemplatePath: "internal/templates/ruby_http/app/lib/backend.rb.tmpl",
			RepoPath:     fmt.Sprintf("lib/%v/server/backend.rb", data.Name),
		},
	}, data)
	if err != nil {
		return err
	}

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

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

	cmd := exec.Command("bundle")
	cmd.Dir = checkoutPath
	if a.verbose {
		cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
	}
	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("error installing bundle: %v", err)
	}

	cmd = exec.Command("git", "add", "Gemfile.lock")
	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", "generate Gemfile.lock\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 Gemfile.lock: %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)
	}

	log.Printf("twitch-sinatra server ready for %v-specific endpoints", *a.repo.Name)

	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/ruby_http/puppet/hiera.yaml.tmpl",
				RepoPath:     fmt.Sprintf("hiera/environment/%v/%v.yaml", "production", t.Name),
			},
			{
				TemplatePath: "internal/templates/ruby_http/puppet/hiera.yaml.tmpl",
				RepoPath:     fmt.Sprintf("hiera/environment/%v/%v.yaml", "staging", t.Name),
			},
			{
				TemplatePath: "internal/templates/ruby_http/puppet/manifests/init.pp.tmpl",
				RepoPath:     fmt.Sprintf("modules/%v/manifests/init.pp", t.PuppetModuleName),
			},
			{
				TemplatePath: "internal/templates/ruby_http/puppet/manifests/install.pp.tmpl",
				RepoPath:     fmt.Sprintf("modules/%v/manifests/install.pp", t.PuppetModuleName),
			},
			{
				TemplatePath: "internal/templates/ruby_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),
		},
		{
			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
}
