package vercel

import (
	"errors"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/ecosystem-service/api/auth"
	vercelDB "github.com/tidbcloud/ecosystem-service/internal/storage/db/vercel"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/tracing"
	mgmtapi "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.opentelemetry.io/otel/attribute"
	"go.uber.org/zap"
)

// AddClusterConnection godoc
//
//	@Description	Add connection between Vercel project and TiDB cluster
//	@Accept			json
//	@Produce		json
//	@Param			org_id			path		string					true	"TiDB Org ID"
//	@Param			project_id		path		string					true	"TiDB Project ID"
//	@Param			cluster_id		path		string					true	"TiDB Cluster ID"
//	@Param			vercel_project	body		AddConnectionRequest	true	"Vercel project information and integration configuration id"
//	@Success		200				{object}	MessageResp				"ok"
//	@Failure		400				{object}	BaseResp				"error"
//	@Failure		500				{object}	BaseResp				"error"
//	@Router			/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/vercel/connections [post]
func (c *VercelController) AddClusterConnection(ctx *gin.Context) {
	ctxReq := ctx.Request.Context()
	var err error
	ctxReq, span := Tracer.Start(ctxReq, "vercel.AddClusterConnection")
	defer func() {
		tracing.EndSpan(span, err)
	}()
	logger := tilog.WithContextL(ctxReq).With(zap.String("method", "vercel.AddClusterConnection"))

	var path TiDBCluster
	if err = ctx.ShouldBindUri(&path); err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Missing required TiDB cluster information"))
		logger.Error("Missing required TiDB cluster information", zap.Error(err))
		return
	}
	orgID, projectID, clusterID, err := parseUintIDForTiDBCluster(path)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Invalid TiDB cluster identifier, all types should be uint64"))
		logger.Error("Invalid TiDB cluster id", zap.Error(err))
		return
	}

	user, err := auth.GetUserFromContext(ctx)
	if err != nil {
		ctx.JSON(http.StatusUnauthorized, NewBaseResp(errs.ErrCodeAuth, "Invalid user"))
		logger.Error("Can't get user from context", zap.Error(err))
		return
	}

	req := AddConnectionRequest{
		Database:      DefaultDatabase,
		BranchEnabled: false,
	}
	if err = ctx.ShouldBindJSON(&req); err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Missing required Vercel project information"))
		logger.Error("Missing required Vercel project information", zap.Error(err))
		return
	}

	span.SetAttributes(attribute.String("tidb_org_id", strconv.FormatUint(orgID, 10)), attribute.String("tidb_project_id", strconv.FormatUint(projectID, 10)), attribute.String("tidb_cluster_id", strconv.FormatUint(clusterID, 10)), attribute.String("vercel_project_name", req.VercelProjectName), attribute.String("user_email", user.Email), attribute.String("database", req.Database), attribute.String("branch_enabled", strconv.FormatBool(req.BranchEnabled)))
	logger = logger.With(zap.Uint64("TiDBOrgID", orgID), zap.Uint64("TiDBProjectID", projectID), zap.Uint64("TiDBClusterID", clusterID), zap.String("VercelProjectID", req.VercelProjectID), zap.String("VercelProjectName", req.VercelProjectName), zap.String("UserEmail", user.Email), zap.String("Database", req.Database), zap.Bool("BranchEnabled", req.BranchEnabled))
	logger.Info("Start to add Vercel connection")

	span.AddEvent("check if connection exists")
	exist, err := c.clusterLogic.CheckConnIfExists(ctxReq, clusterID, req.VercelProjectID)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeInternalServerError, "Can't check if connection exists"))
		logger.Error("Check connection if exist failed", zap.Error(err))
		return
	}
	if exist {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeConflict, "Connection already exists"))
		logger.Error("Connection already exists")
		return
	}

	span.AddEvent("check if Vercel project has been configured")
	configured, err := c.clusterLogic.CheckVercelProjectIfConfigured(ctxReq, req.VercelProjectID)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeInternalServerError, "Can't check if Vercel project has been configured"))
		logger.Error("Check if Vercel project has been configured failed", zap.Error(err))
		return
	}
	if configured {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeConflict, "Vercel project has already been configured"))
		logger.Error("Vercel project has already been configured")
		return
	}

	span.AddEvent("validate TiDB cluster")
	cluster, err := c.clusterLogic.mgmtSvc.GetClusterIncludeDedicated(ctx, clusterID, mgmtapi.ClusterFilter_CLUSTER_VIEW_BASIC)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeInternalServerError, "Can't get TiDB cluster"))
		logger.Error("Get TiDB cluster failed", zap.Error(err))
		return
	}
	err = c.clusterLogic.validateCluster(cluster, orgID, projectID, clusterID)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Invalid TiDB cluster"))
		logger.Error("Invalid TiDB cluster", zap.Error(err))
		return
	}

	span.AddEvent("configure TiDB cluster")
	envs, err := c.clusterLogic.ConfigureTiDBCluster(ctxReq, user, cluster, req, logger)
	if err != nil {
		if errors.Is(err, &PublicEndpointDisableError{}) {
			ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeConflict, err.Error()))
		} else if errors.Is(err, &PublicEndpointNotAllowAllError{}) {
			ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeConflict, err.Error()))
		} else {
			ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeInternalServerError, "Configure TiDB cluster failed"))
		}
		logger.Error("Configure TiDB cluster failed", zap.Error(err))
		return
	}

	connection := vercelDB.ClusterConnection{
		VercelProjectID:                  req.VercelProjectID,
		TiDBOrgID:                        orgID,
		TiDBProjectID:                    projectID,
		TiDBClusterID:                    clusterID,
		VercelIntegrationConfigurationID: req.ConfigurationID,
		FrameworkType:                    req.FrameworkType,
		Database:                         req.Database,
		BranchEnabled:                    req.BranchEnabled,
	}

	span.AddEvent("add connection")
	err = c.clusterLogic.AddClusterConnection(ctxReq, req.VercelProjectID, req.ConfigurationID, req.FrameworkType, envs, &connection)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeInternalServerError, "Failed to create environment variables to Vercel project"))
		logger.Error("Failed to create environment variables to Vercel project", zap.Error(err))
		return
	}

	message := "Add connection between Vercel project and TiDB cluster successfully."
	logger.Info(message)
	ctx.JSON(200, MessageResp{
		Message: message,
	})
}

// DeleteClusterConnection godoc
//
//	@Description	Delete connection between Vercel project and TiDB cluster
//	@Accept			json
//	@Produce		json
//	@Param			org_id			path		string			true	"TiDB Org ID"
//	@Param			project_id		path		string			true	"TiDB Project ID"
//	@Param			cluster_id		path		string			true	"TiDB Cluster ID"
//	@Param			vercel_project	query		VercelProject	true	"Vercel poject information"
//	@Success		200				{object}	MessageResp		"ok"
//	@Failure		400				{object}	BaseResp		"error"
//	@Failure		500				{object}	BaseResp		"error"
//	@Router			/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/vercel/connections [delete]
func (c *VercelController) DeleteClusterConnection(ctx *gin.Context) {
	ctxReq := ctx.Request.Context()
	var err error
	ctxReq, span := Tracer.Start(ctxReq, "vercel.DeleteClusterConnection")
	defer func() {
		tracing.EndSpan(span, err)
	}()
	logger := tilog.WithContextL(ctxReq).With(zap.String("method", "vercel.DeleteClusterConnection"))

	var path TiDBCluster
	if err = ctx.ShouldBindUri(&path); err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Missing required TiDB cluster information"))
		logger.Error("Missing required TiDB cluster information", zap.Error(err))
		return
	}
	orgID, projectID, clusterID, err := parseUintIDForTiDBCluster(path)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Invalid TiDB cluster identifier, all types should be uint64"))
		logger.Error("Invalid TiDB cluster id", zap.Error(err))
		return
	}

	user, err := auth.GetUserFromContext(ctx)
	if err != nil {
		ctx.JSON(http.StatusUnauthorized, NewBaseResp(errs.ErrCodeAuth, "Invalid user"))
		logger.Error("Can't get user from context", zap.Error(err))
		return
	}

	var req VercelProject
	if err = ctx.ShouldBindQuery(&req); err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Missing required Vercel project information"))
		logger.Error("Missing required Vercel project information", zap.Error(err))
		return
	}

	span.SetAttributes(attribute.String("tidb_org_id", strconv.FormatUint(orgID, 10)), attribute.String("tidb_project_id", strconv.FormatUint(projectID, 10)), attribute.String("tidb_cluster_id", strconv.FormatUint(clusterID, 10)), attribute.String("vercel_project_name", req.VercelProjectName), attribute.String("user_email", user.Email))
	logger = logger.With(zap.Uint64("TiDBOrgID", orgID), zap.Uint64("TiDBProjectID", projectID), zap.Uint64("TiDBClusterID", clusterID), zap.String("VercelProjectID", req.VercelProjectID), zap.String("VercelProjectName", req.VercelProjectName), zap.String("UserEmail", user.Email))
	logger.Info("Start to delete Vercel connection")

	span.AddEvent("delete Vercel environment variables")
	if err = c.clusterLogic.DeleteVercelEnvs(ctxReq, clusterID, req, logger); err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeInternalServerError, "Delete Vercel environment variables failed"))
		logger.Error("Delete Vercel environment variables failed", zap.Error(err))
		return
	}

	span.AddEvent("delete connection from db")
	if err = c.composeEntityDAO.DeleteClusterConnection(ctxReq, clusterID, req.VercelProjectID); err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeInternalServerError, "Failed to delete connection"))
		logger.Error("Failed to delete connection between Vercel project and TiDB cluster", zap.Error(err))
		return
	}

	message := "Delete connection between Vercel project and TiDB cluster successfully."
	logger.Info(message)
	ctx.JSON(200, MessageResp{
		Message: message,
	})
}

// ListClusterConnectionsByCluster godoc
//
//	@Description	List connections between Vercel project and TiDB cluster By cluster id.
//
// This API is used by TiDB integration Dashboard, so the connections returned maybe configured by different Vercel integrations.
//
//	@Accept			json
//	@Produce		json
//	@Param			org_id		path		string					true	"TiDB org ID"
//	@Param			project_id	path		string					true	"TiDB project ID"
//	@Param			cluster_id	path		string					true	"TiDB cluster ID"
//	@Success		200			{object}	ListConnectionResponse	"ok"
//	@Failure		400			{object}	BaseResp				"error"
//	@Failure		500			{object}	BaseResp				"error"
//	@Router			/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/vercel/connections [get]
func (c *VercelController) ListClusterConnectionsByCluster(ctx *gin.Context) {
	ctxReq := ctx.Request.Context()
	var err error
	ctxReq, span := Tracer.Start(ctxReq, "vercel.ListClusterConnectionsByCluster")
	defer func() {
		tracing.EndSpan(span, err)
	}()
	logger := tilog.WithContextL(ctxReq).With(zap.String("method", "vercel.ListClusterConnectionsByCluster"))

	var path TiDBCluster
	if err = ctx.ShouldBindUri(&path); err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Missing required TiDB cluster information"))
		logger.Error("Missing required TiDB cluster information", zap.Error(err))
		return
	}
	orgID, projectID, clusterID, err := parseUintIDForTiDBCluster(path)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Invalid TiDB cluster identifier, all types should be uint64"))
		logger.Error("Invalid TiDB cluster id", zap.Error(err))
		return
	}

	user, err := auth.GetUserFromContext(ctx)
	if err != nil {
		ctx.JSON(http.StatusUnauthorized, NewBaseResp(errs.ErrCodeAuth, "Invalid user"))
		logger.Error("Can't get user from context", zap.Error(err))
		return
	}

	span.SetAttributes(attribute.String("tidb_org_id", strconv.FormatUint(orgID, 10)), attribute.String("tidb_project_id", strconv.FormatUint(projectID, 10)), attribute.String("tidb_cluster_id", strconv.FormatUint(clusterID, 10)), attribute.String("user_email", user.Email))
	logger = logger.With(zap.Uint64("TiDBOrgID", orgID), zap.Uint64("TiDBProjectID", projectID), zap.Uint64("TiDBClusterID", clusterID), zap.String("UserEmail", user.Email))
	logger.Info("Start to list Vercel connections by cluster")

	span.AddEvent("list connections from db")
	connections, err := c.clusterConnectionDAO.ListVercelConnectionsByClusterID(ctxReq, clusterID)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeInternalServerError, "Failed to get connections under TiDB cluster"))
		logger.Error("Failed to get connections under TiDB cluster", zap.Error(err))
		return
	}

	span.AddEvent("generate connectionsVOs")
	connectionVOs, err := c.clusterLogic.GetConnectionVOsByTiDBClusterID(ctxReq, connections, logger)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeInternalServerError, "Failed to get connectionsVO under TiDB cluster"))
		logger.Error("Failed to get connectionsVO under TiDB cluster", zap.Error(err))
		return
	}

	logger.Info("List Vercel connections by cluster successfully")
	response := ListConnectionResponse{
		Connections: connectionVOs,
	}
	ctx.JSON(200, response)
}

// CreateServerlessCluster godoc
//
//	@Description	Create serverless cluster, return success when the cluster is ready.
//	@Accept			json
//	@Produce		json
//	@Param			org_id			path		string					true	"TiDB Org ID"
//	@Param			project_id		path		string					true	"TiDB Project ID"
//	@Param			vercel_project	body		CreateClusterRequest	true	"Serverless cluster info"
//	@Success		200				{object}	MessageResp				"ok"
//	@Failure		400				{object}	BaseResp				"error"
//	@Failure		500				{object}	BaseResp				"error"
//	@Router			/orgs/{org_id}/projects/{project_id}/serverless/clusters [post]
func (c *VercelController) CreateServerlessCluster(ctx *gin.Context) {
	ctxReq := ctx.Request.Context()
	var err error
	ctxReq, span := Tracer.Start(ctxReq, "vercel.CreateServerlessCluster")
	logger := tilog.WithContextL(ctxReq).With(zap.String("method", "vercel.CreateServerlessCluster"))
	defer func() {
		tracing.EndSpan(span, err)
	}()

	var path TiDBProject
	if err = ctx.ShouldBindUri(&path); err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Missing required TiDB project information"))
		logger.Error("Missing required TiDB project information", zap.Error(err))
		return
	}
	orgID, projectID, err := parseUintIDForTiDBProject(path)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Invalid TiDB project identifier, all types should be uint64"))
		logger.Error("Invalid TiDB project id", zap.Error(err))
		return
	}

	user, err := auth.GetUserFromContext(ctx)
	if err != nil {
		ctx.JSON(http.StatusUnauthorized, NewBaseResp(errs.ErrCodeAuth, "Invalid user"))
		logger.Error("Can't get user from context", zap.Error(err))
		return
	}

	var req CreateClusterRequest
	if err = ctx.ShouldBindJSON(&req); err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Missing required create cluster information"))
		logger.Error("Missing required create cluster information", zap.Error(err))
		return
	}

	span.SetAttributes(attribute.String("tidb_org_id", strconv.FormatUint(orgID, 10)), attribute.String("tidb_project_id", strconv.FormatUint(projectID, 10)), attribute.String("tidb_cluster_name", req.ClusterName), attribute.String("vercel_configuration_id", req.ConfigurationID), attribute.String("user_email", user.Email))
	logger = logger.With(zap.Uint64("TiDBOrgID", orgID), zap.Uint64("TiDBProjectID", projectID), zap.String("TiDBClusterName", req.ClusterName), zap.Strings("VercelProjectID", req.VercelProjectIDs), zap.String("VercelConfigurationID", req.ConfigurationID), zap.String("UserEmail", user.Email))
	logger.Info("Start to add create serverless cluster")

	span.AddEvent("create serverless cluster")
	clusterID, err := c.clusterLogic.CreateServerlessCluster(ctxReq, orgID, projectID, user.Email, req.ClusterName, req.VercelProjectIDs, req.ConfigurationID, logger)
	if err != nil {
		if bizErr, ok := err.(bizerr.BizError); ok && bizerr.IsBusinessErr(bizErr.ErrCode()) {
			ctx.JSON(http.StatusOK, NewBaseResp(int64(bizErr.ErrCode()), bizErr.ErrMsg()))
		} else {
			ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeInternalServerError, "Create TiDB serverless cluster failed"))
		}
		logger.Error("Create TiDB serverless", zap.Error(err))
		return
	}

	span.AddEvent("wait serverless cluster ready")
	err = c.clusterLogic.WaitingClusterReady(ctx, clusterID)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeInternalServerError, "Failed to check cluster ready, please check status on dashboard."))
		logger.Error("Failed to check cluster ready", zap.Error(err))
		return
	}

	message := "Create TiDB serverless cluster successfully."
	logger.Info(message)
	ctx.JSON(200, MessageResp{
		Message: message,
	})
}
