package main

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"strings"

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

var (
	DMMasterPort = 8261
	DMWorkerPort = 8262
)

var dmWorkerCfgTemplate = `
worker-addr = ":%[4]d"

server-id = %[1]d
flavor = "mysql"
relay-dir = "./relay_log"
enable-gtid = %[2]v
source-id = "%[3]s:3306"

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

[purge]
interval = 300
expires = 1
remain-space = 15
`

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

// Deploy is a deploy config segment in dm-master.toml
type Deploy struct {
	SourceID string `toml:"source-id"`
	DmWorker string `toml:"dm-worker"`
}

// NewMasterConfig creates new master config
func NewMasterConfig(deploy []*Deploy) *MasterConfig {
	return &MasterConfig{
		MasterAddr: fmt.Sprintf(":%d", DMMasterPort),
		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
}

func setUpReplicationMySQL(cli *client.Client, flavor string, version string, id int) ([]string, error) {
	var ty types.Type
	switch flavor {
	case "mysql":
		ty = types.MysqlType
	case "mariadb":
		ty = types.MariaDBType
	default:
		return nil, errors.Errorf("unrecognized MySQL flavor `%s`", flavor)
	}
	// create two mysql or mariadb instances, in master-master architecture
	mysqls, err := util.CreateReplication(cli, 2, ty, version, id)
	if err != nil {
		return nil, errors.Annotatef(err, "create two %s instances error", flavor)
	}
	return mysqls, nil
}

func setUpGTID(mysqls map[int]*replicationMySQL, masterSlaveSwitch bool, flavor string) error {
	var typ types.Type
	switch flavor {
	case "mysql":
		typ = types.MysqlType
	case "mariadb":
		typ = types.MariaDBType
	default:
		return errors.Errorf("unrecognized MySQL flavor `%s`", flavor)
	}

	for _, dbReplic := range mysqls {
		var mysqlHosts []string
		if masterSlaveSwitch {
			mysqlHosts = []string{dbReplic.hosts[0], dbReplic.hosts[1]}
		} else {
			mysqlHosts = []string{dbReplic.hosts[0]}
		}

		dbs, err := initialMySQLDBs(mysqlHosts, "")
		if err != nil {
			return errors.Annotatef(err, "setup gtid with error")
		}
		defer func() {
			for _, db := range dbs {
				db.Close()
			}
		}()

		err = tryEnableGTID(dbs...)
		if err != nil {
			return errors.Annotatef(err, "enable GTID with error")
		}

		if masterSlaveSwitch {
			for index := range mysqlHosts {
				var other int
				if index == 0 {
					other = 1
				} else {
					other = 0
				}
				meta, err := util.GetMasterPos(dbs[other], typ)
				if err != nil {
					return errors.Trace(err)
				}
				sqls := []string{
					"STOP SLAVE;",
					fmt.Sprintf("CHANGE MASTER TO MASTER_HOST = '%s', "+
						"MASTER_USER = 'replicator', MASTER_PASSWORD = 'password', "+
						"MASTER_LOG_FILE = '%s', MASTER_LOG_POS = %d;",
						mysqlHosts[other], meta.BinLogName, meta.BinLogPos),
					"START SLAVE;",
				}
				for _, sql := range sqls {
					db := dbs[index]
					_, err = db.Exec(sql)
					if err != nil {
						return errors.Trace(err)
					}
				}
			}
		}
	}

	return nil
}

func setUpMySQL(cli *client.Client, flavor string, version string, id int) (string, error) {
	// set up mysql server
	var ty types.Type
	switch flavor {
	case "mysql":
		ty = types.MysqlType
	case "mariadb":
		ty = types.MariaDBType
	default:
		return "", errors.Errorf("unrecognized MySQL flavor `%s`", flavor)
	}

	mysql, err := cli.CreateToolV2(&types.APPConfig{
		Type:    ty,
		Name:    fmt.Sprintf("%s-%s-%d", flavor, strings.Replace(version, ".", "-", -1), id),
		Version: version,
		Port:    3306,
	})
	if err != nil {
		return "", errors.Trace(err)
	}

	return mysql.Host, nil
}

// updateDmTaskConfig is used in master-slave-switch, it only updates mysql host
// in dm task config. the config file locates in test case node
func updateDmTaskConfig(testCaseHost, taskCfgName, oldMySQL, newMySQL string) error {
	wd, err := os.Getwd()
	if err != nil {
		return errors.Trace(err)
	}
	configFile := filepath.Join(wd, taskCfgName)
	command := fmt.Sprintf("sed -i 's/host: %s/host: %s/g' %s", oldMySQL, newMySQL, configFile)
	err = util.AgentExec(testCaseHost, command)
	if err != nil {
		return errors.Trace(err)
	}
	return nil
}

// updateDmWorkerConfig is used in master-slave-switch, it only updates mysql
// host in dm worker config. the config file locates in each dm-worker node.
func updateDmWorkerConfig(workerHost, oldMySQL, newMySQL string) error {
	command := fmt.Sprintf("sed -i 's/host = \"%s/host = \"%s/g' /dm-worker.toml", oldMySQL, newMySQL)
	err := util.AgentExec(workerHost, command)
	if err != nil {
		return errors.Trace(err)
	}
	return nil
}

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

	app := &types.APPConfig{
		URL:        downloadAddr,
		BinaryName: "dm-worker",
		Name:       fmt.Sprintf("dm-worker-%d", workerID),
		Args:       "-config /dm-worker.toml",
		Envs:       nil,
		Actions:    actions,
		ResourceRequirement: &types.ResourceRequirement{
			CPU:    "2",
			Memory: memoryQuota,
		},
	}
	// create dm-worker
	config, err := cli.CreateToolV2(app)
	if err != nil {
		return "", errors.Trace(err)
	}
	return config.Host, nil
}

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

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

	app := &types.APPConfig{
		URL:        downloadAddr,
		BinaryName: "dm-master",
		Name:       "dm-master",
		Args:       "-config /dm-master.toml",
		Envs:       nil,
		Actions:    actions,
		ResourceRequirement: &types.ResourceRequirement{
			CPU:    "2",
			Memory: "2Gi",
		},
	}
	// create dm-master
	config, err := cli.CreateToolV2(app)
	if err != nil {
		return "", errors.Trace(err)
	}
	return config.Host, nil
}
