package jump

import (
	"fmt"
	"slices"
	"strconv"
	"strings"

	"github.com/tidbcloud/devops/api"
	jmpapi "github.com/tidbcloud/devops/api/jump"
	jmpent "github.com/tidbcloud/devops/entity/jump"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	jmpsvcapi "github.com/tidbcloud/devops/pkg/service/jump/api"

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

var (
	controlPlanePriority = map[string]int{
		"base-aws-us-west-2":              100,
		jmpent.ControlPlaneInfraAPIServer: 90,
	}
)

// ListAuthorizations
//
//	@Id				JumpControllerListAuthorizations
//	@Description	list authorizations
//	@Tags			JumpController
//	@Accept			json
//	@Produce		json
//	@Param			req	query		jmpapi.ListAuthorizationsReq	false	"request param"
//	@Success		200	{object}	jmpapi.ListAuthorizationsResp
//	@Router			/api/v1/devops/jump/authorizations [get]
func (c *JumpController) ListAuthorizations(ctx *gin.Context) {
	req := &jmpapi.ListAuthorizationsReq{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	param := jmpsvcapi.ListAuthorizationParam{
		Page:              int(req.Page),
		PerPage:           int(req.PerPage),
		Phase:             req.Phase,
		Applicant:         req.Applicant,
		DestinationTypeIs: req.DestinationTypeIs,
		DestinationIDLike: req.DestinationIDLike,
		ShowUnaccessible:  req.ShowUnaccessible,
	}

	auths, total, err := c.jmpSvc.ListAuthorizations(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &jmpapi.ListAuthorizationsResp{
		Total:          total,
		Authorizations: auths,
	}
	ctrl.Success(ctx, resp)

}

// ListAvailableDestinations
//
//	@Id				JumpControllerListAvailableDestinations
//	@Description	list available destinations of a destination type
//	@Tags			JumpController
//	@Accept			json
//	@Produce		json
//	@Param			destination_type	path		string	true	"destination type"
//	@Success		200					{object}	jmpapi.ListAvailableDestinationsResp
//	@Router			/api/v1/devops/jump/destination/{destination_type} [get]
func (c *JumpController) ListAvailableDestinations(ctx *gin.Context) {
	typ := jmpent.DestinationType(ctx.Param("destination_type"))

	if !slices.Contains(jmpent.DestinationTypeValues, typ) {
		ctrl.FailWithCode(ctx, fmt.Errorf("supported destination type"), api.ErrCodeInvalidArgument)
		return
	}

	destinations, err := c.jmpSvc.ListAvailableDestinations(ctx, typ)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInternal)
		return
	}

	slices.SortFunc(destinations, func(i, j string) int {
		prio1 := controlPlanePriority[i]
		prio2 := controlPlanePriority[j]
		if prio1 != prio2 {
			return prio2 - prio1
		}

		return -1 * strings.Compare(i, j)
	})

	resp := &jmpapi.ListAvailableDestinationsResp{Destinations: destinations}
	ctrl.Success(ctx, resp)
}

// GetAuthorization
//
//	@Id				JumpControllerGetAuthorization
//	@Description	get an authorization
//	@Tags			JumpController
//	@Accept			json
//	@Produce		json
//	@Param			authorization_id	path		int							true	"authorization name"
//	@Param			req					query		jmpapi.GetAuthorizationReq	false	"request param"
//	@Success		200					{object}	jmpapi.GetAuthorizationResp
//	@Router			/api/v1/devops/jump/authorizations/{authorization_id} [get]
func (c *JumpController) GetAuthorization(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("authorization_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

	req := &jmpapi.GetAuthorizationReq{}
	err = ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	auth, err := c.jmpSvc.GetAuthorization(ctx, id)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &jmpapi.GetAuthorizationResp{Authorization: auth}
	ctrl.Success(ctx, resp)
}

// CreateAuthorization
//
//	@Id				JumpControllerCreateInstance
//	@Description	create an authorization
//	@Tags			JumpController
//	@Accept			json
//	@Produce		json
//	@Param			req	body		jmpapi.CreateAuthorizationReq	true	"request param"
//	@Success		200	{object}	jmpapi.CreateAuthorizationResp
//	@Router			/api/v1/devops/jump/authorizations [post]
func (c *JumpController) CreateAuthorization(ctx *gin.Context) {
	req := &jmpapi.CreateAuthorizationReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

	param, err := c.buildParamToCreateAuthorization(req, email)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	id, err := c.jmpSvc.CreateAuthorization(ctx, *param)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInternal)
		return
	}

	resp := &jmpapi.CreateAuthorizationResp{AuthorizationID: id}
	ctrl.Success(ctx, resp)
}

// CancelAuthorization
//
//	@Id				JumpControllerCancelAuthorization
//	@Description	cancel an authorization
//	@Tags			JumpController
//	@Accept			json
//	@Produce		json
//	@Param			authorization_id	path		int								true	"authorization id"
//	@Param			req					body		jmpapi.CancelAuthorizationReq	false	"request param"
//	@Success		200					{object}	api.BaseResp
//	@Router			/api/v1/devops/jump/authorizations/{authorization_id}/cancel [post]
func (c *JumpController) CancelAuthorization(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("authorization_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

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

	param := jmpsvcapi.CancelAuthorizaitonParam{
		Force:   req.Force,
		Timeout: req.Timeout,
	}
	err = c.jmpSvc.CancelAuthorizaiton(ctx, id, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// GetConnection
//
//	@Id				JumpControllerGetConnection
//	@Description	get connect jump pod params
//	@Tags			JumpController
//	@Accept			json
//	@Produce		json
//	@Param			authorization_id	path		int							true	"authorization id"
//	@Param			req					query		jmpapi.GetConnectionRequest	false	"request param"
//	@Success		200					{object}	jmpapi.GetConnectionResponse
//	@Router			/api/v1/devops/jump/authorizations/{authorization_id}/connection [get]
func (c *JumpController) GetConnection(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("authorization_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("id is not a int"), api.ErrCodeInvalidArgument)
		return
	}
	req := &jmpapi.GetConnectionRequest{}
	err = ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	email := ctx.GetString(consts.CtxUserEmail)

	conn, err := c.jmpSvc.GetConnection(ctx, id, email, req.Method)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &jmpapi.GetConnectionResponse{
		Connection: conn,
	}
	ctrl.Success(ctx, resp)
}

// GetUserOfApplicant
//
//	@Id				JumpControllerGetUserOfApplicant
//	@Description	get the applicant from the jump server.
//	@Tags			JumpController
//	@Accept			json
//	@Produce		json
//	@Success		200	{object}	jmpapi.GetUserResp
//	@Router			/api/v1/devops/jump/jumpserver/users/me [get]
func (c *JumpController) GetUserOfApplicant(ctx *gin.Context) {
	v, exist := ctx.Get(consts.CtxUserEmail)
	if !exist {
		ctrl.FailWithCode(ctx, fmt.Errorf("account not found in ctx"), api.ErrCodeInternal)
		return
	}
	email := v.(string)

	userID, err := c.jmpSvc.GetUser(ctx, email)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &jmpapi.GetUserResp{UserID: userID}
	ctrl.Success(ctx, resp)
}

// GetUser
//
//	@Id				JumpControllerGetUser
//	@Description	get the user from the jump server.
//	@Tags			JumpController
//	@Accept			json
//	@Produce		json
//	@Param			email	path		string	true	"user email"
//	@Success		200		{object}	jmpapi.GetUserResp
//	@Router			/api/v1/devops/jump/jumpserver/users/{email} [get]
func (c *JumpController) GetUser(ctx *gin.Context) {
	email := ctx.Param("email")

	userID, err := c.jmpSvc.GetUser(ctx, email)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &jmpapi.GetUserResp{UserID: userID}
	ctrl.Success(ctx, resp)
}

// ActivateUserOfApplicant
//
//	@Id				JumpControllerActivateUserOfApplicant
//	@Description	activate the applicant in the jump server.
//	@Tags			JumpController
//	@Accept			json
//	@Produce		json
//	@Success		200	{object}	api.BaseResp
//	@Router			/api/v1/devops/jump/jumpserver/users/activate [post]
func (c *JumpController) ActivateUserOfApplicant(ctx *gin.Context) {
	v, exist := ctx.Get(consts.CtxUserEmail)
	if !exist {
		ctrl.FailWithCode(ctx, fmt.Errorf("account not found in ctx"), api.ErrCodeInternal)
		return
	}
	email := v.(string)

	err := c.jmpSvc.InitUser(ctx, email)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// InviteUser
//
//	@Id				JumpControllerInviteUser
//	@Description	init a user
//	@Tags			JumpController
//	@Accept			json
//	@Produce		json
//	@Param			req	body		jmpapi.InviteUserReq	true	"request param"
//	@Success		200	{object}	jmpapi.InviteUserResp
//	@Router			/api/v1/devops/jump-admin/invite-user [post]
func (c *JumpController) InviteUser(ctx *gin.Context) {
	req := &jmpapi.InviteUserReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	resp := &jmpapi.InviteUserResp{}
	for _, email := range req.Emails {
		err = c.jmpSvc.InitUser(ctx, email)
		if err != nil {
			resp.Failed = append(resp.Failed, jmpapi.InviteUserFailedItem{
				Email: email,
				Err:   err.Error(),
			})
		} else {
			resp.Success = append(resp.Success, email)
		}
	}

	ctrl.Success(ctx, resp)
}

func (c *JumpController) buildParamToCreateAuthorization(req *jmpapi.CreateAuthorizationReq, applicant string) (*jmpsvcapi.CreateAuthorizationParam, error) {
	param := &jmpsvcapi.CreateAuthorizationParam{
		Applicant:           applicant,
		Privilege:           req.Privilege,
		Expiration:          req.Expiration,
		Reason:              req.Reason,
		EnableGatewayAccess: req.EnableGatewayAccess,
	}

	for _, d := range req.Destinations {
		union := &jmpent.DestinationUnion{}

		switch req.DestinationType {
		case jmpent.DestinationTypeControlPlane:
			union.ControlPlane = &jmpent.DestinationControlPlane{
				Name: d,
			}
		case jmpent.DestinationTypeDedicated:
			clusterID, err := strconv.ParseUint(d, 10, 64)
			if err != nil {
				return nil, fmt.Errorf("invalid cluster id %s", d)
			}
			union.DedicatedCluster = &jmpent.DestinationDedicatedCluster{
				ClusterID: clusterID,
			}
		case jmpent.DestinationTypeServerless:
			union.ServerlessCluster = &jmpent.DestinationServerlessCluster{
				Name: d,
			}
		case jmpent.DestinationTypeByoc:
			union.ByocCluster = &jmpent.DestinationByocCluster{
				BastionName: d,
			}
		case jmpent.DestinationTypePremium:
			clusterID, err := strconv.ParseUint(d, 10, 64)
			if err != nil {
				return nil, fmt.Errorf("invalid cluster id %s", d)
			}
			union.PremiumCluster = &jmpent.DestinationPremiumCluster{
				ClusterID: clusterID,
			}
		case jmpent.DestinationTypePremiumResourcePool:
			poolID, err := strconv.ParseUint(d, 10, 64)
			if err != nil {
				return nil, fmt.Errorf("invalid cluster id %s", d)
			}
			union.PremiumResourcePool = &jmpent.DestinationPremiumResourcePool{
				ResourcePoolID: poolID,
			}
		default:
			return nil, fmt.Errorf("unknown destination type %s", req.DestinationType)
		}

		param.Destinations = append(param.Destinations, union)
	}

	return param, nil
}
