package configupdate

import (
	"context"
	"errors"
	"fmt"
	"math/rand"
	"strings"
	"time"

	. "github.com/onsi/gomega"
	"github.com/pingcap/endless/pkg/chaos"
	. "github.com/pingcap/endless/pkg/matcher"
	. "github.com/pingcap/endless/pkg/util"
	"github.com/pingcap/endless/testcase/oltp/oltppkg"
	"github.com/pingcap/log"
	"github.com/pingcap/test-infra/sdk/resource"
	_ "github.com/pingcap/test-infra/sdk/resource/impl/k8s"
	"go.uber.org/zap"
	"golang.org/x/sync/errgroup"
)

func OLTP_DYNAMIC_REGIONS_UPDATE_CONFIG_REPEATEDLY_STABILITY_001(
	ctx context.Context,
	tc resource.TiDBCluster,
	benchToolset resource.WorkloadNode,
	workloadRunParms oltppkg.WorkloadRunParms,
	caseCmdParms oltppkg.CaseCmdParms,
) {
	tikvVer := oltppkg.GetCompVersion(ctx, tc, "tikv")
	curVerShort := strings.TrimSuffix(strings.TrimPrefix(tikvVer, "v"), "-pre")
	if oltppkg.CompareVersion(curVerShort, "6.1.0") < 0 {
		log.Info(fmt.Sprintf("tikv version %s do not support this feature.", curVerShort))
		return
	}
	getCurrentRegionConfigsValue(tc)
	workloadRunParms.InitAndStart(ctx, tc, benchToolset, caseCmdParms, false)
	var runTime time.Duration
	var runTimePerConfig time.Duration
	// default config: max-merge-region-size:20 / region-max-size:144, suggesting the proportion is 10% ~ 20%
	configNum := len(regionSplitSizes)
	runTimePerConfig = 1 * time.Hour
	if caseCmdParms.RunTime.TotalRunTime != 0 {
		runTime = caseCmdParms.RunTime.TotalRunTime
	} else {
		runTime = time.Duration(int(runTimePerConfig) * (configNum + 1))
	}

	log.Info("Test will run ", zap.Duration("TotalRunTime", runTime), zap.Duration("runTimePerConfig", runTimePerConfig))
	ctxAll, cancel := context.WithTimeout(ctx, runTime)
	defer cancel()
	g, gctx := errgroup.WithContext(ctxAll)

	qpsJitterCheckParams := oltppkg.QpsJitterCheckParameters{
		IsCheck:      true,
		Offset:       "2m",
		ToleratePerc: 0.5,
		TolerateDur:  60 * time.Second,
	}
	// there is a bug when update config online: https://github.com/tikv/tikv/issues/12768
	rollingUpdateRegionConfigRepeatedly(gctx, tc, g, regionSplitSizes, runTime, runTimePerConfig, workloadRunParms.Cmdpr, qpsJitterCheckParams)
	Expect(g.Wait()).Should(BeNil())
	regionsCheckAfterCaseEnd(ctx, tc, workloadRunParms.WorkloadNode)
}

func OLTP_DYNAMIC_REGIONS_UPDATE_CONFIG_REPEATEDLY_STABILITY_REL_001(
	ctx context.Context,
	tc resource.TiDBCluster,
	benchToolset resource.WorkloadNode,
	workloadRunParms oltppkg.WorkloadRunParms,
	caseCmdParms oltppkg.CaseCmdParms,
	skipAfterEachCheck chan bool,
) {
	tikvVer := oltppkg.GetCompVersion(ctx, tc, "tikv")
	curVerShort := strings.TrimSuffix(strings.TrimPrefix(tikvVer, "v"), "-pre")
	if oltppkg.CompareVersion(curVerShort, "6.1.0") < 0 {
		log.Info(fmt.Sprintf("tikv version %s do not support this feature.", curVerShort))
		return
	}
	workloadRunParms.InitAndStart(ctx, tc, benchToolset, caseCmdParms, false)
	var runTime time.Duration
	var runTimePerConfig time.Duration
	configNum := len(regionSplitSizes)
	runTimePerConfig = 2 * time.Hour
	if caseCmdParms.RunTime.TotalRunTime != 0 {
		runTime = caseCmdParms.RunTime.TotalRunTime
	} else {
		runTime = time.Duration(int(runTimePerConfig) * (configNum + 1))
	}
	log.Info("Test will run ", zap.Duration("TotalRunTime", runTime), zap.Duration("runTimePerConfig", runTimePerConfig))

	skipAfterEachCheck <- true
	faultDuration := runTime
	instanceType := resource.TiKV
	warmUpTime := 10 * time.Minute
	instanceIdList := oltppkg.GetInstanceIdList(tc, instanceType)
	instanceId, _ := oltppkg.GetRandInstance(tc, instanceType)
	tikvFaultTpyes := []string{"network-loss"}
	rand.Seed(time.Now().UnixNano())
	faultTpye := tikvFaultTpyes[rand.Intn(len(tikvFaultTpyes))]
	var c chaos.Chaos
	switch faultTpye {
	case "network-loss":
		c.ExperimentSpec = &resource.NetworkLossSpec{
			Loss:        oltppkg.GetRandIntStr(50, 100),
			Correlation: "25",
			Duration:    oltppkg.GetRandIntStr(5, 10) + "m",
			Scheduler:   &resource.ExperimentScheduler{Cron: "@every 30m"},
		}
	case "network-delay":
		c.ExperimentSpec = &resource.NetworkLatencySpec{
			Latency:     oltppkg.GetRandIntStr(100, 500) + "ms",
			Duration:    oltppkg.GetRandIntStr(5, 10) + "m",
			Correlation: "25",
			Scheduler:   &resource.ExperimentScheduler{Cron: "@every 30m"},
		}
	case "IODelay":
		c.ExperimentSpec = &resource.IODelaySpec{
			VolumePath: "/var/lib/tikv",
			Path:       "/var/lib/tikv/data/**/*",
			Delay:      oltppkg.GetRandIntStr(100, 500) + "ms", //base delay: 40ms
			Percent:    100,
			Duration:   oltppkg.GetRandIntStr(5, 10) + "m",
			Scheduler:  &resource.ExperimentScheduler{Cron: "@every 30m"},
		}
	default:
		err := errors.New("invalid faultTpye")
		Expect(err).Should(Succeed())
	}
	c = Try(oltppkg.InitChaos(tc, instanceType, faultTpye, c.ExperimentSpec, &instanceIdList[instanceId])).(chaos.Chaos)

	ctxAll, cancel := context.WithTimeout(ctx, runTime)
	defer cancel()
	g, gctx := errgroup.WithContext(ctxAll)

	qpsJitterCheckParams := oltppkg.QpsJitterCheckParameters{
		IsCheck:      true,
		Offset:       "2m",
		ToleratePerc: 0.5,
		TolerateDur:  180 * time.Second,
	}
	g.Go(WithGinkgoRecover(func() {
		oltppkg.WaitFor(gctx, "Warm Up before inject fault", warmUpTime)
		err := oltppkg.InjectFaultByChaos(&c, gctx, faultDuration)
		Expect(err).Should(Succeed())
	}))
	updateRegionConfigOnlineRepeatedly(gctx, tc, g, regionSplitSizes, runTime, runTimePerConfig, workloadRunParms.Cmdpr, qpsJitterCheckParams)
	Expect(g.Wait()).Should(BeNil())
	regionsCheckAfterCaseEnd(ctx, tc, workloadRunParms.WorkloadNode)
}
