package o11y

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"strings"
	"testing"

	"github.com/jarcoal/httpmock"
	"github.com/onsi/gomega"
)

func TestNew(t *testing.T) {
	g := gomega.NewWithT(t)
	{
		cli, err := New(
			"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
			"www.rs.%s.%s.observability-staging.pingcap.cloud",
			"foo",
			"bar",
			"dev")
		g.Expect(err).To(gomega.Succeed())
		g.Expect(cli.globalServiceEndpoint).To(gomega.HavePrefix("http://"))
	}
	{
		cli, err := New(
			"https://www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
			"www.rs.%s.%s.observability-staging.pingcap.cloud",
			"foo",
			"bar",
			"dev",
			ByocDSEndpointOption(" www.ds.byoc.example.com "))
		g.Expect(err).To(gomega.Succeed())
		g.Expect(cli.globalServiceEndpoint).To(gomega.HavePrefix("https://"))
		g.Expect(cli.GetByocDSEndpoint()).To(gomega.Equal("www.ds.byoc.example.com"))
	}
	{
		cli, err := New(
			"",
			"",
			"foo",
			"bar",
			"dev")
		g.Expect(err).NotTo(gomega.BeNil())
		g.Expect(cli).To(gomega.BeNil())
	}
	{
		cli, err := New(
			"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
			"www.rs.%s.%s.observability-staging.pingcap.cloud",
			"",
			"",
			"dev")
		g.Expect(err).NotTo(gomega.BeNil())
		g.Expect(cli).To(gomega.BeNil())
	}
}

// mocking func
var getAccessTokenHandlerFunc = func(r *http.Request) (*http.Response, error) {
	if r.URL.Scheme != "https" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if r.URL.Path != "/oauth/token" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if r.Method != http.MethodPost {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if err := r.ParseForm(); err != nil {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if r.PostFormValue("client_id") != "foo" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if r.PostFormValue("client_secret") != "bar" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if r.PostFormValue("grant_type") != "client_credentials" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if r.PostFormValue("audience") != "https://tidb-soc2.us.auth0.com/api/v2/" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	content := `{"access_token":"xxx","scope":"read:client_grants","expires_in":86400,"token_type":"Bearer"}`
	return httpmock.NewStringResponse(http.StatusOK, content), nil
}

// mocking func
var notFoundHandlerFunc = func(r *http.Request) (*http.Response, error) {
	return httpmock.NewJsonResponse(http.StatusNotFound, struct{}{})
}

var listClusterHandlerFunc = func(r *http.Request) (*http.Response, error) {
	if r.URL.Scheme != "http" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if r.URL.Path != "/api/v1/clusters/" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if r.Method != http.MethodGet {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if r.Header.Get("Authorization") != "Bearer xxx" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	query := r.URL.Query()
	if query.Get("region") != "us-west-2" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if query.Get("vendor") != "aws" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if !strings.HasSuffix(query.Get("bizType"), "tidbcloud") {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if query.Get("tenantID") != "1369847559691367432" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if query.Get("applicationID") != "1373933076658091095" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	content := `{"code":0,"data":{"clusters":[{"applicationID":"1373933076658091095","bizType":"tidbcloud","id":"593e3d7d-d2ed-482a-b382-fe131cad8277","note":"","projectID":"1369847559691457286","region":"us-west-2","shareMode":"exclusive","tags":[],"tenantID":"1369847559691367432","vendor":"aws","version":""}],"continueToken":"","remainingItemCount":0},"message":""}`
	return httpmock.NewStringResponse(http.StatusOK, content), nil
}

var listClusterDevTierHandlerFunc = func(r *http.Request) (*http.Response, error) {
	if r.URL.Scheme != "http" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if r.URL.Path != "/api/v1/clusters/" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if r.Method != http.MethodGet {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if r.Header.Get("Authorization") != "Bearer xxx" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	query := r.URL.Query()
	if query.Get("region") != "us-west-2" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if query.Get("vendor") != "aws" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if !strings.HasSuffix(query.Get("bizType"), "devtier") {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if query.Get("tenantID") != "1369847559691367432" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	if query.Get("applicationID") != "1373933076658091095" {
		return httpmock.NewJsonResponse(http.StatusBadRequest, struct{}{})
	}
	content := `{"code":0,"data":{"clusters":[{"applicationID":"1373933076658091095","bizType":"tidbcloud","id":"593e3d7d-d2ed-482a-b382-fe131cad8277","note":"","projectID":"1369847559691457286","region":"us-west-2","shareMode":"exclusive","tags":[],"tenantID":"1369847559691367432","vendor":"aws","version":""}],"continueToken":"","remainingItemCount":0},"message":""}`
	return httpmock.NewStringResponse(http.StatusOK, content), nil
}

func TestClient_ListCluster(t *testing.T) {
	g := gomega.NewWithT(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()
	{
		cli, _ := New(
			"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
			"www.ds.%s.%s.observability-staging.pingcap.cloud",
			"foo",
			"bar",
			"dev")
		httpmock.RegisterResponder(http.MethodPost, "https://tidb-soc2.us.auth0.com/oauth/token", getAccessTokenHandlerFunc)
		httpmock.RegisterResponder(http.MethodGet, "http://www.gs.us-west-2.aws.observability-staging.pingcap.cloud/api/v1/clusters/", listClusterHandlerFunc)
		httpmock.RegisterNoResponder(notFoundHandlerFunc)
		data, err := cli.ListCluster(context.Background(), &ListClusterParam{
			BizType:       "tidbcloud",
			Region:        "us-west-2",
			Vendor:        "aws",
			TenantID:      "1369847559691367432",
			ApplicationID: "1373933076658091095",
		})
		g.Expect(err).To(gomega.Succeed())
		g.Expect(data.Clusters).To(gomega.HaveLen(1))
	}
	{
		httpmock.Reset()
		cli, _ := New(
			"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
			"www.ds.%s.%s.observability-staging.pingcap.cloud",
			"foo",
			"bar",
			"dev",
		)
		httpmock.RegisterResponder(http.MethodPost, "https://tidb-soc2.us.auth0.com/oauth/token", getAccessTokenHandlerFunc)
		httpmock.RegisterResponder(http.MethodGet, "http://www.gs.us-west-2.aws.observability-staging.pingcap.cloud/api/v1/clusters/", listClusterDevTierHandlerFunc)
		httpmock.RegisterNoResponder(notFoundHandlerFunc)
		data, err := cli.ListCluster(context.Background(), &ListClusterParam{
			BizType:       "dev-devtier",
			Region:        "us-west-2",
			Vendor:        "aws",
			TenantID:      "1369847559691367432",
			ApplicationID: "1373933076658091095",
		})
		g.Expect(err).To(gomega.Succeed())
		g.Expect(data.Clusters).To(gomega.HaveLen(1))
	}
}

func TestListClusterParamToFormShowDeleted(t *testing.T) {
	g := gomega.NewWithT(t)

	form, err := (&ListClusterParam{
		BizType:       "staging-tidbcloud",
		Region:        "us-east-1",
		Vendor:        "aws",
		TenantID:      "tenant",
		ApplicationID: "cluster",
		ShowDeleted:   true,
	}).ToForm()

	g.Expect(err).To(gomega.Succeed())
	g.Expect(form.Get("showDeleted")).To(gomega.Equal("true"))
}

func TestClientListClusterWithCacheEmptyResultIsNotFound(t *testing.T) {
	g := gomega.NewWithT(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	cli, err := New(
		"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
		"www.ds.%s.%s.observability-staging.pingcap.cloud",
		"foo",
		"bar",
		"dev",
	)
	g.Expect(err).To(gomega.Succeed())

	httpmock.RegisterResponder(http.MethodPost, "https://tidb-soc2.us.auth0.com/oauth/token", getAccessTokenHandlerFunc)
	httpmock.RegisterResponder(http.MethodGet, "http://www.gs.us-west-2.aws.observability-staging.pingcap.cloud/api/v1/clusters/",
		func(r *http.Request) (*http.Response, error) {
			return httpmock.NewStringResponse(http.StatusOK, `{"code":0,"data":{"clusters":[],"continueToken":"","remainingItemCount":0},"message":""}`), nil
		})

	_, err = cli.ListClusterWithCache(context.Background(), &ListClusterParam{
		BizType:       "dev-tidbcloud",
		Region:        "us-west-2",
		Vendor:        "aws",
		TenantID:      "infra-dev",
		ProjectID:     "seed",
		ApplicationID: "missing-seed",
		ShowDeleted:   true,
	})

	g.Expect(err).To(gomega.HaveOccurred())
	g.Expect(errors.Is(err, ErrNotFound)).To(gomega.BeTrue())
}

func TestClientSeedClusterEntryPointsUseExactBizTypes(t *testing.T) {
	g := gomega.NewWithT(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	cli, err := New(
		"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
		"www.ds.%s.%s.observability-staging.pingcap.cloud",
		"foo",
		"bar",
		"dev",
	)
	g.Expect(err).To(gomega.Succeed())

	httpmock.RegisterResponder(http.MethodPost, "https://tidb-soc2.us.auth0.com/oauth/token", getAccessTokenHandlerFunc)
	requestedBizTypes := make([]string, 0, 2)
	httpmock.RegisterResponder(http.MethodGet, "http://www.gs.us-west-2.aws.observability-staging.pingcap.cloud/api/v1/clusters/",
		func(r *http.Request) (*http.Response, error) {
			query := r.URL.Query()
			bizType := query.Get("bizType")
			requestedBizTypes = append(requestedBizTypes, bizType)
			g.Expect(query.Get("tenantID")).To(gomega.Equal("infra-dev"))
			g.Expect(query.Get("projectID")).To(gomega.Equal("seed"))
			g.Expect(query.Get("vendor")).To(gomega.Equal("aws"))
			g.Expect(query.Get("region")).To(gomega.Equal("us-west-2"))
			g.Expect(query.Get("applicationID")).To(gomega.Equal("usw2-byoc-pm"))
			g.Expect(query.Get("showDeleted")).To(gomega.Equal("true"))
			g.Expect(r.Header.Get("X-Biz-Type")).To(gomega.Equal(bizType))
			g.Expect(r.Header.Get("X-Org-Id")).To(gomega.Equal("infra-dev"))

			content := fmt.Sprintf(`{"code":0,"data":{"clusters":[{"applicationID":"usw2-byoc-pm","bizType":%q,"id":%q,"projectID":"seed","region":"us-west-2","tenantID":"infra-dev","vendor":"aws","internalReadUri":"https://metrics.example.com/api/v1/"}],"continueToken":"","remainingItemCount":0},"message":""}`, bizType, bizType+"-id")
			return httpmock.NewStringResponse(http.StatusOK, content), nil
		})

	dedicated, err := cli.GetDedicatedSeedCluster(context.Background(), "aws", "us-west-2", "usw2-byoc-pm")
	g.Expect(err).To(gomega.Succeed())
	g.Expect(dedicated.BizType).To(gomega.Equal("dev-tidbcloud"))

	nextGen, err := cli.GetNextGenSeedCluster(context.Background(), "aws", "us-west-2", "usw2-byoc-pm")
	g.Expect(err).To(gomega.Succeed())
	g.Expect(nextGen.BizType).To(gomega.Equal("dev-tidbcloud-nextgen"))
	g.Expect(requestedBizTypes).To(gomega.Equal([]string{"dev-tidbcloud", "dev-tidbcloud-nextgen"}))
}

func TestClusterRequestBuildersEnableShowDeleted(t *testing.T) {
	g := gomega.NewWithT(t)

	cli, err := New(
		"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
		"www.ds.%s.%s.observability-staging.pingcap.cloud",
		"foo",
		"bar",
		"dev",
	)
	g.Expect(err).To(gomega.Succeed())

	params := []*ListClusterParam{
		cli.GetProjectK8sClusterRequest("aws", "us-east-1", "tenant", "project"),
		cli.GetNextGenK8sClusterRequest("aws", "us-east-1", "tenant", "dataplane"),
		cli.GetDedicatedTidbClusterRequest("aws", "us-east-1", "tenant", "cluster"),
		cli.GetNextGenTidbClusterRequest("aws", "us-east-1", "tenant", "cluster"),
		cli.GetNextGenHostTidbClusterRequest("aws", "us-east-1", "tenant", "cluster"),
		cli.GetByocTidbClusterRequest("aws", "us-east-1", "tenant", "cluster"),
		cli.GetByocHostClusterRequest("aws", "us-east-1", "tenant", "cluster"),
		cli.GetByocK8sClusterRequest("aws", "us-east-1", "tenant", "dataplane"),
	}

	for _, param := range params {
		g.Expect(param.ShowDeleted).To(gomega.BeTrue())
	}
}

func TestClient_ListClusterByocDSEndpoint(t *testing.T) {
	g := gomega.NewWithT(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	cli, err := New(
		"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
		"www.ds.%s.%s.observability-staging.pingcap.cloud",
		"foo",
		"bar",
		"dev",
		ByocDSEndpointOption("www.ds.byoc.example.com"),
	)
	g.Expect(err).To(gomega.Succeed())

	httpmock.RegisterNoResponder(func(r *http.Request) (*http.Response, error) {
		content := `{"code":0,"data":{"clusters":[{"applicationID":"2","bizType":"byoc","id":"cluster-id","tenantID":"1","region":"us-west-2","vendor":"aws","internalReadUri":"https://origin.example.com/internal/cluster-id/metrics/api/v1/"}],"continueToken":"","remainingItemCount":0},"message":""}`
		return httpmock.NewStringResponse(http.StatusOK, content), nil
	})

	data, retry, err := cli.listCluster(context.Background(), "xxx", &ListClusterParam{
		BizType:       "byoc",
		Region:        "us-west-2",
		Vendor:        "aws",
		TenantID:      "1",
		ApplicationID: "2",
	})
	g.Expect(err).To(gomega.Succeed())
	g.Expect(retry).To(gomega.BeFalse())
	g.Expect(data.Clusters[0].InternalReadUri).To(gomega.Equal("http://www.ds.byoc.example.com/internal/cluster-id/metrics/api/v1/"))
}

func TestClient_GetServerlessControlPlaneClusterUsesBizTypeAsTenantID(t *testing.T) {
	g := gomega.NewWithT(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	cli, err := New(
		"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
		"www.ds.%s.%s.observability-staging.pingcap.cloud",
		"foo",
		"bar",
		"dev",
	)
	g.Expect(err).To(gomega.Succeed())

	httpmock.RegisterResponder(http.MethodPost, "https://tidb-soc2.us.auth0.com/oauth/token", getAccessTokenHandlerFunc)
	httpmock.RegisterResponder(http.MethodGet, "http://www.gs.us-west-2.aws.observability-staging.pingcap.cloud/api/v1/clusters/",
		func(r *http.Request) (*http.Response, error) {
			query := r.URL.Query()
			g.Expect(query.Get("bizType")).To(gomega.Equal("dev-devtier"))
			g.Expect(query.Get("tenantID")).To(gomega.Equal("dev-devtier"))
			g.Expect(query.Get("vendor")).To(gomega.Equal("alicloud"))
			g.Expect(query.Get("region")).To(gomega.Equal("ap-southeast-1"))
			g.Expect(query.Get("projectID")).To(gomega.Equal("ap-southeast-1-a01"))
			g.Expect(query.Get("applicationID")).To(gomega.Equal("devtier-infra"))
			g.Expect(r.Header.Get("X-Biz-Type")).To(gomega.Equal("dev-devtier"))
			g.Expect(r.Header.Get("X-Org-Id")).To(gomega.Equal("dev-devtier"))

			content := `{"code":0,"data":{"clusters":[{"applicationID":"devtier-infra","bizType":"dev-devtier","id":"control-plane-o11y-id","projectID":"ap-southeast-1-a01","region":"ap-southeast-1","shareMode":"shared","tags":[],"tenantID":"dev-devtier","vendor":"alicloud","version":"1","logQueryUri":"https://logs.example.com/loki/self-monitoring"}],"continueToken":"","remainingItemCount":0},"message":""}`
			return httpmock.NewStringResponse(http.StatusOK, content), nil
		})

	cluster, err := cli.GetServerlessControlPlaneCluster(context.Background(), "alicloud", "ap-southeast-1", "ap-southeast-1-a01", "devtier-infra")
	g.Expect(err).To(gomega.Succeed())
	g.Expect(cluster.ID).To(gomega.Equal("control-plane-o11y-id"))
	g.Expect(cluster.LogQueryUri).To(gomega.Equal("https://logs.example.com/loki/self-monitoring"))
}

func TestClient_GetProjectK8sClusterUsesO11yProjectID(t *testing.T) {
	g := gomega.NewWithT(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	cli, err := New(
		"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
		"www.ds.%s.%s.observability-staging.pingcap.cloud",
		"foo",
		"bar",
		"dev",
	)
	g.Expect(err).To(gomega.Succeed())

	httpmock.RegisterResponder(http.MethodPost, "https://tidb-soc2.us.auth0.com/oauth/token", getAccessTokenHandlerFunc)
	httpmock.RegisterResponder(http.MethodGet, "http://www.gs.us-west-2.aws.observability-staging.pingcap.cloud/api/v1/clusters/",
		func(r *http.Request) (*http.Response, error) {
			query := r.URL.Query()
			g.Expect(query.Get("projectID")).To(gomega.Equal("o11y-project"))
			g.Expect(query.Get("applicationID")).To(gomega.Equal("k8s-infra"))

			content := `{"code":0,"data":{"clusters":[{"applicationID":"k8s-infra","bizType":"dev-tidbcloud","id":"k8s-o11y-id","projectID":"o11y-project","region":"us-east-1","tenantID":"tenant","vendor":"aws"}],"continueToken":"","remainingItemCount":0},"message":""}`
			return httpmock.NewStringResponse(http.StatusOK, content), nil
		})

	cluster, err := cli.GetProjectK8sCluster(context.Background(), "aws", "us-east-1", "tenant", "o11y-project")
	g.Expect(err).To(gomega.Succeed())
	g.Expect(cluster.ID).To(gomega.Equal("k8s-o11y-id"))
}

func TestClient_GenDataServiceProxyHost(t *testing.T) {
	g := gomega.NewWithT(t)
	{
		cli, _ := New(
			"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
			"www.ds.%s.%s.observability-staging.pingcap.cloud",
			"foo",
			"bar",
			"dev")
		host := cli.GenDataServiceProxyHost("us-west-2", "aws")
		g.Expect(host).To(gomega.Equal("www.ds.us-west-2.aws.observability-staging.pingcap.cloud"))
	}
	{
		cli, _ := New(
			"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
			"www.ds.us-west-2.aws.observability-staging.pingcap.cloud",
			"foo",
			"bar",
			"dev")
		host := cli.GenDataServiceProxyHost("us-west-1", "aws")
		g.Expect(host).To(gomega.Equal("www.ds.us-west-2.aws.observability-staging.pingcap.cloud"))
	}
	{
		cli, _ := New(
			"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
			"www.ds.%s.%s.observability-staging.pingcap.cloud",
			"foo",
			"bar",
			"dev",
		)
		host := cli.GenDataServiceProxyHost("us-west-2", "aws")
		g.Expect(host).To(gomega.Equal("www.ds.us-west-2.aws.observability-staging.pingcap.cloud"))
	}
}

func TestClient_GenDataServiceProxyPath(t *testing.T) {
	g := gomega.NewWithT(t)
	cli, _ := New(
		"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
		"www.ds.%s.%s.observability-staging.pingcap.cloud",
		"foo",
		"bar",
		"dev")
	path := cli.GenDataServiceProxyPath("xxx", "/api/v1/labels")
	g.Expect(path).To(gomega.Equal("/internal/metrics/xxx/api/v1/labels"))
}
