package cmd

import (
	"bytes"
	_ "embed"
	"fmt"
	"go/format"
	"io/ioutil"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"text/template"

	"code.justin.tv/cplat/twitchling/project"
	"github.com/spf13/cobra"
)

// genCmd represents the gen command
var genCmd = &cobra.Command{
	Use:   "gen",
	Short: "generate bundle.gen.go file that contains message ID constants and LoadBundle method",
	Long:  ``,
	RunE:  runGen,
}

var messageIDRegexp = regexp.MustCompile(`[A-Za-z_][A-Za-z0-9_\-.]*`)
var messageIDReplacer = strings.NewReplacer(".", "_", "-", "_")

//go:embed templates/bundle.go.tmpl
var codegenTemplate string

func init() {
	rootCmd.AddCommand(genCmd)

}

func runGen(cmd *cobra.Command, args []string) error {
	project, err := project.LoadProject(projectFile)
	if err != nil {
		return fmt.Errorf("code gen: failed to load project file %s\n%v", projectFile, err)
	}

	contents, err := generateBundle(project)
	if err != nil {
		return err
	}

	bundlePath := path.Join(filepath.Dir(projectFile), "bundle.gen.go")
	err = ioutil.WriteFile(bundlePath, contents, 0644)
	if err != nil {
		return err
	}

	cmd.Println("DONE: bundle.gen.go generated")
	return nil
}

func generateBundle(project *project.Project) ([]byte, error) {
	messageIDs := map[string]string{}
	for _, message := range project.Messages {
		name, err := convertMessageIDToGoName(message.ID)
		if err != nil {
			return nil, fmt.Errorf("code gen: %v", err)
		}

		messageIDs[name] = message.ID
	}

	tmpl, err := template.New("bundgle.gen.go").Parse(codegenTemplate)
	if err != nil {
		return nil, fmt.Errorf("code gen: %v", err)
	}

	input := struct{ MessageIDs map[string]string }{messageIDs}

	src := bytes.Buffer{}
	err = tmpl.Execute(&src, input)
	if err != nil {
		return nil, fmt.Errorf("code gen: %v", err)
	}

	bytes, err := format.Source(src.Bytes())
	if err != nil {
		return nil, fmt.Errorf("code gen: %v", err)
	}

	return bytes, nil
}

func convertMessageIDToGoName(messageID string) (string, error) {
	if !messageIDRegexp.Match([]byte(messageID)) {
		return "", fmt.Errorf("code gen: message id %s contains invalid characters", messageID)
	}

	return fmt.Sprintf("MessageID_%s", messageIDReplacer.Replace(messageID)), nil
}
