package crossplane

import (
	"strings"
	"testing"

	. "github.com/onsi/gomega"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"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"

	awskmsv1alpha1 "github.com/crossplane/provider-aws/apis/kms/v1alpha1"
	awsdnsv1alpha1 "github.com/crossplane/provider-aws/apis/route53/v1alpha1"
	s3v1beta1 "github.com/crossplane/provider-aws/apis/s3/v1beta1"
	awsvpcpeeringv1alpha1 "github.com/crossplane/provider-aws/apis/vpcpeering/v1alpha1"
	gcpdnsv1alpha1 "github.com/crossplane/provider-gcp/apis/dns/v1alpha1"
	iamv1alpha1 "github.com/crossplane/provider-gcp/apis/iam/v1alpha1"
	storagev1alpha3 "github.com/crossplane/provider-gcp/apis/storage/v1alpha3"
	gcpvpcpeeringv1beta1 "github.com/crossplane/provider-gcp/apis/vpcpeering/v1beta1"

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

func TestMetricsCollector_Collect(t *testing.T) {
	tests := []struct {
		name           string
		objs           []client.Object
		expecedMetrics string
	}{
		{
			name: "basic metrics collector function - s3 buckets",
			objs: []client.Object{
				&s3v1beta1.Bucket{
					ObjectMeta: v1.ObjectMeta{
						Name:      "",
						Namespace: "",
					},
					Spec: s3v1beta1.BucketSpec{
						ResourceSpec: xpv1.ResourceSpec{
							WriteConnectionSecretToReference: &xpv1.SecretReference{
								Name:      "",
								Namespace: "",
							},
							ProviderConfigReference: &xpv1.Reference{
								Name: "",
							},
							ProviderReference: &xpv1.Reference{
								Name: "",
							},
							DeletionPolicy: "",
						},
						ForProvider: s3v1beta1.BucketParameters{},
					},
					Status: s3v1beta1.BucketStatus{
						ResourceStatus: xpv1.ResourceStatus{
							ConditionedStatus: xpv1.ConditionedStatus{
								Conditions: []xpv1.Condition{},
							},
						},
						AtProvider: s3v1beta1.BucketExternalStatus{
							ARN: "",
						},
					},
				},
			},
			expecedMetrics: `
			# HELP infra_crossplane_resources_aws_s3_bucket_info Information and status of aws s3 buckets.
            # TYPE infra_crossplane_resources_aws_s3_bucket_info gauge
            infra_crossplane_resources_aws_s3_bucket_info{name=""} 1
			`,
		},
		{
			name: "basic metrics collector function - gcs buckets",
			objs: []client.Object{
				&storagev1alpha3.Bucket{
					ObjectMeta: v1.ObjectMeta{},
					Spec: storagev1alpha3.BucketSpec{
						ResourceSpec: xpv1.ResourceSpec{
							WriteConnectionSecretToReference: &xpv1.SecretReference{
								Name:      "",
								Namespace: "",
							},
							ProviderConfigReference: &xpv1.Reference{
								Name: "",
							},
							ProviderReference: &xpv1.Reference{
								Name: "",
							},
							DeletionPolicy: "",
						},
						BucketParameters: storagev1alpha3.BucketParameters{
							BucketSpecAttrs: storagev1alpha3.BucketSpecAttrs{
								BucketUpdatableAttrs: storagev1alpha3.BucketUpdatableAttrs{
									BucketPolicyOnly: &storagev1alpha3.BucketPolicyOnly{
										Enabled:    false,
										LockedTime: v1.Time{},
									},
									CORS:                  []storagev1alpha3.CORS{},
									DefaultEventBasedHold: false,
									Encryption: &storagev1alpha3.BucketEncryption{
										DefaultKMSKeyName: "",
									},
									Labels: map[string]string{},
									Lifecycle: storagev1alpha3.Lifecycle{
										Rules: []storagev1alpha3.LifecycleRule{},
									},
									Logging: &storagev1alpha3.BucketLogging{
										LogBucket:       "",
										LogObjectPrefix: "",
									},
									PredefinedACL:              "",
									PredefinedDefaultObjectACL: "",
									RequesterPays:              false,
									RetentionPolicy: &storagev1alpha3.RetentionPolicy{
										RetentionPeriodSeconds: 0,
									},
									VersioningEnabled: false,
									Website: &storagev1alpha3.BucketWebsite{
										MainPageSuffix: "",
										NotFoundPage:   "",
									},
								},
								ACL:              []storagev1alpha3.ACLRule{},
								DefaultObjectACL: []storagev1alpha3.ACLRule{},
								Location:         "",
								StorageClass:     "",
							},
						},
					},
					Status: storagev1alpha3.BucketStatus{
						ResourceStatus: xpv1.ResourceStatus{},
						BucketOutputAttrs: storagev1alpha3.BucketOutputAttrs{
							BucketPolicyOnly: &storagev1alpha3.BucketPolicyOnly{
								Enabled:    false,
								LockedTime: v1.Time{},
							},
							Created: &v1.Time{},
							RetentionPolicy: &storagev1alpha3.RetentionPolicyStatus{
								EffectiveTime: v1.Time{},
								IsLocked:      false,
							},
						},
					},
				},
			},
			expecedMetrics: `
			# HELP infra_crossplane_resources_gcp_gcs_bucket_info Information and status of gcp gcs.
            # TYPE infra_crossplane_resources_gcp_gcs_bucket_info gauge
            infra_crossplane_resources_gcp_gcs_bucket_info{name=""} 1
			`,
		},
		{
			name: "basic metrics collector function - aws kms",
			objs: []client.Object{
				&awskmsv1alpha1.Key{
					TypeMeta:   v1.TypeMeta{},
					ObjectMeta: v1.ObjectMeta{},
					Spec: awskmsv1alpha1.KeySpec{
						ResourceSpec: xpv1.ResourceSpec{
							WriteConnectionSecretToReference: &xpv1.SecretReference{
								Name:      "",
								Namespace: "",
							},
							ProviderConfigReference: &xpv1.Reference{
								Name: "",
							},
							ProviderReference: &xpv1.Reference{
								Name: "",
							},
							DeletionPolicy: "",
						},
						ForProvider: awskmsv1alpha1.KeyParameters{
							Region:                         "",
							BypassPolicyLockoutSafetyCheck: new(bool),
							CustomKeyStoreID:               new(string),
							CustomerMasterKeySpec:          new(string),
							Description:                    new(string),
							KeyUsage:                       new(string),
							Origin:                         new(string),
							Policy:                         new(string),
							Tags:                           []*awskmsv1alpha1.Tag{},
							CustomKeyParameters: awskmsv1alpha1.CustomKeyParameters{
								Enabled:             new(bool),
								PendingWindowInDays: new(int64),
							},
						},
					},
					Status: awskmsv1alpha1.KeyStatus{
						ResourceStatus: xpv1.ResourceStatus{},
						AtProvider: awskmsv1alpha1.KeyObservation{
							AWSAccountID:         new(string),
							ARN:                  new(string),
							CloudHsmClusterID:    new(string),
							CreationDate:         &v1.Time{},
							DeletionDate:         &v1.Time{},
							Enabled:              new(bool),
							EncryptionAlgorithms: []*string{},
							ExpirationModel:      new(string),
							KeyID:                new(string),
							KeyManager:           new(string),
							KeyState:             new(string),
							SigningAlgorithms:    []*string{},
							ValidTo:              &v1.Time{},
						},
					},
				},
			},
			expecedMetrics: `
			# HELP infra_crossplane_resources_aws_kms_info Information and status of aws kms.
            # TYPE infra_crossplane_resources_aws_kms_info gauge
            infra_crossplane_resources_aws_kms_info{name=""} 1
			`,
		},
		{
			name: "basic metrics collector function - aws dns",
			objs: []client.Object{
				&awsdnsv1alpha1.ResourceRecordSet{
					TypeMeta:   v1.TypeMeta{},
					ObjectMeta: v1.ObjectMeta{},
					Spec: awsdnsv1alpha1.ResourceRecordSetSpec{
						ResourceSpec: xpv1.ResourceSpec{
							WriteConnectionSecretToReference: &xpv1.SecretReference{
								Name:      "",
								Namespace: "",
							},
							ProviderConfigReference: &xpv1.Reference{
								Name: "",
							},
							ProviderReference: &xpv1.Reference{
								Name: "",
							},
							DeletionPolicy: "",
						},
						ForProvider: awsdnsv1alpha1.ResourceRecordSetParameters{
							AliasTarget: &awsdnsv1alpha1.AliasTarget{
								DNSName:              "",
								EvaluateTargetHealth: false,
								HostedZoneID:         "",
							},
							Failover: "",
							GeoLocation: &awsdnsv1alpha1.GeoLocation{
								ContinentCode:   new(string),
								CountryCode:     new(string),
								SubdivisionCode: new(string),
							},
							HealthCheckID:           new(string),
							MultiValueAnswer:        new(bool),
							Region:                  "",
							ResourceRecords:         []awsdnsv1alpha1.ResourceRecord{},
							SetIdentifier:           new(string),
							TTL:                     new(int64),
							TrafficPolicyInstanceID: new(string),
							Type:                    "",
							Weight:                  new(int64),
							ZoneID:                  new(string),
							ZoneIDRef:               &xpv1.Reference{},
							ZoneIDSelector:          &xpv1.Selector{},
						},
					},
					Status: awsdnsv1alpha1.ResourceRecordSetStatus{
						ResourceStatus: xpv1.ResourceStatus{},
					},
				},
			},
			expecedMetrics: `
			# HELP infra_crossplane_resources_aws_dns_record_info Information and status of aws dns record.
            # TYPE infra_crossplane_resources_aws_dns_record_info gauge
            infra_crossplane_resources_aws_dns_record_info{name=""} 1
			`,
		},
		{
			name: "basic metrics collector function - gcp dns",
			objs: []client.Object{
				&gcpdnsv1alpha1.ResourceRecordSet{
					TypeMeta:   v1.TypeMeta{},
					ObjectMeta: v1.ObjectMeta{},
					Spec: gcpdnsv1alpha1.ResourceRecordSetSpec{
						ResourceSpec: xpv1.ResourceSpec{
							WriteConnectionSecretToReference: &xpv1.SecretReference{
								Name:      "",
								Namespace: "",
							},
							ProviderConfigReference: &xpv1.Reference{
								Name: "",
							},
							ProviderReference: &xpv1.Reference{
								Name: "",
							},
							DeletionPolicy: "",
						},
						ForProvider: gcpdnsv1alpha1.ResourceRecordSetParameters{
							Rrdatas: []string{},
							TTL:     new(int64),
							Type:    new(string),
							ZoneID:  new(string),
						},
					},
					Status: gcpdnsv1alpha1.ResourceRecordSetStatus{
						ResourceStatus: xpv1.ResourceStatus{},
					},
				},
			},
			expecedMetrics: `
			# HELP infra_crossplane_resources_gcp_dns_record_info Information and status of gcp dns records.
            # TYPE infra_crossplane_resources_gcp_dns_record_info gauge
            infra_crossplane_resources_gcp_dns_record_info{name=""} 1
			`,
		},
		{
			name: "basic metrics collector function - iam serviceaccount",
			objs: []client.Object{
				&iamv1alpha1.ServiceAccount{
					TypeMeta:   v1.TypeMeta{},
					ObjectMeta: v1.ObjectMeta{},
					Spec: iamv1alpha1.ServiceAccountSpec{
						ResourceSpec: xpv1.ResourceSpec{
							WriteConnectionSecretToReference: &xpv1.SecretReference{
								Name:      "",
								Namespace: "",
							},
							ProviderConfigReference: &xpv1.Reference{
								Name: "",
							},
							ProviderReference: &xpv1.Reference{
								Name: "",
							},
							DeletionPolicy: "",
						},
						ForProvider: iamv1alpha1.ServiceAccountParameters{
							DisplayName: new(string),
							Description: new(string),
						},
					},
					Status: iamv1alpha1.ServiceAccountStatus{
						ResourceStatus: xpv1.ResourceStatus{},
						AtProvider: iamv1alpha1.ServiceAccountObservation{
							Name:           "",
							ProjectID:      "",
							UniqueID:       "",
							Email:          "",
							Oauth2ClientID: "",
							Disabled:       false,
						},
					},
				},
			},
			expecedMetrics: `
			# HELP infra_crossplane_resources_gcp_iam_serviceaccount_info Information and status of gcp iamserviceaccount.
            # TYPE infra_crossplane_resources_gcp_iam_serviceaccount_info gauge
            infra_crossplane_resources_gcp_iam_serviceaccount_info{name=""} 1
			`,
		},
		{
			name: "basic metrics collector function - aws vpc peering",
			objs: []client.Object{
				&awsvpcpeeringv1alpha1.VPCPeeringConnection{
					TypeMeta:   v1.TypeMeta{},
					ObjectMeta: v1.ObjectMeta{},
					Spec: awsvpcpeeringv1alpha1.VPCPeeringConnectionSpec{
						ResourceSpec: xpv1.ResourceSpec{
							WriteConnectionSecretToReference: &xpv1.SecretReference{
								Name:      "",
								Namespace: "",
							},
							ProviderConfigReference: &xpv1.Reference{
								Name: "",
							},
							ProviderReference: &xpv1.Reference{
								Name: "",
							},
							DeletionPolicy: "",
						},
						ForProvider: awsvpcpeeringv1alpha1.VPCPeeringConnectionParameters{
							Region:      "",
							PeerOwnerID: new(string),
							PeerRegion:  new(string),
							PeerCIDR:    new(string),
							PeerVPCID:   new(string),
							Tags:        []*awsvpcpeeringv1alpha1.Tag{},
							VPCID:       new(string),
							HostZoneID:  new(string),
						},
					},
					Status: awsvpcpeeringv1alpha1.VPCPeeringConnectionStatus{
						ResourceStatus: xpv1.ResourceStatus{},
						AtProvider: awsvpcpeeringv1alpha1.VPCPeeringConnectionObservation{
							AccepterVPCInfo: &awsvpcpeeringv1alpha1.VPCPeeringConnectionVPCInfo{
								CIDRBlock:        new(string),
								CIDRBlockSet:     []*awsvpcpeeringv1alpha1.CIDRBlock{},
								IPv6CIDRBlockSet: []*awsvpcpeeringv1alpha1.IPv6CIDRBlock{},
								OwnerID:          new(string),
								PeeringOptions: &awsvpcpeeringv1alpha1.VPCPeeringConnectionOptionsDescription{
									AllowDNSResolutionFromRemoteVPC:            new(bool),
									AllowEgressFromLocalClassicLinkToRemoteVPC: new(bool),
									AllowEgressFromLocalVPCToRemoteClassicLink: new(bool),
								},
								Region: new(string),
								VPCID:  new(string),
							},
							ExpirationTime: &v1.Time{},
							RequesterVPCInfo: &awsvpcpeeringv1alpha1.VPCPeeringConnectionVPCInfo{
								CIDRBlock:        new(string),
								CIDRBlockSet:     []*awsvpcpeeringv1alpha1.CIDRBlock{},
								IPv6CIDRBlockSet: []*awsvpcpeeringv1alpha1.IPv6CIDRBlock{},
								OwnerID:          new(string),
								PeeringOptions: &awsvpcpeeringv1alpha1.VPCPeeringConnectionOptionsDescription{
									AllowDNSResolutionFromRemoteVPC:            new(bool),
									AllowEgressFromLocalClassicLinkToRemoteVPC: new(bool),
									AllowEgressFromLocalVPCToRemoteClassicLink: new(bool),
								},
								Region: new(string),
								VPCID:  new(string),
							},
							Status: &awsvpcpeeringv1alpha1.VPCPeeringConnectionStateReason{
								Code:    new(string),
								Message: new(string),
							},
							Tags:                   []*awsvpcpeeringv1alpha1.Tag{},
							VPCPeeringConnectionID: new(string),
							Phase:                  new(string),
						},
					},
				},
				&gcpvpcpeeringv1beta1.Peering{
					TypeMeta:   v1.TypeMeta{},
					ObjectMeta: v1.ObjectMeta{},
					Spec:       gcpvpcpeeringv1beta1.PeeringSpec{},
					Status:     gcpvpcpeeringv1beta1.PeeringStatus{},
				},
			},
			expecedMetrics: `
			# HELP infra_crossplane_resources_aws_vpc_peering_connection_info Information and status of aws vpc peering.
            # TYPE infra_crossplane_resources_aws_vpc_peering_connection_info gauge
            infra_crossplane_resources_aws_vpc_peering_connection_info{name=""} 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())

			g.Expect(awskmsv1alpha1.AddToScheme(scheme)).To(Succeed())
			g.Expect(gcpvpcpeeringv1beta1.SchemeBuilder.AddToScheme(scheme)).To(Succeed())
			g.Expect(awsvpcpeeringv1alpha1.SchemeBuilder.AddToScheme(scheme)).To(Succeed())
			g.Expect(s3v1beta1.SchemeBuilder.AddToScheme(scheme)).To(Succeed())
			g.Expect(storagev1alpha3.SchemeBuilder.AddToScheme(scheme)).To(Succeed())
			g.Expect(iamv1alpha1.SchemeBuilder.AddToScheme(scheme)).To(Succeed())

			g.Expect(awsdnsv1alpha1.SchemeBuilder.AddToScheme(scheme)).To(Succeed())
			g.Expect(gcpdnsv1alpha1.SchemeBuilder.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)
			}
		})
	}
}
