package cmd

import (
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"text/template"

	"github.com/AlecAivazis/survey/v2"
	"github.com/appist/appy/support"
)

var (
	scaffoldQ = []*survey.Question{
		{
			Name: "description",
			Prompt: &survey.Input{
				Message: "What does your app do?",
				Help:    "Note: This will be used in HTML's description meta tag, package.json and CLI help message.",
				Default: "",
			},
		},
		{
			Name: "dbAdapter",
			Prompt: &survey.Select{
				Message: "Which database adapter would you like to use?",
				Options: support.SupportedDBAdapters,
				Default: "postgres",
			},
		},
	}
)

// Scaffold creates a new application.
func Scaffold() error {
	answers := struct {
		Description string `survey:"description"`
		DBAdapter   string `survey:"dbAdapter"`
	}{}

	err := survey.Ask(scaffoldQ, &answers)
	if err != nil {
		return err
	}

	moduleName := support.ModuleName()
	_, dirname, _, _ := runtime.Caller(0)
	tplPath := filepath.Dir(dirname) + "/templates/scaffold"

	masterKeyDev := hex.EncodeToString(support.GenerateRandomBytes(32))
	masterKeyTest := hex.EncodeToString(support.GenerateRandomBytes(32))

	var dbURIPrimaryDev, dbURIPrimaryTest string
	switch answers.DBAdapter {
	case "mysql":
		dbURIPrimaryDev = getEncryptedValue(fmt.Sprintf("mysql://root:whatever@0.0.0.0:23306/%s", moduleName), masterKeyDev)
		dbURIPrimaryTest = getEncryptedValue(fmt.Sprintf("mysql://root:whatever@0.0.0.0:23306/%s_test", moduleName), masterKeyTest)
	case "postgres":
		dbURIPrimaryDev = getEncryptedValue(fmt.Sprintf("postgresql://postgres:whatever@0.0.0.0:25432/%s?sslmode=disable&connect_timeout=5", moduleName), masterKeyDev)
		dbURIPrimaryTest = getEncryptedValue(fmt.Sprintf("postgresql://postgres:whatever@0.0.0.0:25432/%s_test?sslmode=disable&connect_timeout=5", moduleName), masterKeyTest)
	}

	httpCSRFSecretDev := getEncryptedValue(hex.EncodeToString(support.GenerateRandomBytes(32)), masterKeyDev)
	httpSessionSecretsDev := getEncryptedValue(hex.EncodeToString(support.GenerateRandomBytes(32)), masterKeyDev)
	workerRedisAddrDev := getEncryptedValue("0.0.0.0:26379", masterKeyDev)

	httpCSRFSecretTest := getEncryptedValue(hex.EncodeToString(support.GenerateRandomBytes(32)), masterKeyTest)
	httpSessionSecretsTest := getEncryptedValue(hex.EncodeToString(support.GenerateRandomBytes(32)), masterKeyTest)
	workerRedisAddrTest := getEncryptedValue("0.0.0.0:26379", masterKeyTest)

	if err := filepath.Walk(tplPath,
		func(src string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}

			dest := strings.ReplaceAll(src, tplPath+"/", "")
			dest = strings.TrimSuffix(dest, ".tpl")
			if info.IsDir() {
				if err := os.MkdirAll(dest, 0777); err != nil {
					return err
				}

				return nil
			}

			buf, err := ioutil.ReadFile(src)
			if err != nil {
				return err
			}

			file, err := os.Create(dest)
			if err != nil {
				return err
			}

			tpl, err := template.New("scaffold").Parse(strings.ReplaceAll(string(buf), "// Code generated by github.com/appist/appy.\n\n", ""))
			if err != nil {
				return err
			}

			return tpl.Execute(file, map[string]string{
				"assetWelcomeCSS":         "{{assetPath(`/styles/welcome.css`)}}",
				"blockHead":               "{{block head()}}",
				"blockBody":               "{{block body()}}",
				"blockEnd":                "{{end}}",
				"dbAdapter":               answers.DBAdapter,
				"dbURIPrimaryDev":         dbURIPrimaryDev,
				"httpCSRFSecretDev":       httpCSRFSecretDev,
				"httpSessionSecretsDev":   httpSessionSecretsDev,
				"workerRedisAddrDev":      workerRedisAddrDev,
				"dbURIPrimaryTest":        dbURIPrimaryTest,
				"httpCSRFSecretTest":      httpCSRFSecretTest,
				"httpSessionSecretsTest":  httpSessionSecretsTest,
				"workerRedisAddrTest":     workerRedisAddrTest,
				"extendApplicationLayout": "{{extends \"../layouts/application.html\"}}",
				"projectName":             moduleName,
				"projectDesc":             answers.Description,
				"masterKeyDev":            masterKeyDev,
				"masterKeyTest":           masterKeyTest,
				"translateWelcome":        "{{t(\"welcome\", `{\"Name\": \"John Doe\", \"Title\": \"` + t(\"title\") + `\"}`)}}",
				"yieldHead":               "{{yield head()}}",
				"yieldBody":               "{{yield body()}}",
			})
		}); err != nil {
		return err
	}

	version := strings.ReplaceAll(runtime.Version(), "go", "")
	re := regexp.MustCompile(`[a-zA-Z].*`)
	version = re.ReplaceAllString(version, "")
	versionSplits := strings.Split(version, ".")

	err = ioutil.WriteFile(
		"go.mod",
		[]byte(`module `+moduleName+`

go `+versionSplits[0]+"."+versionSplits[1]+`

require (
	github.com/99designs/gqlgen v0.11.3
	github.com/appist/appy latest
	github.com/vektah/gqlparser/v2 v2.0.1
)`),
		0777,
	)

	if err != nil {
		return err
	}

	err = installBackend()
	if err != nil {
		return err
	}

	err = installFrontend()
	if err != nil {
		return err
	}

	return nil
}

func getEncryptedValue(value string, masterKey string) string {
	plaintext := []byte(value)
	ciphertext, _ := support.AESEncrypt(plaintext, []byte(masterKey))

	return hex.EncodeToString(ciphertext)
}

func installBackend() error {
	cmd := exec.Command("go", "mod", "download")
	cmd.Env = os.Environ()
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return err
	}

	return nil
}

func installFrontend() error {
	cmd := exec.Command("npm", "install")
	cmd.Env = os.Environ()
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return err
	}

	return nil
}
