package backup

import (
	"context"
	"fmt"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"go.uber.org/zap"
	"gorm.io/gorm"

	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	"github.com/tidbcloud/dataflow-service/internal/db"
	"github.com/tidbcloud/dataflow-service/internal/metrics"
	"github.com/tidbcloud/dataflow-service/pkg/log"
)

const BackupDuration = 24 * time.Hour

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

type BackupStatusCollector struct{}

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

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

func (c *BackupStatusCollector) Collect(ch chan<- prometheus.Metric) {
	logger := log.GetLogger(context.Background())
	start := time.Now()
	defer func() {
		logger.Info("backup status collect time", zap.Int64("time", time.Since(start).Milliseconds()))
	}()

	backups, err := c.queryBackups()
	if err != nil {
		logger.Error("Retrieve backup failed", zap.Error(err))
		return
	}
	if len(backups) == 0 {
		return
	}

	for _, br := range backups {
		status := metrics.Unknown
		if br.Status != "" {
			status = string(br.Status)
		}

		var scheduleTime time.Time
		if br.BackupSchedule.NextBackupTime != nil && br.BackupSchedule.Type == entity.BackupTypeAuto {
			scheduleTime = br.BackupSchedule.NextBackupTime.Add(-BackupDuration)
		}

		timeTaken := c.calculateTimeTaken(&br)

		ch <- prometheus.MustNewConstMetric(
			backupExecutionStatusDurationSeconds,
			prometheus.GaugeValue,
			timeTaken,
			fmt.Sprintf("%d", br.TenantID),
			fmt.Sprintf("%d", br.ClusterID),
			fmt.Sprintf("%d", br.ID),
			br.BackupSchedule.Name,
			string(br.BackupSchedule.Type),
			status,
			br.CreatedAt.String(),
			scheduleTime.String(),
			metrics.Dedicate,
		)
	}
}

func (c *BackupStatusCollector) queryBackups() ([]entity.Backup, error) {
	var backups []entity.Backup
	err := db.Transaction(func(tx *gorm.DB) error {
		return entity.NewBackupQuerySet(tx).
			PreloadBackupSchedule().
			DeletedAtIsNull().
			CreatedAtGte(time.Now().Add(-24 * time.Hour)).
			All(&backups)
	})
	if err != nil {
		return nil, err
	}
	return backups, nil
}

func (c *BackupStatusCollector) calculateTimeTaken(br *entity.Backup) float64 {
	// unknown mean backup records created in database, but backup CR status not updated in database.
	createAt := br.CreatedAt
	finishedAt := time.Now()
	timeTaken := 0.0
	if br.BackupSchedule.NextBackupTime != nil && br.BackupSchedule.Type == entity.BackupTypeAuto {
		scheduleTime := br.BackupSchedule.NextBackupTime.Add(-BackupDuration)
		timeTaken = finishedAt.Sub(scheduleTime).Seconds()
	} else {
		timeTaken = finishedAt.Sub(createAt).Seconds()
	}
	return timeTaken
}
