package cloudconfig

import (
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/gin-gonic/gin"
	userapiv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/core-server/user/v1"
	ccproto "github.com/tidbcloud/config-center/proto/config_center"
	"github.com/tidbcloud/devops/api"
	ccapi "github.com/tidbcloud/devops/api/cloudconfig"
	ccent "github.com/tidbcloud/devops/entity/cloudconfig"
	opent "github.com/tidbcloud/devops/entity/operation"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	opsvc "github.com/tidbcloud/devops/pkg/service/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/actions/metaprofileaction"
	opsvcins "github.com/tidbcloud/devops/pkg/service/operation/instance"
)

// GetMetaProfile
//
//	@Id				CloudConfigControllerGetMetaProfile
//	@Description	get the meta profile by id
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			profile_id		path		string	true	"profile id"
//	@Param			cluster_type	query		string	true	"cluster type"
//	@Success		200				{object}	ccapi.MetaProfile
//	@Failure		default			{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/meta_profile/{profile_id} [get]
func (c *CloudConfigController) GetMetaProfile(ctx *gin.Context) {
	profileIDStr := ctx.Param("profile_id")
	if profileIDStr == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("profile_id is required"), api.ErrCodeInvalidArgument)
		return
	}
	profileID, err := strconv.ParseInt(profileIDStr, 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	clusterTypeStr := ctx.Query("cluster_type")
	if clusterTypeStr == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("cluster_type is required"), api.ErrCodeInvalidArgument)
		return
	}
	clusterType, ok := ccproto.ClusterType_value[clusterTypeStr]
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("invalid cluster_type: %s", clusterTypeStr), api.ErrCodeInvalidArgument)
		return
	}

	req := &ccproto.GetMetaProfileRequest{
		ProfileId:   profileID,
		ClusterType: ccproto.ClusterType(clusterType),
	}

	profile, err := c.svc.GetMetaProfile(ctx, req)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

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

	resp := &ccapi.MetaProfile{
		ClusterType:           ccent.ClusterType(ccproto.ClusterType_name[int32(profile.ClusterType)]),
		ProfileId:             profile.ProfileId,
		ProfileName:           profile.ProfileName,
		Description:           profile.Description,
		Tags:                  profile.Tags,
		ComponentImageTags:    profile.ComponentImageTags,
		ConfigCustomTemplates: profile.ConfigCustomTemplates,
		ComponentVersions:     profile.ComponentVersions,
	}
	ctrl.Success(ctx, resp)
}

// CreateMetaProfile
//
//	@Id				CloudConfigControllerCreateMetaProfile
//	@Description	create a meta profile
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		body		ccapi.CreateMetaProfileReq	true	"request param"
//	@Success		200		{object}	ccapi.OperationResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/meta_profile [post]
func (c *CloudConfigController) CreateMetaProfile(ctx *gin.Context) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

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

	_, ok = ccproto.ClusterType_value[string(req.ClusterType)]
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("invalid cluster_type: %s", req.ClusterType), api.ErrCodeInvalidArgument)
		return
	}
	if err := validateMetaProfileComponentVersions(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	tenantID, err := strconv.ParseUint(req.TenantID, 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("invalid tenant_id %q: %w", req.TenantID, err), api.ErrCodeInvalidArgument)
		return
	}
	if err := ctrl.CheckTenantScope(ctx, tenantID); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodePermissionDenied)
		return
	}

	tenant, err := c.tenantSvc.GetTenant(ctx, tenantID)
	if err != nil {
		ctrl.Fail(ctx, fmt.Errorf("query tenant '%d' failed: %w", tenantID, err))
		return
	}

	bytes, err := json.Marshal(req)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("marshal create meta profile request failed: %v", err), api.ErrCodeInvalidArgument)
		return
	}

	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetTenant,
		Operation: "CreateMetaProfileInConfigCenter",
		Applicant: userProfile.UserEmail,
		Reason:    "",
		Params: map[opent.ParamName]string{
			opent.ParamTenantID:                  req.TenantID,
			opent.ParamTenantRegion:              string(tenant.GetTenantRegion()),
			opent.ParamMetaProfifleCreateRequest: string(bytes),
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        metaprofileaction.ActionCreateMetaProfile.Name,
				StartPolicy: opent.ActionStartPolicyManual,
			},
		},
	}
	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &ccapi.OperationResp{InstanceID: insID}
	ctrl.Success(ctx, resp)
}

// ListMetaProfiles
//
//	@Id				CloudConfigControllerListMetaProfiles
//	@Description	list meta profiles
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		query		ccapi.ListMetaProfilesReq	false	"request param"
//	@Success		200		{object}	ccapi.ListMetaProfilesResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/meta_profile [get]
func (c *CloudConfigController) ListMetaProfiles(ctx *gin.Context) {
	query := &ccapi.ListMetaProfilesReq{}
	if err := ctx.ShouldBindQuery(query); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	clusterTypeStr := string(query.ClusterType)
	var clusterType ccproto.ClusterType
	if clusterTypeStr != "" {
		ct, ok := ccproto.ClusterType_value[clusterTypeStr]
		if !ok {
			ctrl.FailWithCode(ctx, fmt.Errorf("invalid cluster_type: %s", clusterTypeStr), api.ErrCodeInvalidArgument)
			return
		}
		clusterType = ccproto.ClusterType(ct)
	}

	tenantIDStr := query.TenantID
	var tenantID *uint64
	if tenantIDStr != "" {
		tid, err := strconv.ParseUint(tenantIDStr, 10, 64)
		if err != nil {
			ctrl.FailWithCode(ctx, fmt.Errorf("parse tenant_id failed: %v", err), api.ErrCodeInvalidArgument)
			return
		}
		t := tid
		tenantID = &t
	}
	if tenantID == nil {
		if ctrl.IsTenantRestricted(ctx) {
			ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id is required for this api key"), api.ErrCodePermissionDenied)
			return
		}
	} else if err := ctrl.CheckTenantScope(ctx, *tenantID); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodePermissionDenied)
		return
	}

	req := &ccproto.ListMetaProfilesRequest{
		ClusterType: clusterType,
		TenantId:    tenantID,
		Page:        query.Page,
		PageSize:    query.PerPage,
	}
	if query.ProfileName != "" {
		req.ProfileName = &query.ProfileName
	}

	profiles, err := c.svc.ListMetaProfiles(ctx, req)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	if profiles == nil {
		profiles = &ccproto.ListMetaProfilesResponse{}
	}
	if profiles.Profiles == nil {
		profiles.Profiles = []*ccproto.MetaProfile{}
	}

	respProfiles := make([]*ccapi.MetaProfile, 0, len(profiles.Profiles))
	for _, profile := range profiles.Profiles {
		respProfiles = append(respProfiles, &ccapi.MetaProfile{
			ClusterType:           ccent.ClusterType(ccproto.ClusterType_name[int32(profile.ClusterType)]),
			ProfileId:             profile.ProfileId,
			ProfileName:           profile.ProfileName,
			Description:           profile.Description,
			Tags:                  profile.Tags,
			ComponentImageTags:    profile.ComponentImageTags,
			ConfigCustomTemplates: profile.ConfigCustomTemplates,
			ComponentVersions:     profile.ComponentVersions,
		})
	}

	resp := &ccapi.ListMetaProfilesResp{
		Items: respProfiles,
		Total: profiles.Total,
	}
	ctrl.Success(ctx, resp)
}

// checkMetaProfileScope enforces per-role tenant scope on a loaded meta
// profile. The owning tenant id is encoded in tags under
// TEMPLATE_TAG_OWNER_TENANT_ID by config-center; an absent/unparseable tag
// means the profile is global, which we deny for restricted callers.
func (c *CloudConfigController) checkMetaProfileScope(ctx *gin.Context, tags map[string]string) error {
	tenantIDStr := tags[ccproto.TEMPLATE_TAG_OWNER_TENANT_ID]
	if tenantIDStr == "" {
		if ctrl.IsTenantRestricted(ctx) {
			return fmt.Errorf("global meta profile is not accessible with this api key")
		}
		return nil
	}
	tenantID, err := strconv.ParseUint(tenantIDStr, 10, 64)
	if err != nil {
		return fmt.Errorf("invalid owner tenant id %q on meta profile: %w", tenantIDStr, err)
	}
	return ctrl.CheckTenantScope(ctx, tenantID)
}

func validateMetaProfileComponentVersions(req *ccapi.CreateMetaProfileReq) error {
	for component := range req.ComponentImageTags {
		if req.ComponentVersions[component] == "" {
			return fmt.Errorf("component_versions[%s] is required when component_image_tags[%s] is set", component, component)
		}
	}
	for component, version := range req.ComponentVersions {
		if version == "" {
			return fmt.Errorf("component_versions[%s] is required", component)
		}
	}
	return nil
}
