package controlplane

import (
	"fmt"
	"strconv"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/devops/api"
	depapi "github.com/tidbcloud/devops/api/controlplane"
	cp "github.com/tidbcloud/devops/entity/controlplane"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	depsvcapi "github.com/tidbcloud/devops/pkg/service/controlplane/api"
)

// CreateDeployment
//
//	@Id				DeploymentControllerCreateDeployment
//	@Description	create a new deployment
//	@Tags			DeploymentController
//	@Accept			json
//	@Produce		json
//	@Param			req	body		depapi.CreateDeploymentReq	true	"request body"
//	@Success		200	{object}	depapi.CreateDeploymentResp
//	@Router			/api/v1/devops/controlplane/deployments [post]
func (c *DeploymentController) CreateDeployment(ctx *gin.Context) {
	req := &depapi.CreateDeploymentReq{}
	if err := ctx.ShouldBindJSON(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	email := consts.GetUserEmailFromCtx(ctx, "")
	if email == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("user not found in ctx"), api.ErrCodeInternal)
		return
	}

	id, err := c.depSvc.CreateDeployment(ctx, depsvcapi.CreateDeploymentParam{
		Scenario: req.Scenario,
		Params:   req.Params,
	}, email)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, &depapi.CreateDeploymentResp{ID: *id})
}

// ListDeployments
//
//	@Id				DeploymentControllerListDeployments
//	@Description	list deployments
//	@Tags			DeploymentController
//	@Accept			json
//	@Produce		json
//	@Param			req	query		depapi.ListDeploymentReq	false	"request param"
//	@Success		200	{object}	depapi.ListDeploymentResp
//	@Router			/api/v1/devops/controlplane/deployments [get]
func (c *DeploymentController) ListDeployments(ctx *gin.Context) {
	req := &depapi.ListDeploymentReq{}
	if err := ctx.ShouldBindQuery(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	param := depsvcapi.ListDeploymentParam{}
	if req.Scenario != "" {
		s := cp.Scenario(req.Scenario)
		param.Scenario = &s
	}
	if req.Phase != "" {
		p := cp.Phase(req.Phase)
		param.Phase = &p
	}

	deployments, err := c.depSvc.ListDeployments(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, &depapi.ListDeploymentResp{Deployments: deployments})
}

// GetDeployment
//
//	@Id				DeploymentControllerGetDeployment
//	@Description	get a deployment
//	@Tags			DeploymentController
//	@Accept			json
//	@Produce		json
//	@Param			deployment_id	path		int	true	"deployment id"
//	@Success		200				{object}	depapi.GetDeploymentResp
//	@Router			/api/v1/devops/controlplane/deployments/{deployment_id} [get]
func (c *DeploymentController) GetDeployment(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("deployment_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("deployment_id is not an integer"), api.ErrCodeInvalidArgument)
		return
	}

	dep, err := c.depSvc.GetDeployment(ctx, id)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, &depapi.GetDeploymentResp{Deployment: dep})
}

// StartPreview
//
//	@Id				DeploymentControllerStartPreview
//	@Description	start preview workflow for a deployment
//	@Tags			DeploymentController
//	@Accept			json
//	@Produce		json
//	@Param			deployment_id	path		int	true	"deployment id"
//	@Success		200				{object}	depapi.StartPreviewResp
//	@Router			/api/v1/devops/controlplane/deployments/{deployment_id}/preview [post]
func (c *DeploymentController) StartPreview(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("deployment_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("deployment_id is not an integer"), api.ErrCodeInvalidArgument)
		return
	}

	result, err := c.depSvc.StartPreview(ctx, id)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, &depapi.StartPreviewResp{RunID: result.RunID, RunURL: result.RunURL})
}

// StartApply
//
//	@Id				DeploymentControllerStartApply
//	@Description	start apply workflow for a deployment (after approval)
//	@Tags			DeploymentController
//	@Accept			json
//	@Produce		json
//	@Param			deployment_id	path		int	true	"deployment id"
//	@Success		200				{object}	api.BaseResp
//	@Router			/api/v1/devops/controlplane/deployments/{deployment_id}/apply [post]
func (c *DeploymentController) StartApply(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("deployment_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("deployment_id is not an integer"), api.ErrCodeInvalidArgument)
		return
	}

	if err := c.depSvc.StartApply(ctx, id); err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// RetryApply
//
//	@Id				DeploymentControllerRetryApply
//	@Description	retry a failed apply using the same frozen snapshot
//	@Tags			DeploymentController
//	@Accept			json
//	@Produce		json
//	@Param			deployment_id	path		int	true	"deployment id"
//	@Success		200				{object}	api.BaseResp
//	@Router			/api/v1/devops/controlplane/deployments/{deployment_id}/retry [post]
func (c *DeploymentController) RetryApply(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("deployment_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("deployment_id is not an integer"), api.ErrCodeInvalidArgument)
		return
	}

	if err := c.depSvc.RetryApply(ctx, id); err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// Cancel
//
//	@Id				DeploymentControllerCancel
//	@Description	cancel a deployment
//	@Tags			DeploymentController
//	@Accept			json
//	@Produce		json
//	@Param			deployment_id	path		int	true	"deployment id"
//	@Success		200				{object}	api.BaseResp
//	@Router			/api/v1/devops/controlplane/deployments/{deployment_id}/cancel [post]
func (c *DeploymentController) Cancel(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("deployment_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("deployment_id is not an integer"), api.ErrCodeInvalidArgument)
		return
	}

	if err := c.depSvc.Cancel(ctx, id); err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// OnPreviewComplete
//
//	@Id				DeploymentControllerOnPreviewComplete
//	@Description	callback when preview workflow completes
//	@Tags			DeploymentController
//	@Accept			json
//	@Produce		json
//	@Param			deployment_id	path		int							true	"deployment id"
//	@Param			req				body		depapi.OnPreviewCompleteReq	true	"request body"
//	@Success		200				{object}	api.BaseResp
//	@Router			/api/v1/devops-internal/controlplane/deployments/{deployment_id}/preview/complete [post]
func (c *DeploymentController) OnPreviewComplete(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("deployment_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("deployment_id is not an integer"), api.ErrCodeInvalidArgument)
		return
	}

	req := &depapi.OnPreviewCompleteReq{}
	if err := ctx.ShouldBindJSON(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	result := depsvcapi.PreviewResult{
		RunID:   req.RunID,
		RunURL:  req.RunURL,
		Status:  req.Status,
		Summary: req.Summary,
	}
	if err := c.depSvc.OnPreviewComplete(ctx, id, result); err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// OnApplyComplete
//
//	@Id				DeploymentControllerOnApplyComplete
//	@Description	callback when apply workflow completes
//	@Tags			DeploymentController
//	@Accept			json
//	@Produce		json
//	@Param			deployment_id	path		int							true	"deployment id"
//	@Param			req				body		depapi.OnApplyCompleteReq	true	"request body"
//	@Success		200				{object}	api.BaseResp
//	@Router			/api/v1/devops-internal/controlplane/deployments/{deployment_id}/apply/complete [post]
func (c *DeploymentController) OnApplyComplete(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("deployment_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("deployment_id is not an integer"), api.ErrCodeInvalidArgument)
		return
	}

	req := &depapi.OnApplyCompleteReq{}
	if err := ctx.ShouldBindJSON(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	result := depsvcapi.ApplyResult{
		RunID:  req.RunID,
		RunURL: req.RunURL,
		Status: req.Status,
	}
	if err := c.depSvc.OnApplyComplete(ctx, id, result); err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}
