package approval

import (
	"fmt"
	"strconv"

	"github.com/tidbcloud/devops/api"
	apprapi "github.com/tidbcloud/devops/api/approval"
	apprent "github.com/tidbcloud/devops/entity/approval"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	apprsvcapi "github.com/tidbcloud/devops/pkg/service/approval/api"

	"github.com/gin-gonic/gin"
)

// checkApprovalScope loads the approval and enforces per-role tenant scope
// using the operation params it carries (tenant_id / cluster_id /
// resource_pool_id), so a restricted API key can't approve, reject, or
// cancel approvals for tenants outside its allow list.
func (c *ApprovalController) checkApprovalScope(ctx *gin.Context, apprID uint64) error {
	appr, err := c.apprSvc.GetApproval(ctx, apprID)
	if err != nil {
		return err
	}
	return ctrl.CheckParamsScope(ctx, c.coreSvcCli, appr.Parameters)
}

// GetApproval
//
//	@Id				ApprovalControllerGetApproval
//	@Description	get a approval
//	@Tags			ApprovalController
//	@Accept			json
//	@Produce		json
//	@Param			appr_id	path		int	true	"approval id"
//	@Success		200		{object}	apprapi.GetApprovalResp
//	@Router			/api/v1/devops/approval/approvals/{appr_id} [get]
func (c *ApprovalController) GetApproval(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("appr_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

	appr, err := c.apprSvc.GetApproval(ctx, id)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	if err := ctrl.CheckParamsScope(ctx, c.coreSvcCli, appr.Parameters); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodePermissionDenied)
		return
	}

	resp := apprapi.GetApprovalResp{Approval: appr}

	email := ctx.GetString(consts.CtxUserEmail)
	if email != "" {
		perm, err := c.apprSvc.CheckApprovalPermission(ctx, id, email)
		if err == nil {
			resp.CanApprove = perm.CanApprove
			resp.CanAdminApprove = perm.CanAdminApprove
		}
	}

	ctrl.Success(ctx, resp)
}

// CreateApproval
//
//	@Id				ApprovalControllerCreateApproval
//	@Description	create a approval
//	@Tags			ApprovalController
//	@Accept			json
//	@Produce		json
//	@Param			request	body		apprapi.CreateApprovalReq	true	"create approval request"
//	@Success		200		{object}	apprapi.CreateApprovalResp
//	@Router			/api/v1/devops/approval/approvals [post]
func (c *ApprovalController) CreateApproval(ctx *gin.Context) {
	req := &apprapi.CreateApprovalReq{}
	err := ctx.BindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("invalid request body: %w", err), api.ErrCodeInvalidArgument)
		return
	}

	param := apprsvcapi.CreateApprovalParam{
		CreateApprovalParamBase: apprsvcapi.CreateApprovalParamBase{
			Name:          req.Name,
			Applicant:     req.Applicant,
			DetailLink:    req.DetailLink,
			Parameters:    req.Parameters,
			Overview:      req.Overview,
			Reason:        req.Reason,
			Configuration: req.Configuration,
		},
		Nodes:   req.Nodes,
		CCTasks: req.CCTasks,
	}

	id, err := c.apprSvc.CreateApproval(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := apprapi.CreateApprovalResp{ID: id}
	ctrl.Success(ctx, resp)
}

// UserApprove
//
//	@Id				ApprovalControllerUserApprove
//	@Description	approve a approval
//	@Tags			ApprovalController
//	@Accept			json
//	@Produce		json
//	@Param			appr_id	path		int	true	"approval id"
//	@Success		200		{object}	api.BaseResp
//	@Router			/api/v1/devops/approval/approvals/{appr_id}/approve [post]
func (c *ApprovalController) UserApprove(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("appr_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

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

	if err := c.checkApprovalScope(ctx, id); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodePermissionDenied)
		return
	}

	param := apprsvcapi.FinishApprovalTaskParam{
		User:   email,
		Result: apprent.ApprovalTaskStatusApproved,
	}
	err = c.apprSvc.FinishApprovalTask(ctx, id, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// UserReject
//
//	@Id				ApprovalControllerUserReject
//	@Description	approve a approval
//	@Tags			ApprovalController
//	@Accept			json
//	@Produce		json
//	@Param			appr_id	path		int	true	"approval id"
//	@Success		200		{object}	api.BaseResp
//	@Router			/api/v1/devops/approval/approvals/{appr_id}/reject [post]
func (c *ApprovalController) UserReject(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("appr_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

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

	if err := c.checkApprovalScope(ctx, id); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodePermissionDenied)
		return
	}

	param := apprsvcapi.FinishApprovalTaskParam{
		User:   email,
		Result: apprent.ApprovalTaskStatusRejected,
	}
	err = c.apprSvc.FinishApprovalTask(ctx, id, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// AdminApprove
//
//	@Id				ApprovalControllerAdminApprove
//	@Description	approve a approval by admin
//	@Tags			ApprovalController
//	@Accept			json
//	@Produce		json
//	@Param			appr_id	path		int	true	"approval id"
//	@Success		200		{object}	api.BaseResp
//	@Router			/api/v1/devops/approval/approvals/{appr_id}/admin_approve [post]
func (c *ApprovalController) AdminApprove(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("appr_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

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

	if err := c.checkApprovalScope(ctx, id); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodePermissionDenied)
		return
	}

	param := apprsvcapi.FinishApprovalTaskParam{
		User:   email,
		Result: apprent.ApprovalTaskStatusApproved,
	}
	err = c.apprSvc.FinishApprovalAdminTask(ctx, id, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// CancelApproval
//
//	@Id				ApprovalControllerCancelApproval
//	@Description	cancel a approval
//	@Tags			ApprovalController
//	@Accept			json
//	@Produce		json
//	@Param			appr_id	path		int	true	"approval id"
//	@Success		200		{object}	api.BaseResp
//	@Router			/api/v1/devops/approval/approvals/{appr_id}/cancel [post]
func (c *ApprovalController) CancelApproval(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("appr_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

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

	if err := c.checkApprovalScope(ctx, id); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodePermissionDenied)
		return
	}

	err = c.apprSvc.CancelApproval(ctx, id)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}
