package gcpquota

import (
	"context"
	"sync"

	"github.com/go-logr/logr"
	"github.com/prometheus/client_golang/prometheus"

	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/quota-exporter/config"
	"github.com/tidbcloud/infra-provider/quota-exporter/exporter"
	"github.com/tidbcloud/infra-provider/quota-exporter/exporter/cache"
)

const (
	Name = "gcpQuota"
)

// gcpAccount is the internal representation of a GCP project read from the
// static config.
type gcpAccount struct {
	ProjectID string
}

type GCPQuotaExporter struct {
	cfg    *config.GCPQuotaExporterConfig
	logger logr.Logger
	client QuotaClient
}

func NewExporter(cfg config.ExporterConfig, deps *exporter.Dependency) (exporter.Exporter, error) {
	myCfg := &config.GCPQuotaExporterConfig{}
	if cfg.GCPQuotaExporterConfig != nil {
		myCfg = cfg.GCPQuotaExporterConfig
	}
	err := myCfg.Complete()
	if err != nil {
		return nil, err
	}

	logger := deps.Logger.WithName(Name)
	e := &GCPQuotaExporter{
		cfg:    myCfg,
		logger: logger,
		client: &quotaClient{
			logger:  logger,
			service: deps.ComputeService,
		},
	}

	if e.cfg.Cache != nil {
		return cache.NewAndRun(context.TODO(), e, e.cfg.Cache, e.logger)
	}
	return e, nil
}

func (e *GCPQuotaExporter) Name() string {
	return Name
}

func (e *GCPQuotaExporter) MetricPath() string {
	return e.cfg.MetricPath
}

func (e *GCPQuotaExporter) Describe(ch chan<- *prometheus.Desc) {
	ch <- descForQuotaUsage()
	ch <- descForQuotaLimit()
}

func (e *GCPQuotaExporter) Collect(ch chan<- prometheus.Metric) {
	accounts := make([]gcpAccount, 0, len(e.cfg.GCPAccounts))
	for _, acct := range e.cfg.GCPAccounts {
		accounts = append(accounts, gcpAccount{
			ProjectID: acct.ProjectID,
		})
	}

	e.logger.V(4).Info("collect quota for accounts", "accounts",
		util.MapSlice(accounts, func(a gcpAccount) string { return a.ProjectID }))

	wg := sync.WaitGroup{}
	wg.Add(len(accounts))

	for i := range accounts {
		account := accounts[i]
		go func() {
			e.collectOneAccount(ch, account)
			wg.Done()
		}()
	}

	wg.Wait()

}

func (e *GCPQuotaExporter) collectOneAccount(ch chan<- prometheus.Metric, account gcpAccount) {
	projectID := account.ProjectID

	quotas, err := e.client.QueryQuotas(context.TODO(), projectID)
	if err != nil {
		e.logger.Error(err, "failed to query quotas", "project", projectID)
		return
	}

	for _, quota := range quotas {
		ch <- metricForQuotaUsage(quota, projectID)
		ch <- metricForQuotaLimit(quota, projectID)
	}
}

func descForQuotaUsage() *prometheus.Desc {
	fqName := prometheus.BuildFQName("gcpproject", "quota", "usage")
	help := "Current used amount of the given resource."
	varLabels := []string{"gcp_project", "region", "metric"}
	constLabels := prometheus.Labels{}
	return prometheus.NewDesc(fqName, help, varLabels, constLabels)
}

func descForQuotaLimit() *prometheus.Desc {
	fqName := prometheus.BuildFQName("gcpproject", "quota", "limit")
	help := "Current limit of the given resource."
	varLabels := []string{"gcp_project", "region", "metric"}
	constLabels := prometheus.Labels{}
	return prometheus.NewDesc(fqName, help, varLabels, constLabels)
}

func metricForQuotaUsage(quota Quota, project string) prometheus.Metric {
	return prometheus.MustNewConstMetric(
		descForQuotaUsage(), prometheus.GaugeValue, quota.Usage,
		project, quota.Region, quota.Metric,
	)
}

func metricForQuotaLimit(quota Quota, project string) prometheus.Metric {
	return prometheus.MustNewConstMetric(
		descForQuotaLimit(), prometheus.GaugeValue, quota.Limit,
		project, quota.Region, quota.Metric,
	)
}
