package main

import (
	"context"
	"fmt"
	"math/rand"
	"net/http"
	"strings"
	"time"

	"github.com/juju/errors"
	myutil "github.com/pingcap/schrodinger-test/util"
	"github.com/pingcap/schrodinger/pkg/test_client"
	"github.com/pingcap/schrodinger/util"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

type nemesis struct {
	ctx        context.Context
	cfg        *config
	cli        *client.Client
	nodes      []string
	components []string
}

func (n *nemesis) init() error {
	if n.cfg.component == "all" {
		n.components = []string{"tidb", "tikv", "pd"}
	} else {
		n.components = strings.Split(n.cfg.component, ",")
	}

	for _, cp := range n.components {
		n.nodes = append(n.nodes, getNodes(n.cli, cp)...)
	}

	return nil
}

func createNemesis(ctx context.Context, cfg *config, cli *client.Client) (*nemesis, error) {
	n := &nemesis{
		ctx: ctx,
		cfg: cfg,
		cli: cli,
	}

	err := n.init()
	if err != nil {
		return nil, errors.Trace(err)
	}

	return n, nil
}

func (n *nemesis) Run() error {
	openGeneralLog(n.cli)
	ticker := time.NewTicker(n.cfg.interval)
	defer ticker.Stop()
	for {
		select {
		case <-n.ctx.Done():
			return nil
		case <-ticker.C:
			client.Infof("start run..")
			err := n.runOnce()
			if err != nil {
				return errors.Trace(err)
			}
		}
	}
}

func (n *nemesis) runOnce() error {
	node := n.nodes[rand.Intn(len(n.nodes))]
	err := n.stopNode(node)
	client.Debugf("stop node %s", node)
	if err != nil {
		return errors.Trace(err)
	}
	time.Sleep(n.cfg.recoverInterval)
	client.Debugf("start node %s", node)
	return errors.Trace(n.startNode(node))
}

func (n *nemesis) stopNode(host string) error {
	ip, name := getIPAndName(host)

	if !util.IsEmpty(n.cfg.notifyDDL) {
		err := n.notifyStop(ip, name)
		if err != nil {
			return errors.Trace(err)
		}
	}
	return errors.Trace(stopProcess(ip, name))
}

func (n *nemesis) startNode(host string) error {
	ip, name := getIPAndName(host)

	err := startProcess(ip, name)
	if err != nil {
		return errors.Trace(err)
	}

	time.Sleep(5 * time.Minute)

	if !util.IsEmpty(n.cfg.notifyDDL) {
		err := n.notifyStart(ip, name)
		if err != nil {
			return errors.Trace(err)
		}
	}
	return nil
}

func (n *nemesis) notifyStop(ip, component string) error {
	return errors.Trace(n.notify(ip, component, "stop"))
}

func (n *nemesis) notifyStart(ip, component string) error {
	return errors.Trace(n.notify(ip, component, "start"))
}

func (n *nemesis) notify(ip, component, operation string) error {
	tests := n.cli.GetTests()
	ddlIP := ""
	for _, test := range tests {
		if test.Name == n.cfg.notifyDDL {
			ddlIP = test.IP
			break
		}
	}

	if util.IsEmpty(ddlIP) {
		return errors.NotFoundf("ddl ip")
	}

	data, err := util.DoGet(http.DefaultClient, fmt.Sprintf("http://%s:8080/kill?ip=%s&component=%s&operation=%s", ddlIP, ip, component, operation))
	if err != nil {
		return errors.Trace(err)
	}

	if string(data) != "ok" {
		return errors.NotValidf("error %s", string(data))
	}

	return nil
}

func getIPAndName(host string) (string, string) {
	s := strings.Split(host, ":")
	return s[0], s[1]
}

func getNodes(cli *client.Client, name string) []string {
	switch name {
	case "tidb":
		return getHost(cli.TiDBs(), "tidb")
	case "tikv":
		return getHost(cli.TiKVs(), "tikv")
	case "pd":
		return getHost(cli.PDs(), "pd")
	}
	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)
		}
	}
}

func getHost(nodes []string, name string) []string {
	var hosts []string
	for _, node := range nodes {
		hosts = append(hosts, fmt.Sprintf("%s:%s", strings.Split(node, ":")[0], name))
	}
	return hosts
}
