package infra

import (
	"strings"
	"testing"

	. "github.com/onsi/gomega"
	"k8s.io/apimachinery/pkg/api/resource"

	"github.com/prometheus/client_golang/prometheus/testutil"
	corev1 "k8s.io/api/core/v1"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/pkg/consts"
)

func TestMetricsCollector_Collect(t *testing.T) {
	tests := []struct {
		name           string
		objs           []client.Object
		expecedMetrics string
	}{
		{
			name: "basic metrics collector function - namespace",
			objs: []client.Object{
				&corev1.Namespace{
					ObjectMeta: v1.ObjectMeta{
						Name: "unit-123",
					},
					Status: corev1.NamespaceStatus{
						Phase: "Active",
						Conditions: []corev1.NamespaceCondition{
							{
								Type:               "",
								Status:             "",
								LastTransitionTime: v1.Time{},
								Reason:             "",
								Message:            "",
							},
						},
					},
				},
			},
			expecedMetrics: `
			# HELP infra_api_resources_namespace_info Information and status of infra namespace.
			# TYPE infra_api_resources_namespace_info gauge
			infra_api_resources_namespace_info{name="unit-123",status="Active"} 1
			`,
		},
		{
			name: "basic metrics collector function - network",
			objs: []client.Object{
				&cloudv1.Network{
					ObjectMeta: v1.ObjectMeta{
						Name:      "network",
						Namespace: "test",
					},
					Spec: cloudv1.NetworkSpec{
						Provider: "123",
						Region:   "us-west-2",
						CIDR:     "1",
					},
					Status: cloudv1.NetworkStatus{
						Phase: "Active",
						Peering: cloudv1.PeeringStatus{
							Allowed: false,
						},
						Import: cloudv1.NetworkImportStatus{
							RoleARN: "",
						},
						IAM: cloudv1.IAMStatus{
							ServiceAccountEmail: "123@dbaas.com",
							AccountID:           "123",
							ExternalId:          "111",
						},
					},
				},
			},
			expecedMetrics: `
			# HELP infra_api_resources_network_info Information and status of infra network.
			# TYPE infra_api_resources_network_info gauge
			infra_api_resources_network_info{account_id="123",bucket_name="",cidr="1",email="123@dbaas.com",external_id="111",name="network",namespace="test",project_id="",provider="123",region="us-west-2",shoot_name="",status="Active"} 1
			`,
		},
		{
			name: "basic metrics collector function - cluster",
			objs: []client.Object{
				&cloudv1.Cluster{
					ObjectMeta: v1.ObjectMeta{
						Name:      "test",
						Namespace: "test",
					},
					Spec: cloudv1.ClusterSpec{
						NetworkName: "test",
						SqlEndpoints: cloudv1.SqlEndpoints{
							Public: cloudv1.Service{
								Enabled:                  new(bool),
								LoadBalancerSourceRanges: []string{},
							},
							Private: &cloudv1.Service{
								Enabled:                  new(bool),
								LoadBalancerSourceRanges: []string{},
							},
						},
					},
					Status: cloudv1.ClusterStatus{
						Phase:  "normal",
						Reason: "",
						DB: &cloudv1.DBStatus{
							Version: "v5.3.0",
						},
						SqlEndpoints: cloudv1.SqlEndpointsStatus{
							PublicEndpoint: &cloudv1.SqlEndpointStatus{
								Host: "",
								Port: 0,
							},
							PrivateEndpoint: &cloudv1.SqlEndpointStatus{
								Host: "",
								Port: 0,
							},
						},
						ObjectStore: &cloudv1.ObjectStoreStatus{
							S3: &cloudv1.Path{
								Path: "s3://aws.com",
							},
							GCS: &cloudv1.Path{
								Path: "gcp://gcp.com",
							},
						},
						Metrics: &cloudv1.MetricsStatus{
							BaseURL:          "",
							CredentialConfig: "",
						},
						Proxy: &cloudv1.ProxyStatus{
							Endpoint:         "",
							CredentialConfig: "",
						},
						LastActiveTimestamp: &v1.Time{},
						LastDetectTimestamp: &v1.Time{},
						Conditions:          []cloudv1.ClusterCondition{},
						FromProvider:        runtime.RawExtension{},
					},
				},
			},
			expecedMetrics: `
            # HELP infra_api_resources_cluster_created Creation timestamp and status of infra cluster.
            # TYPE infra_api_resources_cluster_created gauge
            infra_api_resources_cluster_created{name="test",namespace="test",status="normal"} -6.21355968e+10
            # HELP infra_api_resources_cluster_info Information and status of infra cluster.
            # TYPE infra_api_resources_cluster_info gauge
            infra_api_resources_cluster_info{metrics_base_url="",name="test",namespace="test",network_name="test",proxy_endpoint="",status="normal",version="v5.3.0"} 1
			`,
		},
		{
			name: "basic metrics collector function - import",
			objs: []client.Object{
				&cloudv1.Import{
					ObjectMeta: v1.ObjectMeta{
						Name:      "test",
						Namespace: "test",
						Labels: map[string]string{
							cloudv1.ProviderTypeLabel: consts.ProviderTypeAWS,
							consts.TenantTagKey:       "1",
							consts.ProjectTagKey:      "1",
							consts.ClusterTagKey:      "123",
						},
					},
					Spec: cloudv1.ImportSpec{
						ClusterName: "test",
						Source: cloudv1.Source{
							Format:   0,
							Filters:  []string{},
							UserName: "remove-it",
							Password: "remove-it",
							CsvFormat: &cloudv1.CsvFormat{
								Separator:         new(string),
								Delimiter:         new(string),
								Header:            new(bool),
								NotNull:           new(bool),
								Null:              new(string),
								BackslashEscape:   new(bool),
								TrimLastSeparator: new(bool),
							},
						},
						StorageRequest: "",
						DryRun:         false,
						AccessConfig: cloudv1.AccessConfig{
							RoleArn: "",
						},
					},
					Status: cloudv1.ImportStatus{
						Phase: "running",
					},
				},
			},
			expecedMetrics: `
			# HELP infra_api_resources_import_info Information and status of import job.
		    # TYPE infra_api_resources_import_info gauge
		    infra_api_resources_import_info{cloud_provider="aws",cluster_id="123",cluster_name="test",import_config="{\"clusterName\":\"test\",\"source\":{\"format\":0,\"storage\":{},\"userName\":\"\",\"password\":\"\",\"csvFormat\":{\"separator\":\"\",\"delimiter\":\"\",\"header\":false,\"notNull\":false,\"null\":\"\",\"backslashEscape\":false,\"trimLastSeparator\":false}},\"accessConfig\":{}}",name="test",namespace="test",project_id="1",status="running",tenant_id="1"} 1
			`,
		},
		{
			name: "basic metrics collector function - backup",
			objs: []client.Object{
				&cloudv1.Backup{
					ObjectMeta: v1.ObjectMeta{
						Name:      "test",
						Namespace: "test",
						Labels: map[string]string{
							cloudv1.ProviderTypeLabel: consts.ProviderTypeAWS,
							consts.TenantTagKey:       "1",
							consts.ProjectTagKey:      "1",
							consts.ClusterTagKey:      "123",
						},
					},
					Spec: cloudv1.BackupSpec{
						ClusterName: "123",
					},
					Status: cloudv1.BackupStatus{
						Phase:      "running",
						BackupSize: 0,
						BackupTS:   "",
						BucketName: "",
					},
				},
			},
			expecedMetrics: `
			# HELP infra_api_resources_backup_info Information and status of backup job.
            # TYPE infra_api_resources_backup_info gauge
            infra_api_resources_backup_info{bucket_name="",cloud_provider="aws",cluster_id="123",cluster_name="123",name="test",namespace="test",project_id="1",status="running",tenant_id="1"} 1
			`,
		},
		{
			name: "basic metrics collector function - restore",
			objs: []client.Object{
				&cloudv1.Restore{
					ObjectMeta: v1.ObjectMeta{
						Name:      "test",
						Namespace: "test",
						Labels: map[string]string{
							cloudv1.ProviderTypeLabel: consts.ProviderTypeAWS,
							consts.TenantTagKey:       "1",
							consts.ProjectTagKey:      "1",
							consts.ClusterTagKey:      "123",
						},
					},
					Spec: cloudv1.RestoreSpec{
						ClusterName: "123",
						From: cloudv1.From{
							Backup: "test",
						},
					},
					Status: cloudv1.RestoreStatus{
						Phase: "running",
					},
				},
			},
			expecedMetrics: `
			# HELP infra_api_resources_restore_info Information and status of restore job.
            # TYPE infra_api_resources_restore_info gauge
            infra_api_resources_restore_info{backup="test",cloud_provider="aws",cluster_id="123",cluster_name="123",name="test",namespace="test",project_id="1",status="running",tenant_id="1"} 1
			`,
		},
		{
			name: "basic metrics collector function - networkpeer",
			objs: []client.Object{
				&cloudv1.NetworkPeer{
					ObjectMeta: v1.ObjectMeta{
						Name:      "test",
						Namespace: "test",
					},
					Spec: cloudv1.NetworkPeerSpec{
						Source: "aws-us-west-2",
						Target: cloudv1.Target{
							AWS: &cloudv1.AWS{
								AccountID:  "123",
								VpcID:      "123",
								PeerCIDR:   "10.0.0.0/24",
								PeerRegion: "us-west-2",
							},
							GCP: &cloudv1.GCP{
								ProjectID:   "123",
								PeerNetwork: "test",
							},
							TargetNetwork: &cloudv1.TargetNetwork{
								NetworkName: "targetNetwork",
							},
						},
					},
					Status: cloudv1.NetworkPeerStatus{
						Phase:        cloudv1.NetworkPeerPhaseActive,
						PeeringID:    "test",
						HostedZoneID: "test",
						VpcCidr:      "10.250.0.0/16",
					},
				},
			},
			expecedMetrics: `
			# HELP infra_api_resources_networkpeer_info Information and status of networkpeer.
            # TYPE infra_api_resources_networkpeer_info gauge
            infra_api_resources_networkpeer_info{hosted_zone_id="test",name="test",namespace="test",peer_cidr="10.0.0.0/24",peer_network="test",peer_region="us-west-2",peering_id="test",source="aws-us-west-2",status="Active",target_account_id="123",target_network="targetNetwork",target_project_id="123",target_vpc_id="123",vpc_cidr="10.250.0.0/16"} 1
			`,
		},
		{
			name: "basic metrics collector function - ticdc",
			objs: []client.Object{
				&cloudv1.TiCDC{
					ObjectMeta: v1.ObjectMeta{
						Name:      "test",
						Namespace: "test",
					},
					Spec: cloudv1.TiCDCSpec{
						NetworkName:  "aws-us-west-2",
						ClusterName:  "1",
						Replicas:     1,
						InstanceType: "test",
						StorageSize: resource.Quantity{
							Format: "",
						},
					},
					Status: cloudv1.TiCDCStatus{
						Phase: cloudv1.TiCDCPhaseCreating,
					},
				},
			},
			expecedMetrics: `
			# HELP infra_api_resources_ticdc_info Information and status of TiCDC.
            # TYPE infra_api_resources_ticdc_info gauge
            infra_api_resources_ticdc_info{cluster_name="1",instance_type="test",name="test",namespace="test",network_name="aws-us-west-2",replicas="1",status="Creating"} 1
			`,
		},
		{
			name: "basic metrics collector function - clusterconfig",
			objs: []client.Object{
				&cloudv1.ClusterConfig{
					ObjectMeta: v1.ObjectMeta{Name: "test", Namespace: "test"},
					Spec: cloudv1.ClusterConfigSpec{
						Version: "",
						ConfigMeta: &cloudv1.ConfigMeta{
							GitCommitID:   "",
							ConfigVersion: 0,
						},
						TiDB: &cloudv1.Configuration{
							BaseImage: new(string),
							Tag:       new(string),
							Config:    &cloudv1.TomlConfig{},
						},
						PD:           &cloudv1.Configuration{},
						TiKV:         &cloudv1.Configuration{},
						TiFlash:      &cloudv1.Configuration{},
						Backup:       &cloudv1.Configuration{},
						Restore:      &cloudv1.Configuration{},
						Lightning:    &cloudv1.Configuration{},
						TiCDC:        &cloudv1.Configuration{},
						UpdatePolicy: "",
						Priority:     0,
						Selector:     &v1.LabelSelector{},
					},
				},
			},
			expecedMetrics: `
			# HELP infra_api_resources_clusterconfig_info Information and status of ClusterConfig.
            # TYPE infra_api_resources_clusterconfig_info gauge
            infra_api_resources_clusterconfig_info{cluster_config="{\"version\":\"\",\"configMeta\":{\"gitCommitID\":\"\",\"configVersion\":0},\"tidb\":{\"baseImage\":\"\",\"tag\":\"\",\"config\":\"\"},\"pd\":{\"config\":null},\"tikv\":{\"config\":null},\"tiflash\":{\"config\":null},\"backup\":{\"config\":null},\"restore\":{\"config\":null},\"lightning\":{\"config\":null},\"ticdc\":{\"config\":null},\"selector\":{}}",cluster_id="",cluster_version="",name="test",tenant_id=""} 1
			`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)

			// initialize cli
			scheme := runtime.NewScheme()
			g.Expect(corev1.AddToScheme(scheme)).To(Succeed())
			g.Expect(cloudv1.AddToScheme(scheme)).To(Succeed())
			cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.objs...).Build()
			// initialize collector
			collector := &MetricsCollector{
				infraCli: cli,
			}

			err := testutil.CollectAndCompare(collector, strings.NewReader(tt.expecedMetrics))
			// use err to benil when you add the case
			// g.Expect(err).To(BeNil())
			if err != nil {
				t.Fatal(err)
			}
		})
	}
}
