package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/juju/errors"
	"github.com/ngaut/log"
	myutil "github.com/pingcap/schrodinger-test/util"
	"github.com/pingcap/schrodinger/pkg/test_client"
	"golang.org/x/net/context"
)

var (
	mode           string
	notifyDDL      string
	breakInterval  time.Duration
	waitInterval   time.Duration
	notifyInterval time.Duration
)

func init() {
	flag.StringVar(&mode, "mode", "normal", "panic mode. can be full or random")
	flag.StringVar(&notifyDDL, "notify-ddl", "", "notify ddl name")
	flag.DurationVar(&breakInterval, "break-interval", 10*time.Minute, "what interval will kill a node")
	flag.DurationVar(&notifyInterval, "notify-interval", 3*time.Minute, "what interval will notify")
	flag.DurationVar(&waitInterval, "wait-interval", 10*time.Minute, "what interval to wait")
}

func main() {
	flag.Parse()
	client.Info("[upgrade] start...")

	ctx, cancel := context.WithCancel(context.Background())
	cli := client.New(ctx, client.ModeOnline)

	sc := make(chan os.Signal, 1)
	signal.Notify(sc,
		syscall.SIGHUP,
		syscall.SIGINT,
		syscall.SIGTERM,
		syscall.SIGQUIT)
	go func() {
		sig := <-sc
		client.Infof("[upgrade] Got signal [%s] to exist.", sig)
		cancel()
		os.Exit(0)
	}()

	var pairs []*Pair
	var err error

	client.Debugf("mode %s", mode)
	if mode == "normal" {
		pairs, err = runNormalUpgrade(cli)
	} else if mode == "test" {
		pairs, err = runTest(cli)
	} else {
		pairs, err = runFullUpgrade(cli)
	}
	if err != nil {
		client.Fatalf("run failed %+v", err)
	}
	client.Debugf("result:%+v", pairs)
	var line string
	var panic bool
	for _, p := range pairs {
		if p.Status != "SUCCESS" {
			panic = true
		}
		line += fmt.Sprintf("source: %s, target:%s statue:%s, error:%s", p.Source, p.Target, p.Status, p.Errors)
	}

	if panic {
		client.Fatalf(line)
	} else {
		client.Infof(line)
	}

	err = removeTests(cli)
	if err != nil {
		log.Errorf("remove test error %v", err)
	}
}

func runNormalUpgrade(cli *client.Client) ([]*Pair, error) {
	var pairs []*Pair
	sourceVersions := getSourceCatVersions()
	targetVersion := getTargetCatVersion(cli)

	for _, version := range sourceVersions {
		time.Sleep(breakInterval)
		p := &Pair{
			Source: version.Tidb.Value,
			Target: targetVersion.Tidb.Value,
		}
		client.Infof("start to upgrade cluster %v, target version %v", version, targetVersion)
		err := upgradeAndVerify(cli, version, targetVersion)
		if err != nil {
			p.Status = "FAIL"
			p.Errors = err.Error()
			pairs = append(pairs, p)
			client.Errorf("upgrade error%v", err)
			continue
		}

		p.Status = "SUCCESS"
		pairs = append(pairs, p)
		client.Infof("upgrade cluster success %v", version)
	}
	return pairs, nil
}

func runTest(cli *client.Client) ([]*Pair, error) {
	versions := []string{"v2.1.0-rc.1 v2.1.0-rc.2"}
	var pairs []*Pair
	for _, version := range versions {
		vers := strings.Split(version, " ")
		client.Infof("start to upgrade cluster %v, target version %v", vers[0], vers[1])
		p := &Pair{
			Source: vers[0],
			Target: vers[1],
		}

		ver := buildVersion(vers[0])
		targetVersion := buildVersion(vers[1])

		err := testUpgradeAndVerify(cli, ver, targetVersion)
		if err != nil {
			p.Status = "FAIL"
			p.Errors = err.Error()
			pairs = append(pairs, p)
			client.Errorf("upgrade error%v", err)
			continue
		}

		p.Status = "SUCCESS"
		pairs = append(pairs, p)
		client.Infof("upgrade cluster success %v", version)
	}
	return pairs, nil
}

func runFullUpgrade(cli *client.Client) ([]*Pair, error) {
	var pairs []*Pair
	versions, err := getVersionsFromJSON()
	if err != nil {
		return nil, errors.Trace(err)
	}
	for _, version := range versions {
		time.Sleep(breakInterval)
		client.Infof("start to upgrade cluster %v, target version %v", version.Source.Tidb.Value, version.Target.Tidb.Value)
		p := &Pair{
			Source: version.Source.Tidb.Value,
			Target: version.Target.Tidb.Value,
		}

		err := upgradeAndVerify(cli, version.Source, version.Target)
		if err != nil {
			p.Status = "FAIL"
			p.Errors = err.Error()
			pairs = append(pairs, p)
			client.Errorf("upgrade error%v", err)
			continue
		}

		p.Status = "SUCCESS"
		pairs = append(pairs, p)
		client.Infof("upgrade cluster success %v", version)
	}
	return pairs, nil

}

func upgradeAndVerify(cli *client.Client, version *Version, targetVersion *Version) error {
	client.Info("stop tests")
	err := stopTests(cli)
	if err != nil {
		return errors.Trace(err)
	}

	data, _ := json.Marshal(version)
	client.Infof("start use version to upgrade %s", string(data))

	err = replaceCat(cli, version)
	if err != nil {
		return errors.Trace(err)
	}

	openGeneralLog(cli)

	client.Info("starts tests")
	err = startTests(cli)
	if err != nil {
		return errors.Trace(err)
	}

	data, _ = json.Marshal(targetVersion)
	client.Infof("start upgrade to %s", string(data))
	err = upgradeCatV2(cli, targetVersion)
	if err != nil {
		return errors.Trace(err)
	}

	// wait wait interval
	time.Sleep(waitInterval)

	return nil
}

func testUpgradeAndVerify(cli *client.Client, version *Version, targetVersion *Version) error {
	data, _ := json.Marshal(targetVersion)
	client.Infof("start upgrade to %s", string(data))
	err := upgradeCatV2(cli, targetVersion)
	if err != nil {
		return errors.Trace(err)
	}
	return nil
}

func openGeneralLog(cli *client.Client) {
	for _, ipPort := range cli.TiDBs() {
		ipPortArr := strings.Split(ipPort, ":")
		client.Debugf("open %s general log", ipPortArr[0])
		err := myutil.OpenGeneralLog(ipPortArr[0])
		if err != nil {
			client.Errorf("open ip general error %v", err)
		}
	}
}
