package namer

import (
	"strings"
	"testing"

	. "github.com/onsi/gomega"
	"github.com/stretchr/testify/assert"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestUniqueIdentifier(t *testing.T) {
	cases := map[string]struct {
		meta           metav1.ObjectMeta
		options        []IdentifierOptions
		expectedPrefix string
		expectedLen    int
	}{
		"no option": {
			meta: metav1.ObjectMeta{
				Namespace: "staging-1",
				Name:      "aws-us-west-2",
			},
			expectedPrefix: "staging-1-aws-us-west-2",
			expectedLen:    32, // staging-1-aws-us-west-2-xxxxxxxx
		},
		"no option long name": {
			meta: metav1.ObjectMeta{
				Namespace: "staging-1372813089187791289",
				Name:      "aws-ap-southeast-1",
			},
			expectedPrefix: "staging-1372813089187791289-aws-ap-southeast-1",
			expectedLen:    55, // staging-1372813089187791289-aws-ap-southeast-1-xxxxxxxx
		},
		"with prefix": {
			meta: metav1.ObjectMeta{
				Namespace: "staging-1",
				Name:      "aws-us-west-2",
			},
			options:        []IdentifierOptions{WithPrefix("tidbcloud-")},
			expectedPrefix: "tidbcloud-staging-1-aws-us-west-2",
			expectedLen:    42, // tidbcloud-staging-1-aws-us-west-2-xxxxxxxx
		},
		"with limit": {
			meta: metav1.ObjectMeta{
				Namespace: "staging-1372813089187791289",
				Name:      "gke-asia-northeast1",
			},
			options:        []IdentifierOptions{WithLimit(40)},
			expectedPrefix: "staging-1372813089187791289-gke",
			expectedLen:    40, // staging-1372813089187791289-gke-xxxxxxxx
		},
		"long with prefix and limit": {
			meta: metav1.ObjectMeta{
				Namespace: "staging-1372813089187791289",
				Name:      "gke-asia-northeast1",
			},
			options:        []IdentifierOptions{WithPrefix("tidbcloud-"), WithLimit(40)},
			expectedPrefix: "tidbcloud-staging-1372813089187",
			expectedLen:    40, // tidbcloud-staging-1372813089187-xxxxxxxx
		},
		"short with prefix and limit": {
			meta: metav1.ObjectMeta{
				Namespace: "nightly-ms-12345",
				Name:      "gke-us-west1",
			},
			options:        []IdentifierOptions{WithPrefix("tidbcloud-"), WithLimit(40)},
			expectedPrefix: "tidbcloud-nightly-ms-12345-gke-",
			expectedLen:    40, // tidbcloud-nightly-ms-12345-gke--xxxxxxxx
		},
		"with limit and prefix": { // limit prior to prefix would result in the final uid exceed limit
			meta: metav1.ObjectMeta{
				Namespace: "staging-ms-1372813089187791289",
				Name:      "gke-asia-northeast1",
			},
			options:        []IdentifierOptions{WithLimit(40), WithPrefix("tidbcloud-")},
			expectedPrefix: "tidbcloud-staging-ms-1372813089187791289-",
			expectedLen:    50, // tidbcloud-staging-ms-1372813089187791289--xxxxxxxx
		},
	}

	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			uid := UniqueIdentifier(tc.meta, tc.options...)
			idx := strings.LastIndex(uid, "-")
			prefix := uid[:idx]
			g.Expect(prefix).To(Equal(tc.expectedPrefix))
			g.Expect(uid).To(HaveLen(tc.expectedLen))
		})
	}
}

func TestNewName(t *testing.T) {
	cases := []struct {
		desc     string
		identity string
		prefix   string
		suffix   string
		limit    int
		result   string
	}{
		{
			desc:     "only identity",
			identity: "test-test",
			prefix:   "",
			suffix:   "",
			limit:    -1,
			result:   "test-test-1af1a690",
		},
		{
			desc:     "with prefix",
			identity: "test-test",
			prefix:   "prefix",
			suffix:   "",
			limit:    -1,
			result:   "prefix-test-test-1af1a690",
		},
		{
			desc:     "with suffix",
			identity: "test-test",
			prefix:   "",
			suffix:   "suffix",
			limit:    -1,
			result:   "test-test-1af1a690-suffix",
		},
		{
			desc:     "with prefix and suffix",
			identity: "test-test",
			prefix:   "prefix",
			suffix:   "suffix",
			limit:    -1,
			result:   "prefix-test-test-1af1a690-suffix",
		},
		{
			desc:     "with limit=10",
			identity: "test-test",
			prefix:   "",
			suffix:   "",
			limit:    10,
			result:   "t-1af1a690",
		},
		{
			desc:     "with limit=len(identity)+len(hash)+1",
			identity: "test-test",
			prefix:   "",
			suffix:   "",
			limit:    9 + 8 + 1,
			result:   "test-test-1af1a690",
		},
		{
			desc:     "with limit<len(identity)+len(hash)+1",
			identity: "test-test",
			prefix:   "",
			suffix:   "",
			limit:    9 + 8 + 1 - 5,
			result:   "test-1af1a690",
		},
		{
			desc:     "with limit<len(prefix)+len(identity)+len(hash)+len(suffix)+3",
			identity: "test-test",
			prefix:   "prefix",
			suffix:   "suffix",
			limit:    6 + 9 + 8 + 6 + 3 - 5,
			result:   "prefix-test-1af1a690-suffix",
		},
	}

	for _, c := range cases {
		t.Run(c.desc, func(t *testing.T) {
			g := NewGomegaWithT(t)
			n := NewName(c.identity, c.prefix, c.suffix, c.limit)
			g.Expect(n).To(Equal(c.result))
		})
	}
}

func TestClusterServerSAName(t *testing.T) {
	assert.Equalf(t, "dev-cluster-server@pingcap-gardener.iam.gserviceaccount.com", ClusterServerIAMSAEmail("dev-cluster-server", "pingcap-gardener"), "ClusterServerIAMSAEmail mismatch")
}

func TestNamerForTiDBGroupName(t *testing.T) {
	testCases := []struct {
		desc                         string
		cluster                      *cloudv1.Cluster
		groupSpec                    *cloudv1.ExtraGroupSpec
		publicHostedZoneDomain       string
		expectedTiDBName             string
		expectedClusterName          string
		expectedDNSNames             []string
		expectedPublicEndpoint       string
		expectedPrivateEndpoint      string
		expectedPrivateLinkEndpoint  string
		expectedGCPPSCEndpointDomain string
	}{
		{
			desc: "basic case",
			cluster: &cloudv1.Cluster{
				ObjectMeta: metav1.ObjectMeta{
					Name: "123",
				},
			},
			groupSpec: &cloudv1.ExtraGroupSpec{
				Name: "group1",
				SqlEndpoints: cloudv1.SqlEndpoints{
					DNS: cloudv1.DNS{
						Identity: "test-identity",
					},
				},
			},
			publicHostedZoneDomain: "example.com",
			expectedTiDBName:       "db-group1-513772ee",
			expectedClusterName:    "123-group1",
			expectedDNSNames: []string{
				"db-group1-513772ee-tidb.tidb123",
				"db-group1-513772ee-tidb.tidb123.svc",
				"tidb.test-identity.example.com",
				"private-tidb.test-identity.example.com",
				"privatelink-tidb.test-identity.example.com",
				"*.test-identity.example.com",
			},
			expectedPublicEndpoint:       "tidb.test-identity.example.com",
			expectedPrivateEndpoint:      "private-tidb.test-identity.example.com",
			expectedPrivateLinkEndpoint:  "privatelink-tidb.test-identity.example.com",
			expectedGCPPSCEndpointDomain: "test-identity.example.com",
		},
		{
			desc: "with different identity and domain",
			cluster: &cloudv1.Cluster{
				ObjectMeta: metav1.ObjectMeta{
					Name: "123",
				},
			},
			groupSpec: &cloudv1.ExtraGroupSpec{
				Name: "analytics",
				SqlEndpoints: cloudv1.SqlEndpoints{
					DNS: cloudv1.DNS{
						Identity: "prod-analytics",
					},
				},
			},
			publicHostedZoneDomain: "pingcap.net",
			expectedTiDBName:       "db-analytics-68c4283d",
			expectedClusterName:    "123-analytics",
			expectedDNSNames: []string{
				"db-analytics-68c4283d-tidb.tidb123",
				"db-analytics-68c4283d-tidb.tidb123.svc",
				"tidb.prod-analytics.pingcap.net",
				"private-tidb.prod-analytics.pingcap.net",
				"privatelink-tidb.prod-analytics.pingcap.net",
				"*.prod-analytics.pingcap.net",
			},
			expectedPublicEndpoint:       "tidb.prod-analytics.pingcap.net",
			expectedPrivateEndpoint:      "private-tidb.prod-analytics.pingcap.net",
			expectedPrivateLinkEndpoint:  "privatelink-tidb.prod-analytics.pingcap.net",
			expectedGCPPSCEndpointDomain: "prod-analytics.pingcap.net",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.desc, func(t *testing.T) {
			namer := NewNamer().
				WithTiDBNamespace(tc.cluster.PrefixedId()).
				WithComponent(string(cloudv1.ComponentTiDB))
			result := namer.ForTiDBGroup(tc.cluster, tc.groupSpec, tc.publicHostedZoneDomain)

			assert.Equalf(t, tc.expectedTiDBName, result.GetTiDBName(), "TiDB name mismatch")
			assert.Equalf(t, tc.expectedClusterName, result.GetClusterName(), "Cluster name mismatch")
			assert.Equalf(t, tc.expectedDNSNames, result.GetClusterServerDNSNames(false), "DNS names mismatch")
			assert.Equalf(t, tc.expectedPublicEndpoint, result.GetPublicEndpointAddress(), "Public endpoint mismatch")
			assert.Equalf(t, tc.expectedPrivateEndpoint, result.GetPrivateEndpointAddress(), "Private endpoint mismatch")
			assert.Equalf(t, tc.expectedPrivateLinkEndpoint, result.GetPrivateLinkEndpointAddress(), "Private link endpoint mismatch")
			assert.Equalf(t, tc.expectedGCPPSCEndpointDomain, result.GetPrivateServiceConnectDomain(), "GCP PSC name mismatch")
		})
	}
}
