package random_failpoint_test

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/pingcap/log"
	"github.com/pingcap/test-infra/sdk/resource"
	"go.uber.org/zap"
	"golang.org/x/sync/errgroup"

	"github.com/pingcap/endless/pkg/client/tidb"
	"github.com/pingcap/endless/pkg/matcher"
	"github.com/pingcap/endless/pkg/matcher/metrics"
	"github.com/pingcap/endless/pkg/util"
	"github.com/pingcap/endless/testcase/tiflash/pkg"
	"github.com/pingcap/endless/testcase/tiflash/pkg/checker"
	workload2 "github.com/pingcap/endless/testcase/tiflash/pkg/workload"
	"github.com/pingcap/endless/testcase/tiflash/pkg/workload/workloads"
)

var _ = Describe("TiFlash", func() {
	var (
		tc resource.TiDBCluster
		wl workload2.Workload
	)
	BeforeEach(func() {
		tc = suiteTestCtx.Resource("tc").(resource.TiDBCluster)
		wlType := workloads.WorkloadType(suiteTestCtx.Param("workload", string(workloads.TpchWorkloadType)))
		if w, ok := workloads.HtapWorkloads[wlType]; ok {
			wl = w(suiteTestCtx)
		} else {
			Skip("don't have the workload " + string(wlType))
		}
		tc = suiteTestCtx.Resource("tc").(resource.TiDBCluster)
		matcher.Try(pkg.DisableExpressionPushdown(tc, []string{"date_add"}, []string{"tiflash"}))
		wl.Prepare(suiteTestCtx)
		// the database is created after prepare
		db := matcher.Try(tc.GetDB(wl.DBName())).(*sql.DB)
		tidbClient := tidb.NewClient(db)
		defer tidbClient.Close()
		matcher.Try(tidbClient.ShowClusterVersionInformation())
	})
	// Run TPCH queries parallelly, and edit TiFlash config to enable different random failpoints. After queries finished, check no crash and no leaked threads
	Context("when running tpch queries with random failpoints enabled", func() {
		It("should not crash #RandomFailPoint#", func() {
			tc = suiteTestCtx.Resource("tc").(resource.TiDBCluster)
			runTime := matcher.Try(time.ParseDuration(suiteTestCtx.Param("runtime", "30m"))).(time.Duration)
			intervalTime := matcher.Try(time.ParseDuration(suiteTestCtx.Param("interval", "6m"))).(time.Duration) // Dynamic thread retired after being idle more than 5 minutes
			for _, randomFailPointConfig := range getRandomFailPointConfigs() {
				err := tc.UpdateConfig(resource.TiDBClusterUpdateConfigOptions{Target: resource.TiFlash, Config: fmt.Sprintf("[flash]\nrandom_fail_points=%s", randomFailPointConfig), Timeout: 0})
				Expect(err).ShouldNot(HaveOccurred())
				log.Info(fmt.Sprintf("RandomCfg: [flash]\nrandom_fail_points=%s\n", randomFailPointConfig))
				ctx, cancel := context.WithCancel(suiteTestCtx)
				g, _ := errgroup.WithContext(ctx)
				g.Go(util.WithGinkgoRecover(func() {
					checker.TiFlashCrashChecker(tc, runTime+intervalTime)
				}))
				g.Go(util.WithGinkgoRecover(func() {
					defer cancel()
					log.Info("RandomFailPoint query start")
					ctx1, _ := context.WithTimeout(context.Background(), runTime)
					// olap_threads param
					wl.SetApThreads(15)
					matcher.Try(wl.RunAP(ctx1, pkg.SkipErrorHandler))
					log.Info("RandomFailPoint query end and wait some time", zap.Duration("wait time", intervalTime))
					time.Sleep(intervalTime)
					log.Info("RandomFailPoint query stop")
					Expect(tc).Should(metrics.TiFlashActiveThreadPoolNoLeak().WithCtx(suiteTestCtx))
				}))
				matcher.Try(g.Wait())
			}
		})
	})
})

func getRandomFailPointConfigs() []string {
	return []string{
		"\"random_tunnel_wait_timeout_failpoint-0.001\"",
		"\"random_tunnel_init_rpc_failure_failpoint-0.001\"",
		"\"random_receiver_sync_msg_push_failure_failpoint-0.0001\"",
		"\"random_receiver_async_msg_push_failure_failpoint-0.0001\"",
		"\"random_limit_check_failpoint-0.0001\"",
		"\"random_join_build_failpoint-0.001\"",
		"\"random_join_prob_failpoint-0.001\"",
		// create state failpoint will cause crash bug now, temporarily comment it out
		//"\"random_aggregate_create_state_failpoint-0.001\"",
		"\"random_aggregate_merge_failpoint-0.001\"",
		"\"random_sharedquery_failpoint-0.001\"",
		"\"random_interpreter_failpoint-1.0\"",
		"\"random_task_lifecycle_failpoint-0.001,random_receiver_async_msg_push_failure_failpoint-0.0001\"",
		"\"random_task_manager_find_task_failure_failpoint-0.001\"",
		"\"random_min_tso_scheduler_failpoint-0.001\""}
}
