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

var backupStatusCount = prometheus.NewDesc(
	prometheus.BuildFQName("dbaas", "backup", "status_count"),
	"The execution status count of backup",
	append(metrics.CommonLabels, metrics.ProviderTypeLbl, "backup_type", "status", "tenant_plan"),
	nil)

const expireTime = 8 * time.Hour

type StatusCollector struct{}

func NewBackupStatusCollector() prometheus.Collector {
	return &StatusCollector{}
}

func (c *StatusCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- backupStatusCount
}

type BackupObject struct {
	clusterName string
	tenantID    entity.ID
	tenantName  string
	devTier     bool
	succNum     float64
	failNum     float64
	tenantPlan  string
}

func (c *StatusCollector) Collect(ch chan<- prometheus.Metric) {
	var backups []entity.Backup
	todayTime := time.Now().Truncate(24 * time.Hour)
	err := db.Transaction(func(tx *gorm.DB) error {
		return entity.NewBackupQuerySet(tx.Preload("BackupSchedule.Cluster").Preload("BackupSchedule.Cluster.Tenant")).
			PreloadBackupSchedule().
			CreatedAtGte(todayTime).
			All(&backups)
	})
	if err != nil {
		log.Error("Retrieve backup failed", zap.Error(err))
		return
	}
	backupAutoMap := make(map[entity.ID]*BackupObject)
	backupManualMap := make(map[entity.ID]*BackupObject)
	backupMap := make(map[entity.BackupType]map[entity.ID]*BackupObject)
	backupMap[entity.BackupType_Auto] = backupAutoMap
	backupMap[entity.BackupType_Manual] = backupManualMap
	for _, br := range backups {
		if br.BackupSchedule.Cluster.Tenant.Kind != entity.TenantKindExternal {
			continue
		}
		if br.BackupSchedule.Type != entity.BackupType_Manual && br.BackupSchedule.Type != entity.BackupType_Auto {
			continue
		}
		if _, ok := backupMap[br.BackupSchedule.Type][br.BackupSchedule.ClusterID]; !ok {
			backupMap[br.BackupSchedule.Type][br.BackupSchedule.ClusterID] = &BackupObject{
				clusterName: br.BackupSchedule.Cluster.Name,
				tenantID:    br.BackupSchedule.Cluster.TenantID,
				tenantName:  br.BackupSchedule.Cluster.Tenant.Name,
				devTier:     br.BackupSchedule.Cluster.Free,
				succNum:     0,
				failNum:     0,
				tenantPlan:  string(br.BackupSchedule.Cluster.Tenant.Plan),
			}
		}
		var usedTime time.Duration = 0
		if br.Status == entity.BackupStatus_SUCCESS && br.FinishedAt != nil {
			usedTime = br.FinishedAt.Sub(br.CreatedAt)
		} else if br.Status != entity.BackupStatus_SUCCESS {
			usedTime = time.Since(br.CreatedAt)
		}
		if usedTime > expireTime {
			backupMap[br.BackupSchedule.Type][br.BackupSchedule.ClusterID].failNum++
			continue
		}
		if br.Status == entity.BackupStatus_SUCCESS {
			backupMap[br.BackupSchedule.Type][br.BackupSchedule.ClusterID].succNum++
		} else if br.Status == entity.BackupStatus_FAILED {
			backupMap[br.BackupSchedule.Type][br.BackupSchedule.ClusterID].failNum++
		}
	}

	for backupType, backupList := range backupMap {
		for id, object := range backupList {
			ch <- prometheus.MustNewConstMetric(
				backupStatusCount,
				prometheus.CounterValue,
				object.succNum,
				fmt.Sprintf("%d", object.tenantID),
				object.tenantName,
				fmt.Sprintf("%d", id),
				object.clusterName,
				metrics.GetProviderType(object.devTier),
				string(backupType),
				string(entity.BackupStatus_SUCCESS),
				object.tenantPlan,
			)

			ch <- prometheus.MustNewConstMetric(
				backupStatusCount,
				prometheus.CounterValue,
				object.failNum,
				fmt.Sprintf("%d", object.tenantID),
				object.tenantName,
				fmt.Sprintf("%d", id),
				object.clusterName,
				metrics.GetProviderType(object.devTier),
				string(backupType),
				string(entity.BackupStatus_FAILED),
				object.tenantPlan,
			)
		}
	}
}
