package rest

import (
	"context"
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"github.com/tidbcloud/metrics-server/central/db"
	"github.com/tidbcloud/metrics-server/central/db/dbutil"
	"github.com/tidbcloud/metrics-server/central/db/entity"
	"github.com/tidbcloud/metrics-server/central/model/apikey"
	"github.com/tidbcloud/metrics-server/central/monitor_data_migration"
	"github.com/tidbcloud/metrics-server/central/utils"
	"github.com/tidbcloud/metrics-server/idl/pbgen/proto/core"
	"github.com/tidbcloud/metrics-server/internal/grpc/grpcli"
	"github.com/tidbcloud/metrics-server/internal/service/query"
	"github.com/tidbcloud/pkgv2/bizerr"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

var (
	//errAPIKeyNotFound = errors.NotFoundf("api key not found")
	errTokenNotFound = errors.NotFoundf("token not found")
	errInvalidToken  = errors.BadRequestf("invalid token")
)

type GetMetricsReq struct {
	ClusterID uint64 `uri:"cluster_id" binding:"required"`
}

func BuildMetricsHandler(service *query.Manager) gin.HandlerFunc {
	return func(c *gin.Context) {
		forwardedFor := c.Request.Header.Values("X-Forwarded-For")
		log.Info("BuildMetricsHandler request info", zap.String("requestUri", c.Request.RequestURI), zap.Any("forwardedFor", forwardedFor))

		token, err := getToken(c)
		if err != nil {
			fail(c, err.Error(), http.StatusUnauthorized)
			c.Abort()
			return
		}

		key, err := apikey.AuthenticateAPIKey(token)
		if err != nil {
			fail(c, err.Error(), http.StatusInternalServerError)
			c.Abort()
			return
		}
		if key == nil || key.ID == 0 {
			fail(c, errInvalidToken.Error(), http.StatusUnauthorized)
			c.Abort()
			return
		}

		req := GetMetricsReq{}
		if err := c.BindUri(&req); err != nil {
			fail(c, err.Error(), http.StatusBadRequest)
			return
		}
		tenantID := key.TenantID.Uint64()
		ctx := context.Background()
		cluster, err := getClusterInfo(ctx, req.ClusterID, tenantID)
		if err != nil {
			fail(c, err.Error(), http.StatusNotFound)
			return
		}

		ctx = monitor_data_migration.SetProjectMigrationStatusToContextWithProjectID(ctx, entity.ID(cluster.ProjectId))
		data, err := service.QueryMetrics(ctx, cluster.ProjectId, cluster, false)
		if err != nil {
			fail(c, err.Error(), http.StatusInternalServerError)
			return
		}

		log.Debug("BuildMetricsHandler", zap.String("requestUri", c.Request.RequestURI), zap.ByteString("response", data))
		c.Data(http.StatusOK, "text/plain; version=0.0.4", data)
	}
}

func PingHandler(c *gin.Context) {
	success(c, "pong", http.StatusOK)
}

func getClusterInfo(ctx context.Context, clusterID, tenantID uint64) (*core.Cluster, error) {
	resp, err := grpcli.ListClusters(ctx, &core.ListClustersReq{
		Filter: &core.ClusterFilter{
			ClusterIds: []uint64{clusterID},
			TenantIds:  []uint64{tenantID},
			Deleted:    utils.BoolPtr(false),
			IsDevTier:  utils.BoolPtr(false),
		},
	})
	if err != nil {
		return nil, fmt.Errorf("get info of cluster %d failed: %w", clusterID, err)
	}
	cluster, ok := resp.Clusters[clusterID]
	if !ok {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "cluster is not found,id=%d", clusterID)
	}
	if cluster.IsDevTier {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "serverless clusters is not supported,id=%d", clusterID)
	}
	return cluster, nil
}

func success(c *gin.Context, result interface{}, code int) {
	c.JSON(code, result)
}

func fail(c *gin.Context, msg string, code int) {
	c.JSON(code, gin.H{"code": code, "message": msg})
}

type GetProjectMetricsReq struct {
	ProjectID uint64 `uri:"project_id" binding:"required"`
}

func HandleProjectMetrics(service *query.Manager) gin.HandlerFunc {
	return func(c *gin.Context) {
		token, err := getToken(c)
		if err != nil {
			fail(c, err.Error(), http.StatusUnauthorized)
			c.Abort()
			return
		}

		req := GetProjectMetricsReq{}
		if err := c.BindUri(&req); err != nil {
			fail(c, err.Error(), http.StatusBadRequest)
			return
		}
		projectID := req.ProjectID

		var key *entity.MetricsKey
		err = db.Transaction(func(tx *gorm.DB) error {
			key, err = dbutil.GetMetricsKeyByKey(tx, token, entity.ID(projectID))
			return err
		})
		if err != nil && err != gorm.ErrRecordNotFound {
			fail(c, err.Error(), http.StatusInternalServerError)
			c.Abort()
			return
		}
		if key == nil || key.ID == 0 {
			fail(c, errInvalidToken.Error(), http.StatusUnauthorized)
			c.Abort()
			return
		}

		ctx := context.Background()
		ctx = monitor_data_migration.SetProjectMigrationStatusToContextWithProjectID(ctx, entity.ID(projectID))

		data, err := service.QueryProjectMetrics(ctx, projectID)
		if err != nil {
			fail(c, err.Error(), http.StatusInternalServerError)
			return
		}

		log.Debug("HandleProjectMetrics", zap.String("requestUri", c.Request.RequestURI), zap.ByteString("response", data))
		c.Data(http.StatusOK, "text/plain; version=0.0.4", data)
	}
}
