package scale_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/matcher/metrics"
	. "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 Scale Target Suite")
}

var _ = Describe("TopSQL Scale 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-scale-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 Scale Target Test", "TEST_SCALE_TARGET", func() {
		if deployErr != nil {
			Skip(fmt.Sprintf("tiup is unavailable: %s", deployErr.Error()))
		}
		Expect(topsql.TurnOffConprof(b.NgmURL(12020))).Should(Succeed())

		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())

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

		By(`
2. Scale-out TiDB:4001
	* TiDB:4001 begins to profile
	* Data are available for TiDB:4001`)
		// ===================
		// Scale-out TiDB:4001
		// ===================
		Expect(b.ScaleOut("tidb", 4001, 10081)).Should(Succeed())
		Expect(b.StartSysbench(4001)).Should(Succeed())

		Eventually(func(g Gomega) {
			// ---------------------------
			// * TiDB:4001 begins to profile
			// * Data are available for TiDB:4001
			// ---------------------------
			b.ExpectTopSQLRunning(g, b.NgmURL(12020), []topsql.Target{
				{StatusAddr: "tidb-peer:10081", PubSubAddr: "tidb-peer:10081"},
			})
		}, "5m", "10s").Should(Succeed())

		By(`
3. Scale-out TiKV:20161
	* TiKV:20161 begins to profile
	* Data are available for TiKV:20161`)
		// ====================
		// Scale-out TiKV:20161
		// ====================
		Expect(b.ScaleOut("tikv", 20161, 20181)).Should(Succeed())
		b.StopAllSysbench()
		Expect(b.DestroySysbench()).Should(Succeed())
		Expect(b.PrepareSysbench(4000)).Should(Succeed())
		Expect(b.StartSysbench(4000)).Should(Succeed())
		Expect(b.StartSysbench(4001)).Should(Succeed())

		Eventually(func(g Gomega) {
			// ----------------------------
			// * TiKV:20161 begins to profile
			// * Data are available for TiKV:20161
			// ----------------------------
			b.ExpectTopSQLRunning(g, b.NgmURL(12020), []topsql.Target{
				{StatusAddr: "tikv-peer:20181", PubSubAddr: "tikv-peer:20161"},
			})
		}, "5m", "10s").Should(Succeed())

		By(`
4. Scale-out TiDB:4002
	* TiDB:4002 begins to profile
	* Data are available for TiDB:4002`)
		// ===================
		// Scale-out TiDB:4002
		// ===================
		Expect(b.ScaleOut("tidb", 4002, 10082)).Should(Succeed())
		Expect(b.StartSysbench(4002)).Should(Succeed())

		Eventually(func(g Gomega) {
			// ---------------------------
			// * TiDB:4002 begins to profile
			// * Data are available for TiDB:4002
			// ---------------------------
			b.ExpectTopSQLRunning(g, b.NgmURL(12020), []topsql.Target{
				{StatusAddr: "tidb-peer:10082", PubSubAddr: "tidb-peer:10082"},
			})
		}, "5m", "10s").Should(Succeed())

		By(`
5. Scale-out TiKV:20162
	* TiKV:20162 begins to profile
	* Data are available for TiKV:20162`)
		// ====================
		// Scale-out TiKV:20162
		// ====================
		Expect(b.ScaleOut("tikv", 20162, 20182)).Should(Succeed())
		b.StopAllSysbench()
		Expect(b.DestroySysbench()).Should(Succeed())
		Expect(b.PrepareSysbench(4000)).Should(Succeed())
		Expect(b.StartSysbench(4000)).Should(Succeed())
		Expect(b.StartSysbench(4001)).Should(Succeed())
		Expect(b.StartSysbench(4002)).Should(Succeed())

		Eventually(func(g Gomega) {
			// ----------------------------
			// * TiKV:20162 begins to profile
			// * Data are available for TiKV:20162
			// ----------------------------
			b.ExpectTopSQLRunning(g, b.NgmURL(12020), []topsql.Target{
				{StatusAddr: "tikv-peer:20182", PubSubAddr: "tikv-peer:20162"},
			})
		}, "5m", "10s").Should(Succeed())

		By(`
6. Scale-in TiDB:4001 and TiDB:4002
	* TiDB:4001 and TiDB:4002 stop profiling
	* Ngm stops scraping TiDB:4001 and TiDB:4002
	* Data are unavailable for TiDB:4001 and TiDB:4002`)
		// ================================
		// Scale-in TiDB:4001 and TiDB:4002
		// ================================
		b.StopSysbench(4001)
		b.StopSysbench(4002)
		Expect(b.ScaleIn("tidb", 4001)).Should(Succeed())
		Expect(b.ScaleIn("tidb", 4002)).Should(Succeed())

		Eventually(func(g Gomega) {
			// --------------------------------------
			// TiDB:4001 and TiDB:4002 stop profiling
			// --------------------------------------
			g.Expect(b.Cluster).Should(Not(metrics.TiDBProfilingCPU("tidb-peer:10081")))
			g.Expect(b.Cluster).Should(Not(metrics.TiDBProfilingCPU("tidb-peer:10082")))

			// ------------------------------------------
			// Ngm stops scraping TiDB:4001 and TiDB:4002
			// ------------------------------------------
			connections, err := b.NgmConnections()
			g.Expect(err).ShouldNot(HaveOccurred())
			g.Expect(connections).Should(ContainElement(HaveSuffix("10080")))
			g.Expect(connections).Should(Not(ContainElement(HaveSuffix("10081"))))
			g.Expect(connections).Should(Not(ContainElement(HaveSuffix("10082"))))

			// ----------------------------------------------
			// Data are unavailable for TiDB:4001 and TiDB:4002
			// ----------------------------------------------
			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(Or(ContainElement(
				map[string]interface{}{"instance_type": "tidb", "instance": "tidb-peer:10081"},
			), ContainElement(
				map[string]interface{}{"instance_type": "tidb", "instance": "tidb-peer:10082"},
			))))

			bytes, err = topsql.FetchSummary(b.NgmURL(12020), now-120, now, "tidb-peer:10081", "tidb")
			g.Expect(err).ShouldNot(HaveOccurred())
			g.Expect(topsql.QueryJSON(bytes, "status")).Should(Equal("ok"))
			g.Expect(topsql.QueryJSON(bytes, "data")).Should(BeEmpty())

			bytes, err = topsql.FetchSummary(b.NgmURL(12020), now-120, now, "tidb-peer:10082", "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())

		By(`
7. Scale-in TiKV:20161 and TiKV:20162
	* TiKV:20161 and TiKV:20162 stop profiling
	* Ngm stops scraping TiKV:20161 and TiKV:20162
	* Data are unavailable for TiKV:20161 and TiKV:20162`)
		// ==================================
		// Scale-in TiKV:20161 and TiKV:20162
		// ==================================
		Expect(b.ScaleIn("tikv", 20161)).Should(Succeed())
		Expect(b.ScaleIn("tikv", 20162)).Should(Succeed())

		Eventually(func(g Gomega) {
			// ----------------------------------------
			// TiKV:20161 and TiKV:20162 stop profiling
			// ----------------------------------------
			g.Expect(b.Cluster).Should(Not(metrics.TiKVRsmeterStatingThreads("tikv-peer:20181")))
			g.Expect(b.Cluster).Should(Not(metrics.TiKVRsmeterStatingThreads("tikv-peer:20182")))

			// --------------------------------------------
			// Ngm stops scraping TiKV:20161 and TiKV:20162
			// --------------------------------------------
			connections, err := b.NgmConnections()
			g.Expect(err).ShouldNot(HaveOccurred())
			g.Expect(connections).Should(ContainElement(HaveSuffix("20160")))
			g.Expect(connections).Should(Not(ContainElement(HaveSuffix("20161"))))
			g.Expect(connections).Should(Not(ContainElement(HaveSuffix("20162"))))

			// ------------------------------------------------
			// Data are unavailable for TiKV:20161 and TiKV:20162
			// ------------------------------------------------
			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(Or(ContainElement(
				map[string]interface{}{"instance_type": "tikv", "instance": "tidb-peer:20161"},
			), ContainElement(
				map[string]interface{}{"instance_type": "tikv", "instance": "tidb-peer:20162"},
			))))

			bytes, err = topsql.FetchSummary(b.NgmURL(12020), now-120, now, "tikv-peer:20161", "tikv")
			g.Expect(err).ShouldNot(HaveOccurred())
			g.Expect(topsql.QueryJSON(bytes, "status")).Should(Equal("ok"))
			g.Expect(topsql.QueryJSON(bytes, "data")).Should(BeEmpty())

			bytes, err = topsql.FetchSummary(b.NgmURL(12020), now-120, now, "tikv-peer:20162", "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())
	})
})
