package commands

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"

	"github.com/contiamo/dev/cli/config"
	"github.com/google/go-github/v39/github"
	"github.com/manifoldco/promptui"
	"github.com/spf13/cobra"
	"golang.org/x/oauth2"
)

var repoAliases = map[string]string{
	"ui":                "contiamo-ui",
	"contiamo-ui":       "contiamo-ui",
	"pgql-server":       "pgql-server",
	"pgql":              "pgql-server",
	"graphql":           "pgql-server",
	"idp":               "idp",
	"auth":              "idp",
	"demo-signup":       "demo-signup",
	"demo":              "demo-signup",
	"datastore-manager": "datastore-manager",
	"datastore":         "datastore-manager",
}

var serviceAliases = map[string]string{
	"contiamo-ui":       "ui",
	"ui":                "ui",
	"pgql-server":       "graphql",
	"pgql":              "graphql",
	"graphql":           "graphql",
	"idp":               "auth",
	"auth":              "auth",
	"demo-signup":       "demo",
	"demo":              "demo",
	"datastore-manager": "datastore",
	"datastore":         "datastore",
}

func init() {
	serviceFlags(prPreviewCmd.Flags())
}

const sep = ":"

var prPreviewCmd = &cobra.Command{
	Use:     "pr-preview",
	Aliases: []string{"preview", "pr"},
	Example: `
	Specify the service and PR number
		localdev pr-preview hub:841

	Interactive completion for the PR number
		localdev pr-preview hub
	`,
	Short: "run or restart your dev environment using a PR preview image",
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		services, err := listServices(cmd, false)
		if err != nil {
			return nil, cobra.ShellCompDirectiveError | cobra.ShellCompDirectiveNoFileComp
		}

		return services, cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
	},
	RunE: func(cmd *cobra.Command, args []string) error {

		withIngester := config.WithIngester()
		withPantheonRedir := config.WithPantheonRedir()

		if len(args) == 0 {
			return fmt.Errorf("you much provide the name and pr number of the services to preview using \"<service_name>:<pr_numbers>\"")
		}

		token := strings.TrimSpace(os.Getenv("GITHUB_TOKEN"))
		tokenSource := oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: token},
		)
		client := github.NewClient(oauth2.NewClient(cmd.Context(), tokenSource))

		for idx, option := range args {
			if strings.Contains(option, sep) {
				continue
			}

			if token == "" {
				cmd.SilenceUsage = true
				cmd.SilenceErrors = true
				return fmt.Errorf(
					"Can not access GitHub API!\n"+
						"you must create a personal access token, go to https://github.com/settings/tokens\n"+
						"then set the GITHUB_TOKEN environment variable.\n\n"+
						"Alternatively, find the pull request number from the web UI and use the argument \"%s:<number>\"",
					option,
				)
			}

			result, err := prNumberCompletion(cmd, client, option)
			if err != nil {
				return err
			}

			args[idx] = fmt.Sprintf("%s:%s", option, result)
		}

		exports := []string{}
		for _, option := range args {
			option = strings.ToLower(option)
			parts := strings.Split(option, sep)
			if len(parts) != 2 {
				return fmt.Errorf("argument %q should be of the form \"<service_name>%s<pr_numbers>\"", option, sep)
			}
			repo, service, prNumber := getRepoName(parts[0]), getServiceName(parts[0]), parts[1]
			fmt.Fprintf(cmd.OutOrStdout(), "Pulling image for %s PR %s", service, prNumber)

			pull := exec.CommandContext(
				cmd.Context(),
				"docker",
				"pull",
				fmt.Sprintf("eu.gcr.io/dev-and-test-env/%s-preview:pr-%s", repo, prNumber),
			)
			pull.Dir = config.LocaldevRoot()
			pull.Stderr = cmd.OutOrStderr()
			pull.Stdout = cmd.OutOrStdout()
			pull.Env = os.Environ()

			err := pull.Run()
			if err != nil {
				return err
			}

			exports = append(exports, fmt.Sprintf("%s_IMAGE=eu.gcr.io/dev-and-test-env/%s-preview:pr-%s", strings.ToUpper(service), repo, prNumber))
		}

		err := startLocaldev(cmd, withIngester, withPantheonRedir, exports)
		if err != nil {
			return err
		}

		fmt.Fprintln(cmd.OutOrStdout())
		return printStartMessage(cmd.OutOrStdout())
	},
}

func getServiceName(name string) string {
	alias, ok := serviceAliases[name]
	if !ok {
		return name
	}
	return alias
}

func getRepoName(name string) string {
	alias, ok := repoAliases[name]
	if !ok {
		return name
	}
	return alias
}

func prNumberCompletion(cmd *cobra.Command, client *github.Client, service string) (number string, err error) {
	repo := getRepoName(service)
	prs, _, err := client.PullRequests.List(cmd.Context(), "contiamo", repo, &github.PullRequestListOptions{State: "open"})
	if err != nil {
		return number, err
	}

	items := []prInfo{}
	for _, pull := range prs {
		items = append(items, prInfo{Number: *pull.Number, Title: *pull.Title})
	}

	prompt := promptui.Select{
		Label:     fmt.Sprintf("Select a PR number for %s", service),
		Items:     items,
		Templates: makeTemplates(service),
		Stdin:     io.NopCloser(cmd.InOrStdin()),
		Stdout:    NopWriteCloser(cmd.OutOrStdout()),
	}

	idx, _, err := prompt.Run()
	if err != nil {
		return number, err
	}

	return fmt.Sprintf("%d", items[idx].Number), nil
}

type prInfo struct {
	Number int
	Title  string
}

func makeTemplates(service string) *promptui.SelectTemplates {
	return &promptui.SelectTemplates{
		Label:    "#{{ .Number }} - {{.Title}}",
		Active:   fmt.Sprintf("%s{{ .Number }} - {{.Title}}", promptui.IconSelect),
		Selected: "{{ .Number }}",
		Inactive: "#{{ .Number }} - {{.Title}}",
		Details: `
	--------- PR ----------
	{{ "Number:" | faint }}	{{ .Number }}
	{{ "Title:" | faint }}	{{ .Title }}
	`,
	}

}

func NopWriteCloser(w io.Writer) io.WriteCloser {
	return noopWriteCloser{w: w}
}

type noopWriteCloser struct {
	w io.Writer
}

func (wc noopWriteCloser) Close() error {
	return nil
}

func (wc noopWriteCloser) Write(p []byte) (n int, err error) {
	return wc.w.Write(p)
}
