package backup

import (
	"context"
	"fmt"
	"time"

	"go.uber.org/zap"
	"gorm.io/gorm"

	"github.com/prometheus/client_golang/prometheus"
	"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"
)

var logBackupTruncateStatusDurationSeconds = prometheus.NewDesc(
	prometheus.BuildFQName(metrics.DataflowNamespace, "log_backup", "truncate_status_duration_seconds"),
	"The time taken and status of log backup truncate job",
	append(metrics.CommonIDLabels, "backup_id", "backup_name", "truncate_id", "status", "create_at"),
	nil)

type LogBackupTruncateMetricsCollector struct{}

func NewLogBackupTruncateMetricsCollector() prometheus.Collector {
	return &LogBackupTruncateMetricsCollector{}
}

func (c *LogBackupTruncateMetricsCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- logBackupTruncateStatusDurationSeconds
}

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

	truncates, err := c.queryLogBackupTruncates()
	if err != nil {
		logger.Error("Retrieve log backup truncate failed", zap.Error(err))
		return
	}

	for _, truncate := range truncates {
		if truncate.Status != entity.LogBackupTruncateStatusRunning || truncate.RunningAt == nil {
			continue
		}

		br := truncate.LogBackup
		timeTaken := time.Since(*truncate.RunningAt).Seconds()

		ch <- prometheus.MustNewConstMetric(
			logBackupTruncateStatusDurationSeconds,
			prometheus.GaugeValue,
			timeTaken,
			fmt.Sprintf("%d", br.TenantID),
			fmt.Sprintf("%d", br.ClusterID),
			fmt.Sprintf("%d", br.ID),
			br.Name,
			fmt.Sprintf("%d", truncate.ID),
			string(truncate.Status),
			truncate.CreatedAt.String(),
		)
	}
}

func (c *LogBackupTruncateMetricsCollector) queryLogBackupTruncates() ([]entity.LogBackupTruncate, error) {
	var truncates []entity.LogBackupTruncate
	err := db.Transaction(func(tx *gorm.DB) error {
		// just monitor running, because status will be running when call infra to truncate
		return entity.NewLogBackupTruncateQuerySet(tx.Preload("LogBackup.BackupSchedule")).
			DeletedAtIsNull().
			OrderAscByCreatedAt().
			StatusIn(entity.LogBackupTruncateStatusRunning).
			PreloadLogBackup().
			All(&truncates)
	})
	if err != nil {
		return nil, err
	}

	// remove backup schedule is nil
	tempTruncates := make([]entity.LogBackupTruncate, 0, len(truncates))
	for _, truncate := range truncates {
		if truncate.LogBackup != nil && truncate.LogBackup.BackupSchedule != nil {
			tempTruncates = append(tempTruncates, truncate)
		}
	}
	return tempTruncates, nil
}
