package vercel

import (
	"context"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/tidbcloud/ecosystem-service/internal/service/bigquery"
	"github.com/tidbcloud/ecosystem-service/internal/service/grpc/dataapi"
	vercelDB "github.com/tidbcloud/ecosystem-service/internal/storage/db/vercel"
	"github.com/tidbcloud/ecosystem-service/internal/utils"
	"github.com/tidbcloud/pkgv2/logid"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

const (
	connectionsGaugeName = "tidb_vercel_connections_gauge"
)

var (
	connectionsGauge *prometheus.GaugeVec
)

type AutoCleanJob struct {
	dataAppConnectionDAO vercelDB.DataAppConnectionDAO
	dataApiSvc           dataapi.DataApiSvc
	composeEntityDAO     vercelDB.ComposeEntityDAO
	dataAppLogic         *DataAppConnLogic
}

func NewAutoCleanJob(
	composeEntityDAO vercelDB.ComposeEntityDAO,
	dataAppConnectionDAO vercelDB.DataAppConnectionDAO,
	dataApiSvc dataapi.DataApiSvc,
	dataAppLogic *DataAppConnLogic,
) *AutoCleanJob {
	return &AutoCleanJob{
		composeEntityDAO:     composeEntityDAO,
		dataAppConnectionDAO: dataAppConnectionDAO,
		dataApiSvc:           dataApiSvc,
		dataAppLogic:         dataAppLogic,
	}
}

func (j *AutoCleanJob) AutoCleanConnections() {
	ctx := context.Background()
	logID := logid.GenLogid()
	ctx = logid.WithLogid(ctx, logID)
	tilog.Info(ctx, "Start to clean connections")

	j.cleanDataAppConnections(ctx)
}

func (j *AutoCleanJob) cleanDataAppConnections(ctx context.Context) {
	dataAppIDs, err := j.dataAppConnectionDAO.ListAllDataAppIDs(ctx)
	if err != nil {
		tilog.Error(ctx, "failed to list all data app ids", zap.Error(err))
		return
	}
	if len(dataAppIDs) == 0 {
		tilog.Warn(ctx, "No data apps found")
		return
	}

	var successConnectionIDs []uint64
	for _, dataAppID := range dataAppIDs {
		dataApp, err := j.dataApiSvc.ShowDataApp(ctx, dataAppID)
		if err != nil {
			tilog.Error(ctx, "failed to get data app", zap.Error(err), zap.Uint64("data_app_id", dataAppID))
			continue
		}
		if dataApp == nil {
			conns, err := j.dataAppConnectionDAO.ListVercelConnectionsByDataAppIDs(ctx, []uint64{dataAppID})
			if err != nil {
				tilog.Error(ctx, "failed to list connections", zap.Error(err), zap.Uint64("data_app_id", dataAppID))
				continue
			}
			for _, conn := range conns {
				err := j.dataAppLogic.DeleteEnvsByEnvIdentifier(ctx, conn.VercelProjectID, utils.GetTeamIDString(conn.VercelIntegrationConfiguration.TeamID),
					conn.DataAppEnvironmentIdentifier, conn.VercelIntegrationConfiguration.ApiAccessToken)
				if err != nil {
					tilog.Error(ctx, "failed to delete env", zap.Error(err), zap.Uint64("data_app_id", dataAppID), zap.Uint64("connection_id", conn.ID))
					continue
				}
				err = j.composeEntityDAO.DeleteDataAppConnection(ctx, conn.ID, conn.VercelProjectID)
				if err != nil {
					tilog.Error(ctx, "failed to delete connection", zap.Error(err), zap.Uint64("data_app_id", dataAppID), zap.Uint64("connection_id", conn.ID))
					continue
				}
				successConnectionIDs = append(successConnectionIDs, conn.ID)
			}
		}
	}

	tilog.Info(ctx, "Success to delete connections", zap.Any("successConnectionIDs", successConnectionIDs))
}

type VercelMetricsJob struct {
	db *gorm.DB
}

func NewVercelMetricsJob(db *gorm.DB) *VercelMetricsJob {
	connectionsGauge = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Name: connectionsGaugeName,
			Help: "record the total number of connections in db",
		},
		[]string{"connection_type"},
	)

	prometheus.MustRegister(connectionsGauge)

	return &VercelMetricsJob{
		db: db,
	}
}

func (j *VercelMetricsJob) CollectMetrics() {
	ctx := context.Background()
	logID := logid.GenLogid()
	ctx = logid.WithLogid(ctx, logID)
	var connCount int64
	err := j.db.Table("cluster_connections").Count(&connCount).Error
	if err != nil {
		tilog.Error(ctx, "failed to count cluster connections", zap.Error(err))
		return
	}
	connectionsGauge.WithLabelValues("cluster").Set(float64(connCount))

	err = j.db.Table("data_app_connections").Count(&connCount).Error
	if err != nil {
		tilog.Error(ctx, "failed to count dataApp connections", zap.Error(err))
		return
	}
	connectionsGauge.WithLabelValues("dataApp").Set(float64(connCount))
}

type DailyUploadJob struct {
	connectionDAO vercelDB.ClusterConnectionDAO
	uploader      *bigquery.ConnectionUploader
}

func NewDailyUploadJob(connectionDAO vercelDB.ClusterConnectionDAO, uploader *bigquery.ConnectionUploader) *DailyUploadJob {
	return &DailyUploadJob{
		connectionDAO: connectionDAO,
		uploader:      uploader,
	}
}

func (j *DailyUploadJob) UploadToBigquery() {
	ctx := context.Background()
	logID := logid.GenLogid()
	ctx = logid.WithLogid(ctx, logID)
	tilog.Info(ctx, "Start to upload to bigquery")

	connections, err := j.connectionDAO.ListVercelConnections(ctx)
	if err != nil {
		tilog.Error(ctx, "failed to list connections", zap.Error(err))
		return
	}

	cons := make([]*bigquery.TiDBVercelConnection, 0, len(connections))
	for _, connection := range connections {
		cons = append(cons, &bigquery.TiDBVercelConnection{
			ID:                               connection.ID,
			CreatedAt:                        connection.CreatedAt,
			UpdatedAt:                        connection.UpdatedAt,
			VercelProjectID:                  connection.VercelProjectID,
			TiDBOrgID:                        connection.TiDBOrgID,
			TiDBProjectID:                    connection.TiDBProjectID,
			TiDBClusterID:                    connection.TiDBClusterID,
			FrameworkType:                    connection.FrameworkType,
			VercelIntegrationConfigurationID: connection.VercelIntegrationConfigurationID,
		})
	}

	err = j.uploader.UploadConnections(ctx, cons)
	if err != nil {
		tilog.Error(ctx, "failed to upload connections", zap.Error(err))
		return
	}

	tilog.Info(ctx, "Upload to bigquery finished", zap.Int("total", len(connections)))
}
