package crossplane

import (
	"context"

	"github.com/prometheus/client_golang/prometheus"
	"sigs.k8s.io/controller-runtime/pkg/client"
	logf "sigs.k8s.io/controller-runtime/pkg/log"

	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"
)

var logger = logf.Log.WithName("crossplane-metrics")

var (
	InfraResourcesAWSS3BucketInfo = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "crossplane_resources", "aws_s3_bucket_info"),
		"Information and status of aws s3 buckets.",
		[]string{"name"},
		nil)

	InfraResourcesGCPGCSBucketInfo = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "crossplane_resources", "gcp_gcs_bucket_info"),
		"Information and status of gcp gcs.",
		[]string{"name"},
		nil)

	InfraResourcesAWSKMSInfo = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "crossplane_resources", "aws_kms_info"),
		"Information and status of aws kms.",
		[]string{"name"},
		nil)

	InfraResourcesAWSDNSRecordInfo = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "crossplane_resources", "aws_dns_record_info"),
		"Information and status of aws dns record.",
		[]string{"name"},
		nil)

	InfraResourcesGCPDNSRecordInfo = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "crossplane_resources", "gcp_dns_record_info"),
		"Information and status of gcp dns records.",
		[]string{"name"},
		nil)

	InfraResourcesAWSVPCPeeringConnectionInfo = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "crossplane_resources", "aws_vpc_peering_connection_info"),
		"Information and status of aws vpc peering.",
		[]string{"name"},
		nil)

	InfraResourcesGCPIAMServiceaccountInfo = prometheus.NewDesc(
		prometheus.BuildFQName("infra", "crossplane_resources", "gcp_iam_serviceaccount_info"),
		"Information and status of gcp iamserviceaccount.",
		[]string{"name"},
		nil)
)

type MetricsCollector struct {
	infraCli client.Client
}

func NewMetricsCollector(infraCli client.Client) *MetricsCollector {
	return &MetricsCollector{
		infraCli: infraCli,
	}
}

func (collector *MetricsCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- InfraResourcesAWSS3BucketInfo
	ch <- InfraResourcesAWSKMSInfo
	ch <- InfraResourcesAWSDNSRecordInfo
	ch <- InfraResourcesAWSVPCPeeringConnectionInfo
	ch <- InfraResourcesGCPGCSBucketInfo
	ch <- InfraResourcesGCPDNSRecordInfo
	ch <- InfraResourcesGCPIAMServiceaccountInfo
}

func (collector *MetricsCollector) Collect(ch chan<- prometheus.Metric) {
	cli := collector.infraCli
	ctx := context.Background()

	// AWS
	if err := collectAWSS3BucketMetrics(ctx, cli, ch); err != nil {
		logger.Error(err, "collect s3 bucket metrics failed")
		return
	}

	if err := collectGCPGCSBucketMetrics(ctx, cli, ch); err != nil {
		logger.Error(err, "collect gcp bucket metrics failed")
		return
	}

	if err := collectAWSKMSMetrics(ctx, cli, ch); err != nil {
		logger.Error(err, "collect aws kms metrics failed")
		return
	}

	if err := collectAWSDNSRecordMetrics(ctx, cli, ch); err != nil {
		logger.Error(err, "collect aws dns records metrics failed")
		return
	}

	if err := collectAWSVPCPeeringConnectionMetrics(ctx, cli, ch); err != nil {
		logger.Error(err, "collect aws vpc peering connection metrics failed")
		return
	}

	// GCP
	if err := collectGCPDNSRecordMetrics(ctx, cli, ch); err != nil {
		logger.Error(err, "collect gcp dns record metrics failed")
		return
	}

	if err := collectGCPIAMServiceaccountMetrics(ctx, cli, ch); err != nil {
		logger.Error(err, "collect gcp dns record metrics failed")
		return
	}
}

func collectAWSS3BucketMetrics(ctx context.Context, cli client.Client, ch chan<- prometheus.Metric) error {
	s3Buckets := s3v1beta1.BucketList{}
	if err := cli.List(ctx, &s3Buckets); err != nil {
		return err
	}

	for _, bucket := range s3Buckets.Items {
		ch <- prometheus.MustNewConstMetric(
			InfraResourcesAWSS3BucketInfo,
			prometheus.GaugeValue,
			1.0,
			bucket.Name,
		)
	}

	return nil
}

func collectGCPGCSBucketMetrics(ctx context.Context, cli client.Client, ch chan<- prometheus.Metric) error {
	gcpBuckets := storagev1alpha3.BucketList{}
	if err := cli.List(ctx, &gcpBuckets); err != nil {
		return err
	}

	for _, bucket := range gcpBuckets.Items {
		ch <- prometheus.MustNewConstMetric(
			InfraResourcesGCPGCSBucketInfo,
			prometheus.GaugeValue,
			1.0,
			bucket.Name,
		)
	}

	return nil
}
func collectAWSKMSMetrics(ctx context.Context, cli client.Client, ch chan<- prometheus.Metric) error {
	// kms.aws.crossplane.io/v1alpha1
	awskmslist := awskmsv1alpha1.KeyList{}
	if err := cli.List(ctx, &awskmslist); err != nil {
		return err
	}

	for _, awskms := range awskmslist.Items {
		ch <- prometheus.MustNewConstMetric(
			InfraResourcesAWSKMSInfo,
			prometheus.GaugeValue,
			1.0,
			awskms.Name,
		)
	}

	return nil
}

func collectAWSVPCPeeringConnectionMetrics(ctx context.Context, cli client.Client, ch chan<- prometheus.Metric) error {
	// vpcpeering.gcp.crossplane.io/v1beta1
	vpcpeerings := awsvpcpeeringv1alpha1.VPCPeeringConnectionList{}
	if err := cli.List(ctx, &vpcpeerings); err != nil {
		return err
	}

	for _, vpcpeering := range vpcpeerings.Items {
		ch <- prometheus.MustNewConstMetric(
			InfraResourcesAWSVPCPeeringConnectionInfo,
			prometheus.GaugeValue,
			1.0,
			vpcpeering.Name,
		)
	}

	return nil
}

func collectAWSDNSRecordMetrics(ctx context.Context, cli client.Client, ch chan<- prometheus.Metric) error {
	awsdnsrecords := awsdnsv1alpha1.ResourceRecordSetList{}
	if err := cli.List(ctx, &awsdnsrecords); err != nil {
		return err
	}

	for _, awsdnsrecord := range awsdnsrecords.Items {
		ch <- prometheus.MustNewConstMetric(
			InfraResourcesAWSDNSRecordInfo,
			prometheus.GaugeValue,
			1.0,
			awsdnsrecord.Name,
		)
	}

	return nil
}

func collectGCPDNSRecordMetrics(ctx context.Context, cli client.Client, ch chan<- prometheus.Metric) error {
	gcpdnsrecords := gcpdnsv1alpha1.ResourceRecordSetList{}
	if err := cli.List(ctx, &gcpdnsrecords); err != nil {
		return err
	}

	for _, gcpdnsrecord := range gcpdnsrecords.Items {
		ch <- prometheus.MustNewConstMetric(
			InfraResourcesGCPDNSRecordInfo,
			prometheus.GaugeValue,
			1.0,
			gcpdnsrecord.Name,
		)
	}

	return nil
}

func collectGCPIAMServiceaccountMetrics(ctx context.Context, cli client.Client, ch chan<- prometheus.Metric) error {
	gcpiamserviceaccounts := iamv1alpha1.ServiceAccountList{}
	if err := cli.List(ctx, &gcpiamserviceaccounts); err != nil {
		return err
	}

	for _, gcpiamserviceaccount := range gcpiamserviceaccounts.Items {
		ch <- prometheus.MustNewConstMetric(
			InfraResourcesGCPIAMServiceaccountInfo,
			prometheus.GaugeValue,
			1.0,
			gcpiamserviceaccount.Name,
		)
	}

	return nil
}
