package pd_ctl

import (
	"bytes"
	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"github.com/pingcap/schrodinger-test/once/drivers"
	pd_driver "github.com/pingcap/schrodinger-test/once/drivers/pd"
	"github.com/pingcap/schrodinger-test/once/repack/archive"
	"github.com/pingcap/schrodinger-test/util"
	"go.uber.org/zap"
	"golang.org/x/net/context"
	"os"
	"os/exec"
	"path"
	"strings"
)

var _ pd_driver.PDDriver = &PDCtl{}
var _ pd_driver.StoreDriver = &PDCtl{}

var ctl *PDCtl

func init() {
	ctl = &PDCtl{
		ctx:      nil,
		bin:      "",
		endpoint: "",
	}

	drivers.Register("pd", ctl.DriverType(), ctl)
	drivers.Register("store", ctl.DriverType(), ctl)
}

// Install pd-ctl with `go get`, return pd-ctl abs path and error
func InstallFromGo(tag string) (string, error) {
	cmd := exec.Command("go", "get", "github.com/pingcap/pd/tools/pd-ctl@"+tag)
	cmd.Dir = os.TempDir()
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()
	if err != nil {
		log.Error("go get pd-ctl error",
			zap.String("tag", tag),
			zap.String("stderr", stderr.String()),
			zap.String("stdout", stdout.String()),
			zap.Error(err))
		return "", err
	}
	return os.Getenv("GOPATH") + "/bin/pd-ctl", nil
}

// Install pd-ctl, return pd-ctl abs path and error
func InstallPDCtl(ctx context.Context, tag string, dest string) (string, error) {
	ref, err := archive.ResolveFromFileServer("pd", tag)
	if err != nil {
		log.Error("Failed to resolve PD location on file server", zap.String("tag", tag), zap.Error(err))
		return "", err
	}
	err = os.MkdirAll(dest, os.ModePerm)
	if err != nil {
		log.Error("Failed to mkdir", zap.String("dir", dest), zap.Error(err))
		return "", err
	}
	err = util.InstallArchive(ctx, ref.Addr, dest)
	if err != nil {
		log.Error("Failed to install pd-ctl", zap.Error(err))
	}
	return path.Clean(dest) + "/bin/pd-ctl", err
}

// pd-ctl driver
type PDCtl struct {
	ctx      context.Context
	bin      string
	endpoint string
}

func (p *PDCtl) SetBin(bin string) {
	p.bin = bin
}

func (p *PDCtl) SetEndpoint(endpoint string) {
	p.endpoint = endpoint
}

func (p *PDCtl) SetCtx(ctx context.Context) {
	p.ctx = ctx
}

// Execute `pd-ctl` base command
func (p *PDCtl) Exec(command ...string) ([]byte, error) {
	cmdString := []string{"-u", p.endpoint, "-d"}
	cmdString = append(cmdString, command...)
	var cmd *exec.Cmd
	if p.ctx != nil {
		cmd = exec.CommandContext(p.ctx, p.bin, cmdString...)
	} else {
		cmd = exec.Command(p.bin, cmdString...)
	}
	log.Info("Execute", zap.String("bin", cmd.Path),
		zap.String("args", strings.Join(cmdString, " ")))
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()
	if err != nil {
		log.Error("Execute command error",
			zap.String("command", strings.Join(append([]string{p.bin}, cmdString...), " ")),
			zap.String("stderr", stderr.String()),
			zap.String("stdout", stdout.String()),
			zap.Error(err))
		return nil, err
	}
	errInfo := stderr.Bytes()
	if len(errInfo) != 0 {
		log.Error("stderr is not empty", zap.String("stderr", string(errInfo)))
		err = errors.New(string(errInfo))
	}
	return stdout.Bytes(), err
}

func (p *PDCtl) DriverType() drivers.DriverType {
	return drivers.DRIVER_TYPE__CMD
}

func Setup(ctx context.Context, bin, endpoint string) {
	ctl.SetBin(bin)
	ctl.SetEndpoint(endpoint)
	ctl.SetCtx(ctx)
}
