package restart_target_test

import (
	"fmt"
	"testing"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/pingcap/test-infra/sdk/core"
	"github.com/pingcap/test-infra/sdk/resource"

	. "github.com/pingcap/endless/pkg/util"
	"github.com/pingcap/endless/testcase/topsql"

	_ "github.com/pingcap/test-infra/sdk/resource/impl/k8s"
)

var (
	suiteTestCtx core.TestContext
)

func init() {
	suiteTestCtx = AutoConf()
}

func TestTopSQL(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "TopSQL Restart Target Suite")
}

var _ = Describe("TopSQL Restart Target Test", func() {
	var deployErr error
	var b *topsql.ClusterBundle

	BeforeSuite(func() {
		tiupNode := suiteTestCtx.Resource("tiup").(resource.WorkloadNode)
		if deployErr = topsql.CheckTiUPAvailable(tiupNode); deployErr != nil {
			return
		}

		sysbenchNode := suiteTestCtx.Resource("sysbench").(resource.WorkloadNode)
		b, deployErr = topsql.NewClusterBundle(tiupNode, sysbenchNode, topsql.ClusterConfig{
			Version:        suiteTestCtx.Param("target_version"),
			Name:           "topsql-restart-target",
			PDClientPort:   2379,
			PDPeerPort:     2380,
			TiDBPort:       4000,
			TiDBStatusPort: 10080,
			TiKVPort:       20160,
			TiKVStatusPort: 20180,
			PrometheusPort: 9090,
			NgmPort:        12020,
		})
	})

	AfterSuite(func() {
		if b != nil {
			b.Close()
			b = nil
		}
	})

	TestCase("TopSQL Restart Target Test", "TEST_RESTART_TARGET", func() {
		if deployErr != nil {
			Skip(fmt.Sprintf("tiup is unavailable: %s", deployErr.Error()))
		}
		Expect(topsql.TurnOffConprof(b.NgmURL(12020))).Should(Succeed())

		Expect(b.ScaleOut("tidb", 4100, 10180))
		Expect(b.ScaleOut("tikv", 20260, 20280))

		By(`
1. Turn on TopSQL, start ngm, run sysbench
	* Targets begin to profile
	* Data are available`)
		// =======================================
		// Turn on TopSQL, start ngm, run sysbench
		// =======================================
		Expect(b.TurnOnTopSQL()).Should(Succeed())
		Expect(b.PrepareSysbench(4000)).Should(Succeed())
		Expect(b.StartSysbench(4000)).Should(Succeed())
		Expect(b.StartSysbench(4100)).Should(Succeed())

		Eventually(func(g Gomega) {
			// ---------------------
			// * Targets are profiling
			// * Data are available
			// ---------------------
			b.ExpectTopSQLRunning(g, b.NgmURL(12020), []topsql.Target{
				{StatusAddr: "tidb-peer:10080", PubSubAddr: "tidb-peer:10080"},
				{StatusAddr: "tidb-peer:10180", PubSubAddr: "tidb-peer:10180"},
				{StatusAddr: "tikv-peer:20180", PubSubAddr: "tikv-peer:20160"},
				{StatusAddr: "tikv-peer:20280", PubSubAddr: "tikv-peer:20260"},
			})
		}, "5m", "10s").Should(Succeed())

		By(`
2. Stop TiDB:4100, restart after a while
	* Data are available for all targets except TiDB:4100 during downtime
	* Data are available for all targets after TiDB:4100 restarted`)
		// ==============
		// Stop TiDB:4100
		// ==============
		b.StopSysbench(4100)
		Expect(b.StopComponent("tidb", 4100)).Should(Succeed())

		Eventually(func(g Gomega) {
			// -------------------------------------------------------------------
			// Data are available for all targets except TiDB:4100 during downtime
			// -------------------------------------------------------------------
			b.ExpectTopSQLRunning(g, b.NgmURL(12020), []topsql.Target{
				{StatusAddr: "tidb-peer:10080", PubSubAddr: "tidb-peer:10080"},
				{StatusAddr: "tikv-peer:20180", PubSubAddr: "tikv-peer:20160"},
				{StatusAddr: "tikv-peer:20280", PubSubAddr: "tikv-peer:20260"},
			})

			now := time.Now().Unix()
			bytes, err := topsql.FetchInstances(b.NgmURL(12020), now-120, now)
			g.Expect(err).ShouldNot(HaveOccurred())
			g.Expect(topsql.QueryJSON(bytes, "status")).Should(Equal("ok"))
			g.Expect(topsql.QueryJSON(bytes, "data")).Should(Not(ContainElement(
				map[string]interface{}{"instance_type": "tidb", "instance": "tidb-peer:10180"},
			)))

			bytes, err = topsql.FetchSummary(b.NgmURL(12020), now-120, now, "tidb-peer:10180", "tidb")
			g.Expect(err).ShouldNot(HaveOccurred())
			g.Expect(topsql.QueryJSON(bytes, "status")).Should(Equal("ok"))
			g.Expect(topsql.QueryJSON(bytes, "data")).Should(BeEmpty())
		}, "5m", "10s").Should(Succeed())

		// =====================
		// restart after a while
		// =====================
		Expect(b.RestartComponent("tidb", 4100)).Should(Succeed())
		Expect(b.StartSysbench(4100)).Should(Succeed())

		// ------------------------------------------------------------
		// Data are available for all targets after TiDB:4100 restarted
		// ------------------------------------------------------------
		Eventually(func(g Gomega) {
			// ---------------------
			// * Targets are profiling
			// * Data are available
			// ---------------------
			b.ExpectTopSQLRunning(g, b.NgmURL(12020), []topsql.Target{
				{StatusAddr: "tidb-peer:10080", PubSubAddr: "tidb-peer:10080"},
				{StatusAddr: "tidb-peer:10180", PubSubAddr: "tidb-peer:10180"},
				{StatusAddr: "tikv-peer:20180", PubSubAddr: "tikv-peer:20160"},
				{StatusAddr: "tikv-peer:20280", PubSubAddr: "tikv-peer:20260"},
			})
		}, "5m", "10s").Should(Succeed())

		By(`
3. Stop TiKV:20260, restart after a while
	* Data are available for all targets except TiKV:20260 during downtime
	* Data are available for all targets after TiKV:20260 restarted`)
		// ======================================
		// Stop TiKV:20260, restart after a while
		// ======================================
		Expect(b.StopComponent("tikv", 20260)).Should(Succeed())

		Eventually(func(g Gomega) {
			// --------------------------------------------------------------------
			// Data are available for all targets except TiKV:20260 during downtime
			// --------------------------------------------------------------------
			b.ExpectTopSQLRunning(g, b.NgmURL(12020), []topsql.Target{
				{StatusAddr: "tidb-peer:10080", PubSubAddr: "tidb-peer:10080"},
				{StatusAddr: "tidb-peer:10180", PubSubAddr: "tidb-peer:10180"},
				{StatusAddr: "tikv-peer:20180", PubSubAddr: "tikv-peer:20160"},
			})

			now := time.Now().Unix()
			bytes, err := topsql.FetchInstances(b.NgmURL(12020), now-120, now)
			g.Expect(err).ShouldNot(HaveOccurred())
			g.Expect(topsql.QueryJSON(bytes, "status")).Should(Equal("ok"))
			g.Expect(topsql.QueryJSON(bytes, "data")).Should(Not(ContainElement(
				map[string]interface{}{"instance_type": "tikv", "instance": "tikv-peer:20260"},
			)))

			bytes, err = topsql.FetchSummary(b.NgmURL(12020), now-120, now, "tikv-peer:20260", "tikv")
			g.Expect(err).ShouldNot(HaveOccurred())
			g.Expect(topsql.QueryJSON(bytes, "status")).Should(Equal("ok"))
			g.Expect(topsql.QueryJSON(bytes, "data")).Should(BeEmpty())
		}, "5m", "10s").Should(Succeed())

		// =====================
		// Restart after a while
		// =====================
		Expect(b.RestartComponent("tikv", 20260)).Should(Succeed())
		b.StopAllSysbench()
		Expect(b.DestroySysbench()).Should(Succeed())
		Expect(b.PrepareSysbench(4000)).Should(Succeed())
		Expect(b.StartSysbench(4000))
		Expect(b.StartSysbench(4100))

		// -------------------------------------------------------------
		// Data are available for all targets after TiKV:20260 restarted
		// -------------------------------------------------------------
		Eventually(func(g Gomega) {
			// ---------------------
			// * Targets are profiling
			// * Data are available
			// ---------------------
			b.ExpectTopSQLRunning(g, b.NgmURL(12020), []topsql.Target{
				{StatusAddr: "tidb-peer:10080", PubSubAddr: "tidb-peer:10080"},
				{StatusAddr: "tidb-peer:10180", PubSubAddr: "tidb-peer:10180"},
				{StatusAddr: "tikv-peer:20180", PubSubAddr: "tikv-peer:20160"},
				{StatusAddr: "tikv-peer:20280", PubSubAddr: "tikv-peer:20260"},
			})
		}, "5m", "10s").Should(Succeed())
	})
})
