package bbgo

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"text/template"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

var wrapperTemplate = template.Must(template.New("main").Parse(`// Code generated by bbgo; DO NOT EDIT.

package main

import (
	"github.com/c9s/bbgo/pkg/bbgo"
	"github.com/c9s/bbgo/pkg/cmd"

{{- range .Imports }}
	_ "{{ . }}"
{{- end }}
)

func init() {
	bbgo.SetWrapperBinary()
}

func main() {
	cmd.Execute()
}

`))

// generateRunFile renders the wrapper main.go template
func generateRunFile(filepath string, config *Config, imports []string) error {
	var buf = bytes.NewBuffer(nil)
	if err := wrapperTemplate.Execute(buf, struct {
		Config  *Config
		Imports []string
	}{
		Config:  config,
		Imports: imports,
	}); err != nil {
		return err
	}

	return os.WriteFile(filepath, buf.Bytes(), 0644)
}

// compilePackage generates the main.go file of the wrapper package
func compilePackage(packageDir string, userConfig *Config, imports []string) error {
	if _, err := os.Stat(packageDir); os.IsNotExist(err) {
		if err := os.MkdirAll(packageDir, 0777); err != nil {
			return errors.Wrapf(err, "can not create wrapper package directory: %s", packageDir)
		}
	}

	mainFile := filepath.Join(packageDir, "main.go")
	if err := generateRunFile(mainFile, userConfig, imports); err != nil {
		return errors.Wrap(err, "compile error")
	}

	return nil
}

// Build builds the bbgo wrapper binary with the given build target config
func Build(ctx context.Context, userConfig *Config, targetConfig BuildTargetConfig) (string, error) {
	// combine global imports and target imports
	imports := append(userConfig.Build.Imports, targetConfig.Imports...)

	buildDir := userConfig.Build.BuildDir
	if len(buildDir) == 0 {
		buildDir = "build"
	}

	packageDir, err := os.MkdirTemp(buildDir, "bbgow-") // with prefix bbgow
	if err != nil {
		return "", err
	}

	if err := compilePackage(packageDir, userConfig, imports); err != nil {
		return "", err
	}

	cwd, err := os.Getwd()
	if err != nil {
		return "", err
	}

	var buildEnvs []string

	if targetConfig.OS != runtime.GOOS {
		buildEnvs = append(buildEnvs, "GOOS="+targetConfig.OS)
	}

	if targetConfig.Arch != runtime.GOARCH {
		buildEnvs = append(buildEnvs, "GOARCH="+targetConfig.Arch)
	}

	buildTarget := filepath.Join(cwd, packageDir)

	binary := targetConfig.Name
	if len(binary) == 0 {
		binary = fmt.Sprintf("bbgow-%s-%s", targetConfig.OS, targetConfig.Arch)
	}

	output := filepath.Join(buildDir, binary)

	args := []string{"build", "-tags", "wrapper", "-o", output, buildTarget}
	logrus.Debugf("building binary %s from %s: go %v", output, buildTarget, args)
	buildCmd := exec.CommandContext(ctx, "go", args...)
	buildCmd.Env = append(os.Environ(), buildEnvs...)
	buildCmd.Stdout = os.Stdout
	buildCmd.Stderr = os.Stderr
	if err := buildCmd.Run(); err != nil {
		return output, err
	}

	return output, os.RemoveAll(packageDir)
}

// BuildTarget builds the one of the targets.
func BuildTarget(ctx context.Context, userConfig *Config, target BuildTargetConfig) (string, error) {
	buildDir := userConfig.Build.BuildDir
	if len(buildDir) == 0 {
		buildDir = "build"
	}

	if _, err := os.Stat(buildDir); os.IsNotExist(err) {
		err = os.Mkdir(buildDir, 0777)
		if err != nil {
			return "", err
		}
	}

	buildDir = filepath.Join(userConfig.Build.BuildDir, target.Name)
	return Build(ctx, userConfig, target)
}
