package restore

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 restoreExecutionStatusDurationSeconds = prometheus.NewDesc(
	prometheus.BuildFQName(metrics.DataflowNamespace, "restore", "execution_status_duration_seconds"),
	"The time taken and status of restore job execution",
	append(metrics.CommonIDLabels, "restore_id", "restore_name", "status"),
	nil)

type RestoreStatusCollector struct{}

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

func (c *RestoreStatusCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- restoreExecutionStatusDurationSeconds
}

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

	restores, err := c.queryRestores()
	if err != nil {
		logger.Error("Retrieve restore failed", zap.Error(err))
		return
	}
	if len(restores) == 0 {
		return
	}

	for _, restore := range restores {
		// unknown mean restore records created in database, but restore CR status not updated in database.
		tenantID, err := c.queryTenantID(&restore)
		if err != nil {
			logger.Error("Query tenant id failed", zap.Error(err))
			continue
		}

		status := metrics.Unknown
		if restore.Status != "" {
			status = string(restore.Status)
		}
		timeTaken := time.Since(restore.CreatedAt).Seconds()

		ch <- prometheus.MustNewConstMetric(
			restoreExecutionStatusDurationSeconds,
			prometheus.GaugeValue,
			timeTaken,
			fmt.Sprintf("%d", tenantID),
			fmt.Sprintf("%d", restore.ClusterID),
			fmt.Sprintf("%d", restore.ID),
			restore.Name,
			status,
		)
	}
}

func (c *RestoreStatusCollector) queryRestores() ([]entity.Restore, error) {
	// Retrieve the last day backup records
	// filter cluster_id is empty, if cluster_id is empty, restore.Cluster will panic.
	var restores []entity.Restore
	err := db.Transaction(func(tx *gorm.DB) error {
		return entity.NewRestoreQuerySet(tx).
			ClusterIDNe(0).
			CreatedAtGte(time.Now().Add(-24 * time.Hour)).
			All(&restores)
	})
	if err != nil {
		return nil, err
	}
	return restores, nil
}

func (c *RestoreStatusCollector) queryTenantID(restore *entity.Restore) (uint64, error) {
	if restore.BackupID != 0 {
		var backup entity.Backup
		err := db.Transaction(func(tx *gorm.DB) error {
			return entity.NewBackupQuerySet(tx).IDEq(restore.BackupID).One(&backup)
		})
		if err != nil {
			return 0, err
		}
		return backup.TenantID.Uint64(), nil
	}

	if restore.LogBackupID != 0 {
		var logBackup entity.LogBackup
		err := db.Transaction(func(tx *gorm.DB) error {
			return entity.NewLogBackupQuerySet(tx).IDEq(restore.LogBackupID).One(&logBackup)
		})
		if err != nil {
			return 0, err
		}
		return logBackup.TenantID.Uint64(), nil
	}

	return 0, fmt.Errorf("restore %d has no backup or log backup", restore.ID)
}
