package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/juju/errors"
	"github.com/ngaut/log"
	agClient "github.com/pingcap/schrodinger/agent/client"
	"github.com/pingcap/schrodinger/agent/daemon"
	"k8s.io/apimachinery/pkg/util/wait"
)

const (
	//AgentPort is agent port
	AgentPort = 8350
)

func startProcess(ip, process string) error {
	clients := genClients([]string{ip})
	return errors.Trace(agentDoRestart(clients, process))
}

func stopProcess(ip, process string) error {
	clients := genClients([]string{ip})
	return errors.Trace(agentDoKill(clients, process))
}

func downProcess(ip, process string) error {
	clients := genClients([]string{ip})
	return errors.Trace(agentDoDown(clients, process))
}

func genClients(ips []string) []*agClient.Client {
	var clients []*agClient.Client
	for _, ip := range ips {
		clients = append(clients, agClient.NewClient(fmt.Sprintf("%s:%d", ip, AgentPort)))
	}
	return clients
}

func agentDoRestart(clients []*agClient.Client, processName string) error {
	for _, cli := range clients {
		err := wait.Poll(20*time.Second, 5*time.Minute, func() (bool, error) {
			if err := cli.RestartProcess(processName); err != nil {
				if strings.Contains(err.Error(), "start process from state [RUNNING]") {
					return true, nil
				}

				log.Errorf("restart %s failed: %v, retry", processName, err)
				return false, nil
			}

			if err := checkProcessState(cli, processName, daemon.ProcStatRunning); err != nil {
				log.Errorf("check %s expect status %s failed, error: %v, retry", processName, daemon.ProcStatRunning, err)
				return false, nil
			}
			return true, nil
		})

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

func agentDoKill(clients []*agClient.Client, processName string) error {
	for _, cli := range clients {
		err := wait.Poll(10*time.Second, 1*time.Minute, func() (bool, error) {
			if err := cli.StopProcess(processName); err != nil {
				log.Errorf("stop %s failed: %v, retry", processName, err)
				return false, nil
			}

			if err := checkProcessState(cli, processName, daemon.ProcStatKilled); err != nil {
				return true, errors.Trace(err)
			}
			return true, nil
		})

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

func agentDoDown(clients []*agClient.Client, processName string) error {
	for _, cli := range clients {
		err := wait.Poll(10*time.Second, 1*time.Minute, func() (bool, error) {
			if err := cli.ShutdownProcess(processName); err != nil {
				log.Errorf("shutdown %s failed: %v, retry", processName, err)
				return false, nil
			}

			if err := checkProcessState(cli, processName, daemon.ProcStatStopped); err != nil {
				return true, errors.Trace(err)
			}
			return true, nil
		})

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

func checkProcessState(cli *agClient.Client, processName string, state daemon.ProcessState) error {
	err := wait.Poll(10*time.Second, 30*time.Second, func() (bool, error) {
		tmpState, err := cli.ProcessState(processName)
		if err != nil {
			log.Errorf("get %s state failed: %v, retry", processName, err)
			return false, nil
		}

		if tmpState != state {
			log.Errorf("get %s state: %s, expect state: %s, retry", processName,
				tmpState.String(), state.String())
			return false, nil
		}

		return true, nil
	})
	return errors.Trace(err)
}
