package client

import (
	"context"
	"strings"
	"time"

	"github.com/juju/errors"
)

// KillNode kills node
func (c *Client) KillNode(ctx context.Context, node *Node) error {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return nil
	}

	if err := c.refreshConfig(); err != nil {
		Fatalf("init client config error %v", errors.ErrorStack(err))
	}

	if node == nil {
		return NewErrorf("node is nil")
	}

	Infof("start to kill node %s", node.Name())

	if err := node.stop(ctx); err != nil {
		return NewErrorf("stop node %s failed: %v", node.Name(), err)
	}

	Infof("kill node %s end", node.Name())
	return nil
}

// StartNode starts node
func (c *Client) StartNode(ctx context.Context, node *Node) error {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return nil
	}

	if err := c.refreshConfig(); err != nil {
		Fatalf("init client config error %v", errors.ErrorStack(err))
	}

	if node == nil {
		return NewErrorf("node is nil")
	}

	Infof("start node %s", node.Name())

	if err := node.start(ctx); err != nil && !strings.Contains(err.Error(), "process from state [RUNNING]") {
		return NewErrorf("start node failed: %v", err)
	}

	Infof("start node %s end", node.Name())
	return nil
}

// RecoverNode recovers node from unhealth state
func (c *Client) RecoverNode(ctx context.Context, node *Node) error {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return nil
	}

	if err := c.refreshConfig(); err != nil {
		Fatalf("init client config error %v", errors.ErrorStack(err))
	}

	c.KillNode(ctx, node)
	return errors.Trace(c.StartNode(ctx, node))
}

// PauseNode pauses a node
func (c *Client) PauseNode(ctx context.Context, node *Node) error {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return nil
	}

	if err := c.refreshConfig(); err != nil {
		Fatalf("init client config error %v", errors.ErrorStack(err))
	}

	if node == nil {
		return NewErrorf("node is nil")
	}

	Infof("start to pause node %s", node.Name())

	if err := node.pauseProcess(ctx); err != nil {
		return NewErrorf("pause node %s failed: %v", node.Name(), err)
	}

	Infof("pause node %s end", node.Name())
	return nil
}

// ContinueNode continues a paused node
func (c *Client) ContinueNode(ctx context.Context, node *Node) error {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return nil
	}

	if err := c.refreshConfig(); err != nil {
		Fatalf("init client config error %v", errors.ErrorStack(err))
	}

	if node == nil {
		return NewErrorf("node is nil")
	}

	Infof("start to continue node %s", node.Name())

	if err := node.continueProcess(ctx); err != nil {
		return NewErrorf("continue node %s failed: %v", node.Name(), err)
	}

	Infof("continue node %s end", node.Name())
	return nil
}

// KillWithRecover kills service according to the specified kind
// and restarts this service after 'waitTime',
// Node is the unit of nemesis execution and contains the kind and host of the service
func (c *Client) KillWithRecover(ctx context.Context, kind string, waitTime time.Duration) (*Node, error) {
	return c.killWithRecover(ctx, kind, waitTime, false)
}

// KillWithRecoverLeaderBiased kills service(bias to leader node for TiDB and PD) according to the specified kind
// and restarts this service after 'waitTime',
// Node is the unit of nemesis execution and contains the kind and host of the service
func (c *Client) KillWithRecoverLeaderBiased(ctx context.Context, kind string, waitTime time.Duration) (*Node, error) {
	return c.killWithRecover(ctx, kind, waitTime, true)
}

func (c *Client) killWithRecover(ctx context.Context, kind string, waitTime time.Duration, leaderBiased bool) (*Node, error) {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return nil, nil
	}

	if err := c.refreshConfig(); err != nil {
		Fatalf("init client config error %v", errors.ErrorStack(err))
	}

	Info("[kill_with_recover] start")

	node := c.nemesis.selectNodeByKind(kind, leaderBiased)
	if node == nil {
		return nil, NewErrorf("[kill_with_recover] service %s not found", kind)
	}

	if err := c.KillNode(ctx, node); err != nil {
		return nil, errors.Trace(err)
	}

	// wait
	sleep(ctx, waitTime)

	// recover
	if err := c.StartNode(ctx, node); err != nil {
		return nil, errors.Trace(err)
	}

	Infof("[kill_with_recover] end")
	return node, nil
}

// PauseWithRecover pauses service according to the specified kind
// and continues this service after 'waitTime',
// Node is the unit of nemesis execution and contains the kind and host of the service
func (c *Client) PauseWithRecover(ctx context.Context, kind string, waitTime time.Duration) (*Node, error) {
	return c.pauseWithRecover(ctx, kind, waitTime, false)
}

// PauseWithRecoverLeaderBiased pauses service(bias to leader node for TiDB and PD) according to the specified kind
// and continues this service after 'waitTime',
// Node is the unit of nemesis execution and contains the kind and host of the service
func (c *Client) PauseWithRecoverLeaderBiased(ctx context.Context, kind string, waitTime time.Duration) (*Node, error) {
	return c.pauseWithRecover(ctx, kind, waitTime, true)
}

func (c *Client) pauseWithRecover(ctx context.Context, kind string, waitTime time.Duration, leaderBiased bool) (*Node, error) {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return nil, nil
	}

	if err := c.refreshConfig(); err != nil {
		Fatalf("init client config error %v", errors.ErrorStack(err))
	}

	Info("[pause_with_recover] start")

	node := c.nemesis.selectNodeByKind(kind, leaderBiased)
	if node == nil {
		return nil, NewErrorf("[pause_with_recover] service %s not found", kind)
	}

	if err := c.PauseNode(ctx, node); err != nil {
		return nil, errors.Trace(err)
	}

	// wait
	sleep(ctx, waitTime)

	// continue
	if err := c.ContinueNode(ctx, node); err != nil {
		return nil, errors.Trace(err)
	}

	Infof("[pause_with_recover] end")
	return node, nil
}
