package tenant

import (
	"fmt"
	"strconv"

	"github.com/gin-gonic/gin"
	userapiv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/core-server/user/v1"
	"github.com/tidbcloud/devops/api"
	tenantapi "github.com/tidbcloud/devops/api/tenant"
	opent "github.com/tidbcloud/devops/entity/operation"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	opsvc "github.com/tidbcloud/devops/pkg/service/operation"
	opsvctenantaction "github.com/tidbcloud/devops/pkg/service/operation/actions/tenantaction"
	opsvcins "github.com/tidbcloud/devops/pkg/service/operation/instance"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"google.golang.org/protobuf/encoding/protojson"
)

const (
	operationCreateReservedCapacityTemplate       = "CreateReservedCapacityTemplate"
	operationUpdateReservedCapacityTemplateStatus = "UpdateReservedCapacityTemplateStatus"
	operationDeleteReservedCapacityTemplate       = "DeleteReservedCapacityTemplate"
)

// ListReservedCapacityTemplates
//
//	@Id				ListReservedCapacityTemplates
//	@Description	list reserved capacity templates
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			req	query		tenantapi.ListReservedCapacityTemplatesReq	true	"ListReservedCapacityTemplatesReq"
//	@Success		200	{object}	tenantapi.ListReservedCapacityTemplatesResp
//	@Failure		500	{object}	api.BaseResp
//	@Router			/api/v1/devops/tenants/reservedCapacityTemplates [get]
func (c *TenantController) ListReservedCapacityTemplates(ctx *gin.Context) {
	req := &tenantapi.ListReservedCapacityTemplatesReq{}
	if err := ctx.ShouldBindQuery(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	reqPB, err := tenantapi.BuildListReservedCapacityTemplatesRequest(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	respPB, err := c.coreSvcCli.Mgmt.ReservedCapacityTemplate.ListReservedCapacityTemplates(ctx, reqPB)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, &tenantapi.ListReservedCapacityTemplatesResp{
		Total:                     respPB.GetTotal(),
		ReservedCapacityTemplates: tenantapi.BuildReservedCapacityTemplates(respPB),
	})
}

// GetReservedCapacityTemplate
//
//	@Id				GetReservedCapacityTemplate
//	@Description	get reserved capacity template
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			capacity_template_id	path		string	true	"capacity template id"
//	@Success		200						{object}	tenantapi.GetReservedCapacityTemplateResp
//	@Failure		500						{object}	api.BaseResp
//	@Router			/api/v1/devops/tenants/reservedCapacityTemplates/{capacity_template_id} [get]
func (c *TenantController) GetReservedCapacityTemplate(ctx *gin.Context) {
	capacityTemplateID, err := parseCapacityTemplateID(ctx)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	respPB, err := c.coreSvcCli.Mgmt.ReservedCapacityTemplate.GetReservedCapacityTemplate(ctx, &mgmtv1.GetReservedCapacityTemplateRequest{
		CapacityTemplateId: capacityTemplateID,
	})
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, &tenantapi.GetReservedCapacityTemplateResp{
		ReservedCapacityTemplate: tenantapi.BuildReservedCapacityTemplate(respPB),
	})
}

// CreateReservedCapacityTemplate
//
//	@Id				CreateReservedCapacityTemplate
//	@Description	create reserved capacity template by operation
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			req	body		tenantapi.CreateReservedCapacityTemplateReq	true	"CreateReservedCapacityTemplateReq"
//	@Success		200	{object}	tenantapi.ReservedCapacityTemplateOperationResp
//	@Failure		500	{object}	api.BaseResp
//	@Router			/api/v1/devops/tenants/reservedCapacityTemplates [post]
func (c *TenantController) CreateReservedCapacityTemplate(ctx *gin.Context) {
	req := &tenantapi.CreateReservedCapacityTemplateReq{}
	if err := ctx.ShouldBindJSON(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	templatePB, err := tenantapi.BuildReservedCapacityTemplatePB(req.ReservedCapacityTemplate)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	reqPB := &mgmtv1.CreateReservedCapacityTemplateRequest{
		ReservedCapacityTemplate: templatePB,
	}
	reqData, err := protojson.Marshal(reqPB)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("marshal proto message failed: %s", err), api.ErrCodeInternal)
		return
	}

	instanceID, err := c.createReservedCapacityTemplateOperation(ctx,
		operationCreateReservedCapacityTemplate,
		req.Reason,
		templatePB.GetOrganizationId(),
		map[opent.ParamName]string{
			opent.ParamCreateReservedCapacityTemplateRequest: string(reqData),
		},
		opsvctenantaction.ActionCreateReservedCapacityTemplate.Name,
	)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, &tenantapi.ReservedCapacityTemplateOperationResp{InstanceID: instanceID})
}

// UpdateReservedCapacityTemplateStatus
//
//	@Id				UpdateReservedCapacityTemplateStatus
//	@Description	update reserved capacity template status by operation
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			capacity_template_id	path		string												true	"capacity template id"
//	@Param			req						body		tenantapi.UpdateReservedCapacityTemplateStatusReq	true	"UpdateReservedCapacityTemplateStatusReq"
//	@Success		200						{object}	tenantapi.ReservedCapacityTemplateOperationResp
//	@Failure		500						{object}	api.BaseResp
//	@Router			/api/v1/devops/tenants/reservedCapacityTemplates/{capacity_template_id}/status [patch]
func (c *TenantController) UpdateReservedCapacityTemplateStatus(ctx *gin.Context) {
	capacityTemplateID, err := parseCapacityTemplateID(ctx)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	req := &tenantapi.UpdateReservedCapacityTemplateStatusReq{}
	if err := ctx.ShouldBindJSON(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	status, err := tenantapi.ReservedCapacityTemplateStatusPB(req.Status)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	reqPB := &mgmtv1.UpdateReservedCapacityTemplateStatusRequest{
		CapacityTemplateId: capacityTemplateID,
		Status:             status,
	}
	reqData, err := protojson.Marshal(reqPB)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("marshal proto message failed: %s", err), api.ErrCodeInternal)
		return
	}

	tenantID, err := c.getReservedCapacityTemplateTenantID(ctx, capacityTemplateID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	instanceID, err := c.createReservedCapacityTemplateOperation(ctx,
		operationUpdateReservedCapacityTemplateStatus,
		req.Reason,
		tenantID,
		map[opent.ParamName]string{
			opent.ParamUpdateReservedCapacityTemplateStatusRequest: string(reqData),
		},
		opsvctenantaction.ActionUpdateReservedCapacityTemplateStatus.Name,
	)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, &tenantapi.ReservedCapacityTemplateOperationResp{InstanceID: instanceID})
}

// DeleteReservedCapacityTemplate
//
//	@Id				DeleteReservedCapacityTemplate
//	@Description	delete reserved capacity template by operation
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			capacity_template_id	path		string										true	"capacity template id"
//	@Param			req						body		tenantapi.DeleteReservedCapacityTemplateReq	true	"DeleteReservedCapacityTemplateReq"
//	@Success		200						{object}	tenantapi.ReservedCapacityTemplateOperationResp
//	@Failure		500						{object}	api.BaseResp
//	@Router			/api/v1/devops/tenants/reservedCapacityTemplates/{capacity_template_id} [delete]
func (c *TenantController) DeleteReservedCapacityTemplate(ctx *gin.Context) {
	capacityTemplateID, err := parseCapacityTemplateID(ctx)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

	reqPB := &mgmtv1.DeleteReservedCapacityTemplateRequest{
		CapacityTemplateId: capacityTemplateID,
	}
	reqData, err := protojson.Marshal(reqPB)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("marshal proto message failed: %s", err), api.ErrCodeInternal)
		return
	}

	tenantID, err := c.getReservedCapacityTemplateTenantID(ctx, capacityTemplateID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	instanceID, err := c.createReservedCapacityTemplateOperation(ctx,
		operationDeleteReservedCapacityTemplate,
		req.Reason,
		tenantID,
		map[opent.ParamName]string{
			opent.ParamDeleteReservedCapacityTemplateRequest: string(reqData),
		},
		opsvctenantaction.ActionDeleteReservedCapacityTemplate.Name,
	)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, &tenantapi.ReservedCapacityTemplateOperationResp{InstanceID: instanceID})
}

func (c *TenantController) createReservedCapacityTemplateOperation(
	ctx *gin.Context,
	operation string,
	reason string,
	tenantID uint64,
	params map[opent.ParamName]string,
	actionName opent.ActionName,
) (*uint64, error) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		return nil, errs.APIErrorf(api.ErrCodeUnauthenticated, "user profile not found")
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	tenantRegion := ""
	if tenantID != 0 {
		tenantEntity, err := c.tenantSvc.GetTenant(ctx, tenantID)
		if err != nil {
			return nil, err
		}
		if !userProfile.AggregatedRoleScope.AllowGlobalRegion {
			tenant := entityToApi(tenantEntity)
			found := false
			for _, region := range userProfile.AggregatedRoleScope.AllowedRegions {
				if string(tenant.Region) == region {
					found = true
					break
				}
			}
			if !found {
				return nil, errs.APIErrorf(api.ErrCodePermissionDenied, "org %d is not allowed", tenantID)
			}
		}
		tenantRegion = string(tenantEntity.GetTenantRegion())
	}

	params[opent.ParamTenantID] = strconv.FormatUint(tenantID, 10)
	params[opent.ParamTenantRegion] = tenantRegion
	instanceID, err := c.opSvc.CreateInstance(ctx, opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetTenant,
		Operation: operation,
		Reason:    reason,
		Applicant: userProfile.UserEmail,
		Params:    params,
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        actionName,
				StartPolicy: opent.ActionStartPolicyImmediate,
			},
		},
	})
	if err != nil {
		return nil, err
	}
	return instanceID, nil
}

func (c *TenantController) getReservedCapacityTemplateTenantID(ctx *gin.Context, capacityTemplateID uint64) (uint64, error) {
	template, err := c.coreSvcCli.Mgmt.ReservedCapacityTemplate.GetReservedCapacityTemplate(ctx, &mgmtv1.GetReservedCapacityTemplateRequest{
		CapacityTemplateId: capacityTemplateID,
	})
	if err != nil {
		return 0, err
	}
	return template.GetOrganizationId(), nil
}

func parseCapacityTemplateID(ctx *gin.Context) (uint64, error) {
	capacityTemplateID, err := strconv.ParseUint(ctx.Param("capacity_template_id"), 10, 64)
	if err != nil {
		return 0, fmt.Errorf("capacity_template_id is not a int")
	}
	return capacityTemplateID, nil
}
