package rest

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

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/central/db"
	"github.com/pingcap/dbaas/central/db/dbutil"
	"github.com/pingcap/dbaas/central/db/entity"
	"github.com/pingcap/dbaas/central/model/apikey"
	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/metrics/internal/service/query"
	"gorm.io/gorm"
)

const (
//contextApiKey     = "api_key"
//contextMetricsKey = "metrics_key"
)

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) {
		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()
		cluster, _, err := getClusterAndRegion(tenantID, req.ClusterID)
		if err != nil {
			fail(c, err.Error(), http.StatusNotFound)
			return
		}

		data, err := service.QueryMetrics(cluster.ProjectID.Uint64(), cluster, cluster.Provider.Name, false)
		if err != nil {
			fail(c, err.Error(), http.StatusInternalServerError)
			return
		}

		c.Data(http.StatusOK, "text/plain; version=0.0.4", data)
	}
}

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

func getClusterAndRegion(tenantID, clusterID uint64) (*entity.Cluster, *entity.Region, error) {
	var (
		cluster entity.Cluster
		region  *entity.Region
		err     error
	)
	orgID := entity.ID(tenantID)
	err = db.Transaction(func(tx *gorm.DB) error {
		var err error
		cluster, err = dbutil.GetCluster(tx, entity.ID(clusterID))
		if err != nil {
			return err
		}
		if cluster.TenantID != orgID {
			return errcode.NewForbiddenErr(fmt.Errorf("cluster %d does not belong to tenant %d", clusterID, orgID))
		}

		region, err = dbutil.GetClusterRegion(context.Background(), tx, cluster)
		if err != nil {
			return errcode.NewInternalErr(errors.Errorf("cannot get cluster region for %s", cluster.Name))
		}
		return nil
	})

	if err != nil {
		return nil, nil, err
	}

	return &cluster, region, 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
		}

		var key *entity.MetricsKey

		err = db.Transaction(func(tx *gorm.DB) error {
			key, err = dbutil.GetMetricsKeyByKey(tx, token)
			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
		}

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

		projectID := key.Project.ID.Uint64()
		if projectID != req.ProjectID {
			fail(c, "Bad project id", http.StatusBadRequest)
			return
		}

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

		c.Data(http.StatusOK, "text/plain; version=0.0.4", data)
	}
}
