package cluster

import (
	"testing"

	"github.com/google/go-cmp/cmp"
	. "github.com/onsi/gomega"
	"github.com/tidbcloud/shared-provider/pkg/types"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/yaml"

	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"github.com/tidbcloud/config-service/sdk/api"
	"github.com/tidbcloud/config-service/sdk/mock"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
)

func TestCreateTidbCluster(t *testing.T) {
	cases := map[string]struct {
		cluster    *cloudv1.Cluster
		cc         *api.DescribeClusterConfigOutput
		expectYaml string
	}{
		"AWS": {
			cluster: &cloudv1.Cluster{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Cluster",
					APIVersion: "cloud.pingcap.com/v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-aws",
					Namespace: "test-aws-ns",
					Labels: map[string]string{
						"tags.tidbcloud.com/tenant":                              "1",
						"tags.tidbcloud.com/project":                             "1",
						"tags.tidbcloud.com/billing-cluster-status-from-central": "CREATING",
					},
					UID: "test-uid",
				},
				Spec: cloudv1.ClusterSpec{
					Cluster: cloudv1.ClusterTopology{
						TiDB: cloudv1.TiDB{
							Replicas:     2,
							InstanceType: "tidb.sharedtier",
						},
						TiKV: cloudv1.TiKV{
							Replicas:     3,
							InstanceType: "tikv.sharedtier",
						},
						PD: cloudv1.PD{
							Replicas:     3,
							InstanceType: "pd.sharedtier",
						},
					},
				},
			},
			cc: getMockedClusterConfig(),
			expectYaml: `metadata:
  labels:
    infra.tidbcloud.com/name: test-aws
    infra.tidbcloud.com/namespace: test-aws-ns
    infra.tidbcloud.com/owned-by: cloud.pingcap.com.v1.cluster
    infra.tidbcloud.com/uid: test-uid
    tags.tidbcloud.com/project: "1"
    tags.tidbcloud.com/tenant: "1"
  annotations:
    ops.tidbcloud.com/config-commit:                  TEST
    ops.tidbcloud.com/config-id:                      base
    ops.tidbcloud.com/config-version:                 "1"
    tidb.tidb.pingcap.com/skip-tls-when-connect-tidb: ""
  name: "test-aws"
  namespace: test-user-ns
  resourceVersion: "1"
spec:
  helper:
    image: gcr.io/pingcap-public/dbaas/busybox:1.31.1
  labels:
    infra.tidbcloud.com/name: test-aws
    infra.tidbcloud.com/namespace: test-aws-ns
    infra.tidbcloud.com/owned-by: cloud.pingcap.com.v1.cluster
    infra.tidbcloud.com/uid: test-uid
    tags.tidbcloud.com/project: "1"
    tags.tidbcloud.com/tenant: "1"
  podSecurityContext:
    runAsUser: 1000
    runAsGroup: 2000
    fsGroup: 2000
  discovery:
    limits:
      cpu: 100m
      memory: 50Mi
    requests:
      cpu: 20m
      memory: 10Mi
  pd:
    baseImage: gcr.io/pingcap-public/dbaas/pd
    env:
    - name: DASHBOARD_SESSION_SECRET
      valueFrom:
        secretKeyRef:
          name: test-aws-dashboard-session-secret
          key: encryption_key
    config: |
      [dashboard]
        public-path-prefix = "/proxy/cluster/test-aws/pd/dashboard"
      [security]
        cert-allowed-cn = ["b02d2429"]
    limits:
      cpu: "2"
      memory: 4Gi
    replicas: 3
    requests:
      cpu: 200m
      memory: 1Gi
      storage: 1Gi
    storageClassName: default
    maxFailoverCount: 0
    mountClusterClientSecret: true
  pvReclaimPolicy: Delete
  tidb:
    readinessProbe:
      type: command
    annotations:
      tidb.pingcap.com/sysctl-init: "true"
    baseImage: gcr.io/pingcap-public/dbaas/tidb
    binlogEnabled: false
    config: |
      [performance]
        tcp-keep-alive = true
      [security]
        cluster-verify-cn = ["b02d2429"]
    limits:
      cpu: "8"
      memory: 16Gi
    maxFailoverCount: 3
    podSecurityContext:
      runAsUser: 1000
      runAsGroup: 2000
      fsGroup: 2000
      sysctls:
      - name: net.core.somaxconn
        value: "32768"
      - name: net.ipv4.tcp_keepalive_intvl
        value: "75"
      - name: net.ipv4.tcp_keepalive_time
        value: "300"
    replicas: 2
    requests:
      cpu: 800m
      memory: 2Gi
    separateSlowLog: true
    tlsClient:
      enabled: true
    slowLogTailer:
      limits:
        cpu: 100m
        memory: 50Mi
      requests:
        cpu: 20m
        memory: 5Mi
    service:
      type: ClusterIP
  tikv:
    annotations:
      tidb.pingcap.com/sysctl-init: "true"
    baseImage: gcr.io/pingcap-public/dbaas/tikv
    evictLeaderTimeout: 1m
    mountClusterClientSecret: true
    config: |
      [security]
        cert-allowed-cn = ["b02d2429"]
        [security.encryption]
          data-encryption-method = "aes256-ctr"
          data-key-rotation-period = "7d"
          [security.encryption.master-key]
            key-id = ""
            region = ""
            type = "kms"
    limits:
      cpu: "8"
      memory: 32Gi
    maxFailoverCount: 0
    podSecurityContext:
      runAsUser: 1000
      runAsGroup: 2000
      fsGroup: 2000
      sysctls:
      - name: net.core.somaxconn
        value: "32768"
    replicas: 3
    requests:
      cpu: 800m
      memory: 2Gi
      storage: 10Gi
    serviceAccount: tidb-backup-manager
    storageClassName: default
  tlsCluster:
    enabled: true
  timezone: UTC
  topologySpreadConstraints:
  - topologyKey: kubernetes.io/hostname
  - topologyKey: topology.kubernetes.io/zone
  configUpdateStrategy: RollingUpdate
  enableDynamicConfiguration: true
  version: v5.0.2`,
		},
		"AWS_HTAP": {
			cluster: &cloudv1.Cluster{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Cluster",
					APIVersion: "cloud.pingcap.com/v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-aws-htap",
					Namespace: "test-aws-htap-ns",
					Labels: map[string]string{
						"tags.tidbcloud.com/tenant":  "1",
						"tags.tidbcloud.com/project": "1",
						"nopass.label":               "nopass",
					},
					UID: "test-uid",
				},
				Spec: cloudv1.ClusterSpec{
					Cluster: cloudv1.ClusterTopology{
						TiDB: cloudv1.TiDB{
							Replicas:     2,
							InstanceType: "tidb.sharedtier",
						},
						TiKV: cloudv1.TiKV{
							Replicas:     3,
							InstanceType: "tikv.sharedtier",
							StorageSize:  resource.MustParse("100Gi"),
						},
						PD: cloudv1.PD{
							Replicas:     3,
							InstanceType: "pd.sharedtier",
						},
						TiFlash: cloudv1.TiFlash{
							Replicas:     1,
							InstanceType: "tiflash.sharedtier",
						},
					},
				},
			},
			cc: getMockedClusterConfig(),
			expectYaml: `metadata:
  labels:
    infra.tidbcloud.com/name: test-aws-htap
    infra.tidbcloud.com/namespace: "test-aws-htap-ns"
    infra.tidbcloud.com/owned-by: cloud.pingcap.com.v1.cluster
    infra.tidbcloud.com/uid: test-uid
    tags.tidbcloud.com/project: "1"
    tags.tidbcloud.com/tenant: "1"
  annotations:
    ops.tidbcloud.com/config-commit:                  TEST
    ops.tidbcloud.com/config-id:                      base
    ops.tidbcloud.com/config-version:                 "1"
    tidb.tidb.pingcap.com/skip-tls-when-connect-tidb: ""
  name: "test-aws-htap"
  namespace: test-user-ns
  resourceVersion: "1"
spec:
  helper:
    image: gcr.io/pingcap-public/dbaas/busybox:1.31.1
  labels:
    infra.tidbcloud.com/name: test-aws-htap
    infra.tidbcloud.com/namespace: test-aws-htap-ns
    infra.tidbcloud.com/owned-by: cloud.pingcap.com.v1.cluster
    infra.tidbcloud.com/uid: test-uid
    tags.tidbcloud.com/project: "1"
    tags.tidbcloud.com/tenant: "1"
  podSecurityContext:
    runAsUser: 1000
    runAsGroup: 2000
    fsGroup: 2000
  discovery:
    limits:
      cpu: 100m
      memory: 50Mi
    requests:
      cpu: 20m
      memory: 10Mi
  pd:
    baseImage: gcr.io/pingcap-public/dbaas/pd
    env:
    - name: DASHBOARD_SESSION_SECRET
      valueFrom:
        secretKeyRef:
          name: test-aws-htap-dashboard-session-secret
          key: encryption_key
    config: |
      [dashboard]
        public-path-prefix = "/proxy/cluster/test-aws-htap/pd/dashboard"

      [replication]
        enable-placement-rules = true

      [security]
        cert-allowed-cn = ["fc01008d"]
    limits:
      cpu: "2"
      memory: 4Gi
    replicas: 3
    requests:
      cpu: 200m
      memory: 1Gi
      storage: 1Gi
    storageClassName: default
    mountClusterClientSecret: true
    maxFailoverCount: 0
  pvReclaimPolicy: Delete
  tidb:
    readinessProbe:
      type: command
    annotations:
      tidb.pingcap.com/sysctl-init: "true"
    baseImage: gcr.io/pingcap-public/dbaas/tidb
    binlogEnabled: false
    config: |
      [performance]
        tcp-keep-alive = true
      [security]
        cluster-verify-cn = ["fc01008d"]
    limits:
      cpu: "8"
      memory: 16Gi
    maxFailoverCount: 3
    podSecurityContext:
      runAsUser: 1000
      runAsGroup: 2000
      fsGroup: 2000
      sysctls:
      - name: net.core.somaxconn
        value: "32768"
      - name: net.ipv4.tcp_keepalive_intvl
        value: "75"
      - name: net.ipv4.tcp_keepalive_time
        value: "300"
    replicas: 2
    requests:
      cpu: 800m
      memory: 2Gi
    separateSlowLog: true
    tlsClient:
      enabled: true
    slowLogTailer:
      limits:
        cpu: 100m
        memory: 50Mi
      requests:
        cpu: 20m
        memory: 5Mi
    service:
      type: ClusterIP
  tiflash:
    baseImage: gcr.io/pingcap-public/dbaas/tiflash
    config:
      config: |
        [security]
          cert_allowed_cn = ["fc01008d"]
      proxy: |
        [security]
          cert-allowed-cn = ["fc01008d"]
          [security.encryption]
            data-encryption-method = "aes256-ctr"
            data-key-rotation-period = "7d"
            [security.encryption.master-key]
              key-id = ""
              region = ""
              type = "kms"
    limits:
      cpu: "8"
      memory: 32Gi
    logTailer:
      limits:
        cpu: 100m
        memory: 50Mi
      requests:
        cpu: 20m
        memory: 5Mi
    replicas: 1
    requests:
      cpu: 800m
      memory: 2Gi
      storage: 15Gi
    storageClaims:
    - resources:
        requests:
          storage: 15Gi
      storageClassName: default
    serviceAccount: tidb-backup-manager
    maxFailoverCount: 0
  tikv:
    annotations:
      tidb.pingcap.com/sysctl-init: "true"
    baseImage: gcr.io/pingcap-public/dbaas/tikv
    evictLeaderTimeout: 1m
    config: |
      [security]
        cert-allowed-cn = ["fc01008d"]
        [security.encryption]
          data-encryption-method = "aes256-ctr"
          data-key-rotation-period = "7d"
          [security.encryption.master-key]
            key-id = ""
            region = ""
            type = "kms"
    limits:
      cpu: "8"
      memory: 32Gi
    maxFailoverCount: 0
    mountClusterClientSecret: true
    podSecurityContext:
      runAsUser: 1000
      runAsGroup: 2000
      fsGroup: 2000
      sysctls:
      - name: net.core.somaxconn
        value: "32768"
    replicas: 3
    requests:
      cpu: 800m
      memory: 2Gi
      storage: 100Gi
    serviceAccount: tidb-backup-manager
    storageClassName: default
  tlsCluster:
    enabled: true
  timezone: UTC
  topologySpreadConstraints:
  - topologyKey: kubernetes.io/hostname
  - topologyKey: topology.kubernetes.io/zone
  configUpdateStrategy: RollingUpdate
  enableDynamicConfiguration: true
  version: v5.0.2`,
		},
	}
	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			r := newFakeReconcileContext(tc.cluster, nil)
			r.ConfigService = &mock.MockClient{ClusterConfigOutput: tc.cc}

			g.Expect(r.createTidbCluster()).NotTo(HaveOccurred())

			// test marshal then unmarshal the tidbcluster value
			resultMarshaled, err := yaml.Marshal(r.tc)
			g.Expect(err).NotTo(HaveOccurred())
			var resultUnmarshaled v1alpha1.TidbCluster
			err = yaml.Unmarshal(resultMarshaled, &resultUnmarshaled)
			g.Expect(err).NotTo(HaveOccurred())

			var expect v1alpha1.TidbCluster
			err = yaml.Unmarshal([]byte(tc.expectYaml), &expect)

			g.Expect(err).NotTo(HaveOccurred())
			diff := cmp.Diff(&expect, &resultUnmarshaled)
			if diff != "" {
				t.Errorf("unexpected (-want, +got): %s", diff)
			}
		})
	}
}

func TestCreateTidbMonitor(t *testing.T) {
	cases := map[string]struct {
		cluster    *cloudv1.Cluster
		tc         *v1alpha1.TidbCluster
		expectYaml string
	}{
		"TEST-TM": {
			cluster: &cloudv1.Cluster{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Cluster",
					APIVersion: "cloud.pingcap.com/v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-tm",
					Namespace: "test-tm-ns",
					Labels: map[string]string{
						cloudv1.ProviderTypeLabel: types.ProviderTypeAWSSharedTier,
					},
					UID: "test-uid",
				},
			},
			tc: &v1alpha1.TidbCluster{
				ObjectMeta: metav1.ObjectMeta{
					Name: "test-tm",
					UID:  "dd2aac1d-16bf-8888-ab21-708454b9aa88",
				},
				Spec: v1alpha1.TidbClusterSpec{
					Version: "v5.0.2",
				},
			},
			expectYaml: `metadata:
  creationTimestamp: null
  name: test-tm
  namespace: test-user-ns
  labels:
    infra.tidbcloud.com/name: test-tm
    infra.tidbcloud.com/namespace: test-tm-ns
    infra.tidbcloud.com/owned-by: cloud.pingcap.com.v1.cluster
    infra.tidbcloud.com/uid: "test-uid"
  resourceVersion: "1"
spec:
  clusters:
  - name: test-tm
    namespace: test-user-ns
  initializer:
    baseImage: gcr.io/pingcap-public/dbaas/tidb-monitor-initializer
    version: v5.0.2
    limits:
      cpu: 100m
      memory: 50Mi
    requests:
      cpu: 20m
      memory: 10Mi
  persistent: true
  prometheus:
    baseImage: gcr.io/pingcap-public/third-party/prometheus
    logLevel: info
    retentionTime: 2h
    remoteWrite: [{}]
    version: v2.18.1
    limits:
      cpu: 300m
      memory: 800Mi
    requests:
      cpu: 80m
      memory: 200Mi
  pvReclaimPolicy: Delete
  externalLabels:
    cluster_id: ""
    cluster: "sharedtier/eks/"
    projectId: ""
    tenantId: ""
    provider_type: aws-shared-tier
    region: test-region
  reloader:
    baseImage: gcr.io/pingcap-public/dbaas/tidb-monitor-reloader
    version: v1.0.1
    limits:
      cpu: 100m
      memory: 50Mi
    requests:
      cpu: 20m
      memory: 10Mi
  storage: 2Gi
  storageClassName: default
  podSecurityContext:
    runAsUser: 1000
    runAsGroup: 2000
    fsGroup: 2000`,
		},
	}
	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			r := newFakeReconcileContext(tc.cluster, nil)
			r.tc = tc.tc

			g.Expect(r.createTidbMonitor()).To(Succeed())

			// test marshal then unmarshal the tidbmonitor value
			resultMarshaled, err := yaml.Marshal(r.tm)
			g.Expect(err).NotTo(HaveOccurred())
			var resultUnmarshaled v1alpha1.TidbMonitor
			err = yaml.Unmarshal(resultMarshaled, &resultUnmarshaled)
			g.Expect(err).NotTo(HaveOccurred())

			var expect v1alpha1.TidbMonitor
			err = yaml.Unmarshal([]byte(tc.expectYaml), &expect)

			g.Expect(err).NotTo(HaveOccurred())
			diff := cmp.Diff(&expect, &resultUnmarshaled)
			if diff != "" {
				t.Errorf("unexpected (-want, +got): %s", diff)
			}
		})
	}
}
