package util

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"

	"github.com/BurntSushi/toml"
	"github.com/juju/errors"
	"github.com/pingcap/schrodinger/pkg/test_client"
	"github.com/pingcap/schrodinger/pkg/types"
	"github.com/pingcap/schrodinger/util"
)

/*
application managation utils
set up dm-worker/dm-master
kill dm-worker
stop dm-worker
restart dm-worker
*/

var dmWorkerCfgTemplate = `
worker-addr = ":11081"
status-addr = ":11082"

server-id = %d
flavor = "%s"
relay-dir = "./relay_log"
meta-file = "relay.meta"
enable-gtid = false

[from]
host = "%s"
user = "root"
password = ""
port = 3306
`

// MasterConfig is used for creating dm master
type MasterConfig struct {
	MasterAddr string    `toml:"master-addr"`
	StatusAddr string    `toml:"status-addr"`
	Deploy     []*Deploy `toml:"deploy"`
}

// Deploy is master deploy
type Deploy struct {
	MysqlInstance string `toml:"mysql-instance"`
	DmWorker      string `toml:"dm-worker"`
}

// NewMasterConfig creates new master config
func NewMasterConfig(deploy []*Deploy) *MasterConfig {
	return &MasterConfig{
		MasterAddr: ":11083", // hardcode, master api
		StatusAddr: ":11084", // hardcode, pprof api
		Deploy:     deploy,
	}
}

func (m *MasterConfig) toToml() (string, error) {
	buf := new(bytes.Buffer)
	err := toml.NewEncoder(buf).Encode(m)
	if err != nil {
		return "", errors.Trace(err)
	}

	return buf.String(), nil
}

// SetUpDB setups db, returns mysql/mariadb host
// typ support github.com/pingcap/schrodinger/pkg/types.MysqlType or github.com/pingcap/schrodinger/pkg/types.MariaDBType
// they are for mysql or mariadb
// if typ is types.MysqlType support 5.6/5.7/8.0
// if typ is types.MariaDBType support 10.0/10.1/10.2/10.3
// id is identify helper for db name in pod
func SetUpDB(cli *client.Client, typ types.Type, version string, id int) (string, error) {
	// set up mysql server
	app := &types.APPConfig{
		Type:    typ,
		Name:    genDBName(typ, version, id),
		Version: version,
		Port:    3306,
		Labels:  make(map[string]string),
	}

	app.Labels["id"] = strconv.Itoa(id)
	app, err := cli.CreateToolV2(app)
	if err != nil {
		return "", errors.Trace(err)
	}

	return app.Host, nil
}

func genDBName(typ types.Type, version string, id int) string {
	return strings.Replace(fmt.Sprintf("%s-%s-%d", typ, version, id), ".", "-", -1)
}

// setUpDmWorker setups dm worker
func setUpDmWorker(cli *client.Client, typ types.Type, workerID int, mysqlHost string, downloadAddr string) (string, error) {
	// upload dm worker config file
	cfg := fmt.Sprintf(dmWorkerCfgTemplate, workerID, typ, mysqlHost)
	options, err := cli.UploadCfgWithInfo([]byte(cfg), "/dm-worker.toml")
	if err != nil {
		return "", errors.Trace(err)
	}

	// create dm-worker
	app, err := cli.CreateTool(downloadAddr, "dm-worker", fmt.Sprintf("dm-worker-%d", workerID), "-config /dm-worker.toml", "", nil, options...)
	if err != nil {
		return "", errors.Trace(err)
	}
	return app.Host, nil
}

// setUpDmMaster setups dm master
func setUpDmMaster(cli *client.Client, cfg *MasterConfig, downloadAddr string) (string, error) {
	// upload dm master config
	cfgStr, err := cfg.toToml()
	if err != nil {
		return "", errors.Trace(err)
	}

	options, err := cli.UploadCfgWithInfo([]byte(cfgStr), "/dm-master.toml")
	if err != nil {
		return "", errors.Trace(err)
	}

	// create dm-worker
	app, err := cli.CreateTool(downloadAddr, "dm-master", "dm-master", "-config /dm-master.toml", "", nil, options...)
	if err != nil {
		return "", errors.Trace(err)
	}
	return app.Host, nil
}

// SetUpDM setups dm
// typ support github.com/pingcap/schrodinger/pkg/types.MysqlType or github.com/pingcap/schrodinger/pkg/types.MariaDBType
// if typ is types.MysqlType support 5.6/5.7/8.0
// if typ is types.MariaDBType support 10.0/10.1/10.2/10.3
// if do not know downloadAddr, just set to "" and will use latest dm
func SetUpDM(cli *client.Client, workerCount int, dbType types.Type, version string, downloadAddr string) (string, []*Deploy, error) {
	if util.IsEmpty(downloadAddr) {
		downloadAddr = "http://download.pingcap.org/dm-latest-linux-amd64.tar.gz"
	}
	var deploys []*Deploy

	for i := 0; i < workerCount; i++ {
		workerID := 100 + i
		host, err := SetUpDB(cli, dbType, version, workerID)
		if err != nil {
			return "", nil, errors.Annotatef(err, "setUpDB failed for %s version: %s id: %d", dbType, version, workerID)
		}

		worker, err := setUpDmWorker(cli, dbType, workerID, host, downloadAddr)
		if err != nil {
			return "", nil, errors.Annotatef(err, "setUpDMWorker failed %d, %s:%s ", workerID, dbType, host)
		}
		deploy := &Deploy{
			MysqlInstance: fmt.Sprintf("%s:3306", host),
			DmWorker:      fmt.Sprintf("%s:11081", worker),
		}
		deploys = append(deploys, deploy)
	}

	masterCfg := NewMasterConfig(deploys)

	master, err := setUpDmMaster(cli, masterCfg, downloadAddr)

	if err != nil {
		return "", nil, errors.Trace(err)
	}

	return fmt.Sprintf("%s:11083", master), deploys, nil
}
