package vercel

import (
	"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/tracing"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.opentelemetry.io/otel/attribute"
	"go.uber.org/zap"
)

// AddDataAppVercelConnection godoc
//
//	@Description	Add connection between Vercel project and TiDB Data app
//	@Accept			json
//	@Produce		json
//	@Param			org_id			path		string						true	"TiDB Org ID"
//	@Param			project_id		path		string						true	"TiDB Project ID"
//	@Param			data_app_id		path		string						true	"TiDB Data App ID"
//	@Param			vercel_project	body		AddDataAppConnectionRequest	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}/dataapps/{data_app_id}/vercel/connections [post]
func (c *VercelController) AddDataAppVercelConnection(ctx *gin.Context) {
	ctxReq := ctx.Request.Context()
	var err error
	ctxReq, span := Tracer.Start(ctxReq, "vercel.AddDataAppVercelConnection")
	defer func() {
		tracing.EndSpan(span, err)
	}()
	logger := tilog.WithContextL(ctxReq).With(zap.String("method", "vercel.AddDataAppVercelConnection"))

	var path DataAppPath
	if err = ctx.ShouldBindUri(&path); err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Missing required data app information"))
		logger.Error("Missing required data app information", zap.Error(err))
		return
	}
	orgID, projectID, dataAppID, err := parseUintIDForDataApp(path)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Invalid data app identifier, all types should be uint64"))
		logger.Error("Invalid data app 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 AddDataAppConnectionRequest
	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("data_app_id", strconv.FormatUint(dataAppID, 10)), attribute.String("vercel_project_name", req.VercelProjectName), attribute.String("configuration_id", req.ConfigurationID), attribute.String("user_email", user.Email))
	logger = logger.With(zap.Uint64("TiDBOrgID", orgID), zap.Uint64("TiDBProjectID", projectID), zap.Uint64("DataAppID", dataAppID), zap.String("VercelProjectID", req.VercelProjectID), zap.String("VercelProjectName", req.VercelProjectName), zap.String("ConfigurationID", req.ConfigurationID), zap.String("UserEmail", user.Email))
	logger.Info("Start to add Vercel and data app connection")

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

	span.AddEvent("check if Vercel project has been configured")
	configured, err := c.dataAppLogic.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.ErrCodeBadRequest, "Vercel project has already been configured"))
		logger.Error("Vercel project has already been configured")
		return
	}

	span.AddEvent("validate data app")
	dataApp, err := c.dataAppLogic.dataApiSvc.ShowDataApp(ctxReq, dataAppID)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeInternalServerError, "Get data app failed"))
		logger.Error("Get data app failed", zap.Error(err))
		return
	}
	err = c.dataAppLogic.validateDataApp(ctxReq, dataApp, orgID, projectID, dataAppID)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Invalid data app"))
		logger.Error("Invalid data app", zap.Error(err))
		return
	}

	span.AddEvent("prepare environment variables")
	envs, err := c.dataAppLogic.PrepareEnvironmentVariables(ctxReq, dataApp, user.Email, req.VercelProjectName)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeInternalServerError, "Failed to prepare vercel environment variables"))
		logger.Error("Failed to prepare vercel environment variables", zap.Error(err))
		return
	}

	connection := vercelDB.DataAppConnection{
		VercelProjectID:                  req.VercelProjectID,
		TiDBOrgID:                        orgID,
		TiDBProjectID:                    projectID,
		DataAppID:                        dataAppID,
		VercelIntegrationConfigurationID: req.ConfigurationID,
	}

	span.AddEvent("add connection")
	err = c.dataAppLogic.AddDataAppConnection(ctxReq, req.VercelProjectID, req.ConfigurationID, 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 data app successfully."
	logger.Info(message)
	ctx.JSON(200, MessageResp{
		Message: message,
	})
}

// DeleteDataAppConnection godoc
//
//	@Description	Delete connection between Vercel project and TiDB data app
//	@Accept			json
//	@Produce		json
//	@Param			org_id			path		string			true	"TiDB Org ID"
//	@Param			project_id		path		string			true	"TiDB Project ID"
//	@Param			data_app_id		path		string			true	"TiDB Data App 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}/dataapps/{data_app_id}/vercel/connections [delete]
func (c *VercelController) DeleteDataAppConnection(ctx *gin.Context) {
	ctxReq := ctx.Request.Context()
	var err error
	ctxReq, span := Tracer.Start(ctxReq, "vercel.DeleteDataAppConnection")
	defer func() {
		tracing.EndSpan(span, err)
	}()
	logger := tilog.WithContextL(ctxReq).With(zap.String("method", "vercel.DeleteDataAppConnection"))

	var path DataAppPath
	if err = ctx.ShouldBindUri(&path); err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Missing required data app information"))
		logger.Error("Missing required data app information", zap.Error(err))
		return
	}
	orgID, projectID, dataAppID, err := parseUintIDForDataApp(path)
	if err != nil {
		ctx.JSON(http.StatusOK, NewBaseResp(errs.ErrCodeBadRequest, "Invalid data app identifier, all types should be uint64"))
		logger.Error("Invalid data app 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("data_app_id", strconv.FormatUint(dataAppID, 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("DataAppID", dataAppID), 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.dataAppLogic.DeleteVercelEnvs(ctxReq, dataAppID, 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.DeleteDataAppConnection(ctxReq, dataAppID, 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 data app", zap.Error(err))
		return
	}

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