package backup

import (
	"fmt"
	"time"

	"github.com/pingcap/dbaas/central/db"
	"github.com/pingcap/dbaas/central/db/entity"
	"github.com/pingcap/dbaas/central/metrics"
	"github.com/pingcap/log"
	"github.com/prometheus/client_golang/prometheus"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

const BackupDuration = 24 * time.Hour

var (
	backupExecutionStatusDurationSeconds = prometheus.NewDesc(
		prometheus.BuildFQName("dbaas", "backup", "execution_status_duration_seconds"),
		"The time taken and status of backup job execution",
		append(metrics.CommonLabels, "backup_id", "backup_name", "backup_type", "status", "create_at", "schedule_at", metrics.ProviderTypeLbl),
		nil)
)

func RegisterMetrics() {
	prometheus.MustRegister(NewMetricsCollector())
	prometheus.MustRegister(NewBackupStatusCollector())
}

type MetricsCollector struct{}

func NewMetricsCollector() prometheus.Collector {
	return &MetricsCollector{}
}

func (c *MetricsCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- backupExecutionStatusDurationSeconds
}

func (c *MetricsCollector) Collect(ch chan<- prometheus.Metric) {
	var start = time.Now()
	defer func() {
		log.Info("backup metrics collect time", zap.Int64("time", time.Since(start).Milliseconds()))
	}()
	var backups []entity.Backup
	err := db.Transaction(func(tx *gorm.DB) error {
		return entity.NewBackupQuerySet(tx.Preload("BackupSchedule.Cluster").
			Preload("BackupSchedule.Cluster.Tenant")).
			PreloadBackupSchedule().
			DeletedAtIsNull().
			CreatedAtGte(time.Now().Add(-24 * time.Hour)).
			All(&backups)
	})
	if err != nil {
		log.Error("Retrieve backup failed", zap.Error(err))
		return
	}
	for _, br := range backups {
		if br.BackupSchedule.Cluster.Tenant.Status != entity.TenantStatusActive {
			continue
		}
		clusterName := metrics.Unknown
		cluster := br.BackupSchedule.Cluster
		if cluster != nil {
			clusterName = cluster.Name
		}
		if cluster == nil {
			continue
		}
		// worker will create final backup for poc_deleted cluster
		if cluster.Status.DeletedOrCleared() {
			if !(cluster.Status == entity.ClusterStatusDeleted && cluster.FinalBackupBeforeDelete) {
				continue
			}
		}

		// unknown mean backup records created in database, but backup CR status not updated in database.
		status := metrics.Unknown
		if br.Status != "" {
			status = string(br.Status)
		}

		timeTaken := 0.0
		createAt := br.CreatedAt
		var scheduleTime time.Time
		finishedAt := time.Now()
		if br.FinishedAt != nil {
			finishedAt = *br.FinishedAt
		}
		if br.BackupSchedule.NextBackupTime != nil && br.BackupSchedule.Type == entity.BackupType_Auto {
			scheduleTime = br.BackupSchedule.NextBackupTime.Add(-BackupDuration)
			timeTaken = finishedAt.Sub(scheduleTime).Seconds()
		} else {
			timeTaken = finishedAt.Sub(createAt).Seconds()
		}

		ch <- prometheus.MustNewConstMetric(
			backupExecutionStatusDurationSeconds,
			prometheus.GaugeValue,
			timeTaken,
			fmt.Sprintf("%d", br.BackupSchedule.Cluster.Tenant.ID),
			br.BackupSchedule.Cluster.Tenant.Name,
			fmt.Sprintf("%d", br.BackupSchedule.ClusterID),
			clusterName,
			fmt.Sprintf("%d", br.ID),
			br.BackupSchedule.Name,
			string(br.BackupSchedule.Type),
			status,
			createAt.String(),
			scheduleTime.String(),
			metrics.GetProviderType(br.BackupSchedule.Cluster.Free),
		)
	}
}
