package combination_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 Change PD x Switch Suite")
}

var _ = Describe("TopSQL Change PD x Switch Test", func() {
	var deployErr error
	var ca *topsql.ClusterBundle
	var cb *topsql.ClusterBundle
	var n *topsql.NgmBundle

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

		sysbenchNode := suiteTestCtx.Resource("sysbench").(resource.WorkloadNode)
		caCfg := topsql.ClusterConfig{
			Version:              suiteTestCtx.Param("target_version"),
			Name:                 "topsql-combination-a",
			PDClientPort:         2379,
			PDPeerPort:           2380,
			TiDBPort:             4000,
			TiDBStatusPort:       10080,
			TiKVPort:             20160,
			TiKVStatusPort:       20180,
			PrometheusPort:       9090,
			NodeExporterPort:     9100,
			BlackboxExporterPort: 9115,
		}
		if ca, deployErr = topsql.NewClusterBundle(tiupNode, sysbenchNode, caCfg); deployErr != nil {
			return
		}
		if cb, deployErr = topsql.NewClusterBundle(tiupNode, sysbenchNode, topsql.ClusterConfig{
			Version:              suiteTestCtx.Param("target_version"),
			Name:                 "topsql-combination-b",
			PDClientPort:         2479,
			PDPeerPort:           2480,
			TiDBPort:             4100,
			TiDBStatusPort:       10180,
			TiKVPort:             20260,
			TiKVStatusPort:       20280,
			PrometheusPort:       9190,
			NodeExporterPort:     9200,
			BlackboxExporterPort: 9215,
		}); deployErr != nil {
			return
		}

		ngmNode := suiteTestCtx.Resource("ngm").(resource.WorkloadNode)
		n, deployErr = topsql.NewNgmBundle(ngmNode, tiupNode, 12020, caCfg.PDClientPort, caCfg.PrometheusPort, caCfg.Name)
	})

	AfterSuite(func() {
		if n != nil {
			n.Close()
			n = nil
		}
		if ca != nil {
			ca.Close()
			ca = nil
		}
		if cb != nil {
			cb.Close()
			cb = nil
		}
	})

	TestCase("TopSQL Change PD x Switch Test", "TEST_PD_CHANGE_X_SWITCH", func() {
		if deployErr != nil {
			Skip(fmt.Sprintf("tiup is unavailable: %s", deployErr.Error()))
		}

		By(`
1. On clusterA, turn on TopSQL, start ngm, run sysbench
	* Targets on clusterA begin to profile
	* Data are available for targets on clusterA`)
		// ====================================================
		// On clusterA, turn on TopSQL, start ngm, run sysbench
		// ====================================================
		Expect(ca.TurnOnTopSQL()).Should(Succeed())
		Expect(n.StartNGM()).Should(Succeed())
		Expect(topsql.TurnOffConprof(n.NgmURL())).Should(Succeed())
		Expect(ca.PrepareSysbench(4000)).Should(Succeed())
		Expect(ca.StartSysbench(4000)).Should(Succeed())

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

		By(`
2. On clusterB, turn on TopSQL, run sysbench, but no ngm
	* Targets on clusterB do not profile`)
		// =====================================================
		// On clusterB, turn on TopSQL, run sysbench, but no ngm
		// =====================================================
		Expect(cb.TurnOnTopSQL()).Should(Succeed())
		Expect(cb.PrepareSysbench(4100)).Should(Succeed())
		Expect(cb.StartSysbench(4100)).Should(Succeed())

		Consistently(func(g Gomega) {
			// ----------------------------------
			// Targets on clusterB do not profile
			// ----------------------------------
			g.Expect(cb.Cluster).Should(Not(metrics.TiDBProfilingCPU("tidb-peer:10180")))
			g.Expect(cb.Cluster).Should(Not(metrics.TiKVRsmeterStatingThreads("tikv-peer:20280")))
		}, "2m", "30s").Should(Succeed())

		By(`
3. On clusterB, turn off TopSQL`)
		// ============================
		// On clusterB, turn off TopSQL
		// ============================
		Expect(cb.TurnOffTopSQL()).Should(Succeed())

		By(`
4. Change PD address in Ngm config to clusterB
	* Targets on clusterA stop profiling
	* Targets on clusterB do not profile
	* Data are unavailable for targets on both clusterA and clusterB
	* Ngm does not scrape any target on both clusterA and clusterB`)
		// ===========================================
		// Change PD address in Ngm config to clusterB
		// ===========================================
		Expect(n.ChangePDEndpoint(2479)).Should(Succeed())

		Eventually(func(g Gomega) {
			// ----------------------------------
			// Targets on clusterA stop profiling
			// ----------------------------------
			g.Expect(ca.Cluster).Should(Not(metrics.TiDBProfilingCPU("tidb-peer:10080")))
			g.Expect(ca.Cluster).Should(Not(metrics.TiKVRsmeterStatingThreads("tikv-peer:20180")))

			// ----------------------------------
			// Targets on clusterB do not profile
			// ----------------------------------
			g.Expect(cb.Cluster).Should(Not(metrics.TiDBProfilingCPU("tidb-peer:10180")))
			g.Expect(cb.Cluster).Should(Not(metrics.TiKVRsmeterStatingThreads("tikv-peer:20280")))

			// --------------------------------------------------------------
			// Data are unavailable for targets on both clusterA and clusterB
			// --------------------------------------------------------------
			now := time.Now().Unix()
			bytes, err := topsql.FetchInstances(n.NgmURL(), 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:10080"})))
			g.Expect(topsql.QueryJSON(bytes, "data")).Should(Not(ContainElement(map[string]interface{}{"instance_type": "tidb", "instance": "tidb-peer:10180"})))
			g.Expect(topsql.QueryJSON(bytes, "data")).Should(Not(ContainElement(map[string]interface{}{"instance_type": "tikv", "instance": "tikv-peer:20160"})))
			g.Expect(topsql.QueryJSON(bytes, "data")).Should(Not(ContainElement(map[string]interface{}{"instance_type": "tikv", "instance": "tikv-peer:20260"})))

			bytes, err = topsql.FetchSummary(n.NgmURL(), now-120, now, "tidb-peer:10080", "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(n.NgmURL(), 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())
			bytes, err = topsql.FetchSummary(n.NgmURL(), now-120, now, "tikv-peer:20160", "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(n.NgmURL(), 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())

			// ------------------------------------------------------------
			// Ngm does not scrape any target on both clusterA and clusterB
			// ------------------------------------------------------------
			connections, err := n.Connections()
			g.Expect(err).ShouldNot(HaveOccurred())
			g.Expect(connections).Should(Not(ContainElement(HaveSuffix("10080"))))
			g.Expect(connections).Should(Not(ContainElement(HaveSuffix("10180"))))
			g.Expect(connections).Should(Not(ContainElement(HaveSuffix("20160"))))
			g.Expect(connections).Should(Not(ContainElement(HaveSuffix("20260"))))
		}, "5m", "10s").Should(Succeed())

		By(`
5. Turn on TopSQL on clusterB
	* Targets on clusterB begin to profile
	* Data are available for targets on clusterB`)
		// ==========================
		// Turn on TopSQL on clusterB
		// ==========================
		Expect(cb.TurnOnTopSQL()).Should(Succeed())

		Eventually(func(g Gomega) {
			// --------------------------------------------
			// * Targets on clusterB begin to profile
			// * Data are available for targets on clusterB
			// --------------------------------------------
			cb.ExpectTopSQLRunning(g, n.NgmURL(), []topsql.Target{
				{StatusAddr: "tidb-peer:10180", PubSubAddr: "tidb-peer:10180"},
				{StatusAddr: "tikv-peer:20280", PubSubAddr: "tikv-peer:20260"},
			})
		}, "5m", "10s").Should(Succeed())
	})
})
