package availzone

import (
	"context"
	"strings"
	"time"

	_ "github.com/go-sql-driver/mysql"
	. "github.com/onsi/gomega"
	"github.com/pingcap/endless/pkg/chaos"
	pdclient "github.com/pingcap/endless/pkg/client/pd"
	. "github.com/pingcap/endless/pkg/matcher"
	. "github.com/pingcap/endless/pkg/util"
	"github.com/pingcap/endless/testcase/oltp/oltppkg"
	"github.com/pingcap/endless/testcase/oltp/systemtest/pdrel"
	"github.com/pingcap/log"
	"github.com/pingcap/test-infra/sdk/resource"
	_ "github.com/pingcap/test-infra/sdk/resource/impl/k8s"
	"golang.org/x/sync/errgroup"
)

//network partiton
func OLTP_AZ_REL_NETWORK_PARTITION_001(
	ctx context.Context,
	tc resource.TiDBCluster,
	caseCmdParms oltppkg.CaseCmdParms,
	workloadRunParms oltppkg.WorkloadRunParms,
) {
	labels := Try(oltppkg.GetTikvLables(ctx, tc)).(string)
	if !(strings.Contains(labels, "zone1") && strings.Contains(labels, "zone2") && strings.Contains(labels, "zone3")) {
		log.Info("Skip this test, for no zone label in tikv")
		return
	}
	runTimePara := oltppkg.NewRunTimePara(60*time.Minute, 10*time.Minute, oltppkg.GetRandDuration(time.Minute, 10), 10*time.Minute)

	qpsJitterCheckParms := oltppkg.QpsJitterCheckParameters{
		IsCheck:      true,
		Offset:       "2m",
		ToleratePerc: 0.5,
		TolerateDur:  300 * time.Second,
	}
	durJitterCheckParms := oltppkg.DurJitterCheckParameters{
		IsCheck: false,
	}
	workloadCheckParms := oltppkg.WorkloadCheckParameters{
		IsCheck:   true,
		IgnoreErr: pdrel.GetPdIngoreErrors(caseCmdParms, "rel"),
	}
	restartCheckParms := oltppkg.RestartCheckParameters{
		AllComponentRestartCheck: false,
		TiKVRestartCheck:         true,
		PDRestartCheck:           false,
		TiDBRestartCheck:         true,
	}
	targetSelectOpts := chaos.SelectAllTiKVInOneAZ("zone2")
	c := chaos.Chaos{
		Name:               "zone1 zone2 network partition",
		Tc:                 tc,
		TargetTc:           tc,
		ExperimentSpec:     &resource.NetworkPartitionSpec{},
		SelectorOpts:       chaos.SelectAllTiKVInOneAZ("zone1"),
		TargetSelectorOpts: &targetSelectOpts,
	}

	ctxAll, cancelAll := context.WithTimeout(ctx, runTimePara.TotalRunTime)
	defer cancelAll()
	g, ctxg := errgroup.WithContext(ctxAll)

	startTime := time.Now().Add(runTimePara.WarmUpTime / 2)
	oltppkg.FunCommonCheck(ctxg, tc, g, runTimePara.TotalRunTime, workloadRunParms.Cmdpr, workloadCheckParms, qpsJitterCheckParms, durJitterCheckParms, restartCheckParms)
	//oltppkg.WaitFor(ctxAll, "waiting for warm up", runTimePara.WarmUpTime)

	g.Go(WithGinkgoRecover(func() {
		c.RunPeriodically(ctxg, "@every 11m", runTimePara.FaultDuration)
	}))

	Expect(g.Wait()).Should(BeNil())

	oltppkg.LogAndBy("Post qps check after recover")
	oltppkg.WaitFor(ctxAll, "waiting for recover", runTimePara.RecoveryTime)
	oltppkg.RepeatQpsDurCheckWithAbsTime(ctxAll, 5*time.Minute, startTime, tc, 0.9)
	log.Info("Test finished, check the store size to see if gc works fine.")
}

//down one az, wait region healthy
func OLTP_AZ_REL_DOWN_TIKV_001(
	ctx context.Context,
	tc resource.TiDBCluster,
	caseCmdParms oltppkg.CaseCmdParms,
	workloadRunParms oltppkg.WorkloadRunParms,
) {
	labels := Try(oltppkg.GetTikvLables(ctx, tc)).(string)
	if !(strings.Contains(labels, "zone1") && strings.Contains(labels, "zone2") && strings.Contains(labels, "zone3")) {
		log.Info("Skip this test, for no zone label in tikv")
		return
	}

	pd := Try(pdclient.NewPDClient(tc)).(*pdclient.Client)
	Try(oltppkg.ExecPdCtl(tc, "config set max-store-down-time 5m"))
	Try(oltppkg.ExecPdCtl(tc, "config set replica-schedule-limit 640"))
	Try(pd.SetAllStoreLimit(200))
	Try(oltppkg.ExecPdCtl(tc, "config show all"))

	defer func() {
		Try(oltppkg.ExecPdCtl(tc, "config set max-store-down-time 30m"))
		Try(oltppkg.ExecPdCtl(tc, "config set replica-schedule-limit 64"))
		Try(pd.SetAllStoreLimit(15))
	}()

	runTimePara := oltppkg.NewRunTimePara(90*time.Minute, 10*time.Minute, oltppkg.GetRandDuration(time.Minute, 10), 60*time.Minute)

	qpsJitterCheckParms := oltppkg.QpsJitterCheckParameters{
		IsCheck:      false,
		Offset:       "2m",
		ToleratePerc: 0.5,
		TolerateDur:  120 * time.Second,
	}
	durJitterCheckParms := oltppkg.DurJitterCheckParameters{
		IsCheck: false,
	}
	restartCheckParms := oltppkg.RestartCheckParameters{
		AllComponentRestartCheck: false,
		TiKVRestartCheck:         false,
		PDRestartCheck:           true,
		TiDBRestartCheck:         true,
	}

	workloadCheckParms := oltppkg.WorkloadCheckParameters{
		IsCheck:   false,
		IgnoreErr: pdrel.GetPdIngoreErrors(caseCmdParms, "rel"),
	}

	ctxAll, cancelAll := context.WithTimeout(ctx, runTimePara.TotalRunTime)
	defer cancelAll()
	g, ctxg := errgroup.WithContext(ctxAll)

	startTime := time.Now().Add(runTimePara.WarmUpTime / 2)
	oltppkg.FunCommonCheck(ctxg, tc, g, runTimePara.TotalRunTime, workloadRunParms.Cmdpr, workloadCheckParms, qpsJitterCheckParms, durJitterCheckParms, restartCheckParms)
	oltppkg.WaitFor(ctxAll, "waiting for warm up", runTimePara.WarmUpTime)

	g.Go(WithGinkgoRecover(func() {
		c := chaos.Chaos{
			Name:           "zone3 down",
			Tc:             tc,
			ExperimentSpec: &resource.FailureExperimentSpec{},
			SelectorOpts:   chaos.SelectAllTiKVInOneAZ("zone3"),
		}
		oltppkg.InjectFaultByChaos(&c, ctxg, runTimePara.TotalRunTime)
	}))

	oltppkg.WaitFor(ctxg, "waiting for fault inject", 10*time.Minute)
	WaitForRegionHealth(ctxg, tc, runTimePara.RecoveryTime, 3*time.Minute)

	cancelAll()
	Expect(g.Wait()).Should(BeNil())

	oltppkg.LogAndBy("Post qps check after recover")
	oltppkg.WaitFor(ctxAll, "waiting for recover", runTimePara.RecoveryTime)
	oltppkg.RepeatQpsDurCheckWithAbsTime(ctxAll, 5*time.Minute, startTime, tc, 0.9)

	oltppkg.LogAndBy("Waiting for cluster back to balance again.")
	oltppkg.WaitForBalance(ctx, tc, 60*time.Minute, 3*time.Minute, 0.3)
	log.Info("Test finished, check the store size to see if gc works fine.")
}

//network delay
func OLTP_AZ_REL_NETWORK_DELAY_001(
	ctx context.Context,
	tc resource.TiDBCluster,
	caseCmdParms oltppkg.CaseCmdParms,
	workloadRunParms oltppkg.WorkloadRunParms,
) {
	labels := Try(oltppkg.GetTikvLables(ctx, tc)).(string)
	if !(strings.Contains(labels, "zone1") && strings.Contains(labels, "zone2") && strings.Contains(labels, "zone3")) {
		log.Info("Skip this test, for no zone label in tikv")
		return
	}
	runTimePara := oltppkg.NewRunTimePara(60*time.Minute, 10*time.Minute, oltppkg.GetRandDuration(time.Minute, 10), 10*time.Minute)

	qpsJitterCheckParms := oltppkg.QpsJitterCheckParameters{
		IsCheck:      true,
		Offset:       "2m",
		ToleratePerc: 0.5,
		TolerateDur:  120 * time.Second,
	}
	durJitterCheckParms := oltppkg.DurJitterCheckParameters{
		IsCheck: false,
	}
	restartCheckParms := oltppkg.RestartCheckParameters{
		AllComponentRestartCheck: false,
		TiKVRestartCheck:         true,
		PDRestartCheck:           false,
		TiDBRestartCheck:         true,
	}
	opts := chaos.SelectAllTiKVInOneAZ("zone3")
	c := chaos.Chaos{
		Name:               "zone1 zone2 network delay",
		Tc:                 tc,
		TargetTc:           tc,
		ExperimentSpec:     &resource.NetworkLatencySpec{Latency: "100ms"},
		SelectorOpts:       chaos.SelectAllTiKVInOneAZ("zone2"),
		TargetSelectorOpts: &opts,
	}

	ctxAll, cancelAll := context.WithTimeout(ctx, runTimePara.TotalRunTime)
	defer cancelAll()
	g, ctxg := errgroup.WithContext(ctxAll)

	startTime := time.Now().Add(runTimePara.WarmUpTime / 2)
	oltppkg.FunCommonCheck(ctxg, tc, g, runTimePara.TotalRunTime, workloadRunParms.Cmdpr, qpsJitterCheckParms, durJitterCheckParms, restartCheckParms)

	g.Go(WithGinkgoRecover(func() {
		c.RunPeriodically(ctxg, "@every 11m", runTimePara.FaultDuration)
	}))

	Expect(g.Wait()).Should(BeNil())

	oltppkg.LogAndBy("Post qps check after recover")
	oltppkg.WaitFor(ctxAll, "waiting for recover", runTimePara.RecoveryTime)
	oltppkg.RepeatQpsDurCheckWithAbsTime(ctxAll, 5*time.Minute, startTime, tc, 0.9)
	log.Info("Test finished, check the store size to see if gc works fine.")
}

//network loss
func OLTP_AZ_REL_NETWORK_LOSS_001(
	ctx context.Context,
	tc resource.TiDBCluster,
	caseCmdParms oltppkg.CaseCmdParms,
	workloadRunParms oltppkg.WorkloadRunParms,
) {
	labels := Try(oltppkg.GetTikvLables(ctx, tc)).(string)
	if !(strings.Contains(labels, "zone1") && strings.Contains(labels, "zone2") && strings.Contains(labels, "zone3")) {
		log.Info("Skip this test, for no zone label in tikv")
		return
	}
	runTimePara := oltppkg.NewRunTimePara(60*time.Minute, 10*time.Minute, oltppkg.GetRandDuration(time.Minute, 10), 10*time.Minute)

	qpsJitterCheckParms := oltppkg.QpsJitterCheckParameters{
		IsCheck:      true,
		Offset:       "2m",
		ToleratePerc: 0.8,
		TolerateDur:  120 * time.Second,
	}
	durJitterCheckParms := oltppkg.DurJitterCheckParameters{
		IsCheck: false,
	}
	restartCheckParms := oltppkg.RestartCheckParameters{
		AllComponentRestartCheck: false,
		TiKVRestartCheck:         true,
		PDRestartCheck:           false,
		TiDBRestartCheck:         true,
	}
	opts := chaos.SelectAllTiKVInOneAZ("zone3")
	c := chaos.Chaos{
		Name:               "zone1 zone2 network loss",
		Tc:                 tc,
		TargetTc:           tc,
		ExperimentSpec:     &resource.NetworkLossSpec{Loss: "10", Correlation: "50"},
		SelectorOpts:       chaos.SelectAllTiKVInOneAZ("zone2"),
		TargetSelectorOpts: &opts,
	}

	ctxAll, cancelAll := context.WithTimeout(ctx, runTimePara.TotalRunTime)
	defer cancelAll()
	g, ctxg := errgroup.WithContext(ctxAll)

	startTime := time.Now().Add(runTimePara.WarmUpTime / 2)
	oltppkg.FunCommonCheck(ctxg, tc, g, runTimePara.TotalRunTime, workloadRunParms.Cmdpr, qpsJitterCheckParms, durJitterCheckParms, restartCheckParms)

	g.Go(WithGinkgoRecover(func() {
		c.RunPeriodically(ctxg, "@every 11m", runTimePara.FaultDuration)
	}))
	Expect(g.Wait()).Should(BeNil())

	oltppkg.LogAndBy("Post qps check after recover")
	oltppkg.WaitFor(ctxAll, "waiting for recover", runTimePara.RecoveryTime)
	oltppkg.RepeatQpsDurCheckWithAbsTime(ctxAll, 5*time.Minute, startTime, tc, 0.9)
	log.Info("Test finished, check the store size to see if gc works fine.")
}
