package common

import (
	"fmt"
	"strconv"

	"google.golang.org/protobuf/types/known/emptypb"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapingv1beta2 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/nextgen/v1beta2"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

var NotSupportedCmekErrorMsg = "customer managed encryption key (CMEK) is only supported for AWS or Aliyun cloud provider with Premium service plan"

func BuildGlobalCmek(
	orgId uint64,
	cloudProviderId globalcommonv1.CloudProvider_Name,
	globalRegion globalcommonv1.Region_Name,
	servicePlan globalcommonv1.ServicePlan,
	key *openapingv1beta2.CustomerManagedEncryptionKey,
) (*globalngv1.CustomerManagedEncryptionKey, bizerr.BizError) {
	cmek := &globalngv1.CustomerManagedEncryptionKey{
		OrganizationId:  orgId,
		CloudProviderId: cloudProviderId,
		RegionId:        globalRegion,
		ServicePlan:     servicePlan,
	}

	switch {
	case cloudProviderId == globalcommonv1.CloudProvider_AWS && key.GetAwsKms() != nil:
		cmek.Key = CvtAwsCmekKeyToGlobal(key.GetAwsKms())
		cmek.Principal = &globalngv1.CustomerManagedEncryptionKey_AwsPrincipal{
			AwsPrincipal: &globalngv1.CmekAccessIamPrincipal_AwsCmekPrincipal{
				AccountId: key.GetAwsPrincipal().GetAccountId(),
			},
		}
	case cloudProviderId == globalcommonv1.CloudProvider_ALICLOUD && key.GetAliyunKms() != nil:
		cmek.Key = CvtAliyunCmekKeyToGlobal(key.GetAliyunKms())
		cmek.Principal = &globalngv1.CustomerManagedEncryptionKey_AliyunPrincipal{
			AliyunPrincipal: &globalngv1.CmekAccessIamPrincipal_AliyunCmekPrincipal{
				// If request not contains principal, get from nextGenCli.
				AccountId: key.GetAliyunPrincipal().GetAccountId(),
			},
		}
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "%s", NotSupportedCmekErrorMsg)
	}

	return cmek, nil
}

func CvtAwsCmekKeyToGlobal(kms *openapingv1beta2.CustomerManagedEncryptionKey_AwsKms) *globalngv1.CustomerManagedEncryptionKey_AwsKms_ {
	return &globalngv1.CustomerManagedEncryptionKey_AwsKms_{
		AwsKms: &globalngv1.CustomerManagedEncryptionKey_AwsKms{
			KmsKeyArn: kms.GetKmsKeyArn(),
		},
	}
}

func CvtAwsCmekPrincipalToGlobal(
	principal *openapingv1beta2.CmekAccessIamPrincipal_AwsCmekPrincipal,
) *globalngv1.CustomerManagedEncryptionKey_AwsPrincipal {
	return &globalngv1.CustomerManagedEncryptionKey_AwsPrincipal{
		AwsPrincipal: &globalngv1.CmekAccessIamPrincipal_AwsCmekPrincipal{
			AccountId: principal.GetAccountId(),
		},
	}
}

func CvtGlobalCmekToOpenapi(
	globalCmek *globalngv1.CustomerManagedEncryptionKey,
) (*openapingv1beta2.CustomerManagedEncryptionKey, bizerr.BizError) {
	_, regionId, cloudProvider, err := common.BuildOpenApiRegionName(
		globalCmek.GetCloudProviderId().String(),
		globalCmek.GetRegionId().String(),
	)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to build region ID")
	}

	cmek := &openapingv1beta2.CustomerManagedEncryptionKey{
		Name:                           fmt.Sprintf("cmeks/%d", globalCmek.GetCustomerManagedEncryptionKeyId()),
		CustomerManagedEncryptionKeyId: strconv.FormatUint(globalCmek.CustomerManagedEncryptionKeyId, 10),
		RegionId:                       regionId,
		CloudProvider:                  cloudProvider,
		ServicePlan:                    openapicommonv1beta1.ServicePlan(globalCmek.GetServicePlan()),
	}
	switch {
	case globalCmek.GetCloudProviderId() == globalcommonv1.CloudProvider_AWS && globalCmek.GetAwsKms() != nil && globalCmek.GetAwsPrincipal() != nil:
		cmek.Key = &openapingv1beta2.CustomerManagedEncryptionKey_AwsKms_{
			AwsKms: CvtGlobalAwsCmekKeyToOpenapi(globalCmek.GetAwsKms()),
		}
		cmek.Principal = &openapingv1beta2.CustomerManagedEncryptionKey_AwsPrincipal{
			AwsPrincipal: CvtGlobalAwsCmekPrincipalToOpenapi(globalCmek.GetAwsPrincipal()),
		}
	case globalCmek.GetCloudProviderId() == globalcommonv1.CloudProvider_ALICLOUD && globalCmek.GetAliyunKms() != nil && globalCmek.GetAliyunPrincipal() != nil:
		cmek.Key = &openapingv1beta2.CustomerManagedEncryptionKey_AliyunKms_{
			AliyunKms: CvtGlobalAliyunCmekKeyToOpenapi(globalCmek.GetAliyunKms()),
		}
		cmek.Principal = &openapingv1beta2.CustomerManagedEncryptionKey_AliyunPrincipal{
			AliyunPrincipal: CvtGlobalAliyunCmekPrincipalToOpenapi(globalCmek.GetAliyunPrincipal()),
		}
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "%s", NotSupportedCmekErrorMsg)
	}

	return cmek, nil
}

func CvtGlobalDualLayerDataEncryptionToOpenapi(
	in *globalngv1.Cluster_DualLayerDataEncryption,
) (*openapingv1beta2.Tidb_DualLayerDataEncryption, bizerr.BizError) {
	openAPIEncryption := &openapingv1beta2.Tidb_DualLayerDataEncryption{}
	switch {
	case in == nil, in.GetNoEncryption() != nil:
		openAPIEncryption.Key = &openapingv1beta2.Tidb_DualLayerDataEncryption_NoEncryption{
			NoEncryption: &emptypb.Empty{},
		}
	case in.GetDefaultKey() != nil:
		openAPIEncryption.Key = &openapingv1beta2.Tidb_DualLayerDataEncryption_DefaultKey{
			DefaultKey: &emptypb.Empty{},
		}
	case in.GetCmek() != nil:
		cmek, err := CvtGlobalCmekToOpenapi(in.GetCmek())
		if err != nil {
			return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid cmek configuration")
		}
		openAPIEncryption.Key = &openapingv1beta2.Tidb_DualLayerDataEncryption_Cmek{
			Cmek: cmek,
		}
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "invalid dual layer data encryption configuration")
	}
	return openAPIEncryption, nil
}

func CvtGlobalAwsCmekKeyToOpenapi(
	kms *globalngv1.CustomerManagedEncryptionKey_AwsKms,
) *openapingv1beta2.CustomerManagedEncryptionKey_AwsKms {
	if kms == nil {
		return nil
	}
	return &openapingv1beta2.CustomerManagedEncryptionKey_AwsKms{
		KmsKeyArn: kms.GetKmsKeyArn(),
	}
}

func CvtGlobalAwsCmekPrincipalToOpenapi(
	principal *globalngv1.CmekAccessIamPrincipal_AwsCmekPrincipal,
) *openapingv1beta2.CmekAccessIamPrincipal_AwsCmekPrincipal {
	if principal == nil {
		return nil
	}
	return &openapingv1beta2.CmekAccessIamPrincipal_AwsCmekPrincipal{
		AccountId:  principal.GetAccountId(),
		ExternalId: principal.GetExternalId(),
	}
}

func CvtAliyunCmekKeyToGlobal(kms *openapingv1beta2.CustomerManagedEncryptionKey_AliyunKms) *globalngv1.CustomerManagedEncryptionKey_AliyunKms_ {
	return &globalngv1.CustomerManagedEncryptionKey_AliyunKms_{
		AliyunKms: &globalngv1.CustomerManagedEncryptionKey_AliyunKms{
			KmsKeyArn: kms.GetKmsKeyArn(),
		},
	}
}

func CvtAliyunCmekPrincipalToGlobal(
	principal *openapingv1beta2.CmekAccessIamPrincipal_AliyunCmekPrincipal,
) *globalngv1.CustomerManagedEncryptionKey_AliyunPrincipal {
	return &globalngv1.CustomerManagedEncryptionKey_AliyunPrincipal{
		AliyunPrincipal: &globalngv1.CmekAccessIamPrincipal_AliyunCmekPrincipal{
			AccountId: principal.GetAccountId(),
		},
	}
}

func CvtGlobalAliyunCmekKeyToOpenapi(
	kms *globalngv1.CustomerManagedEncryptionKey_AliyunKms,
) *openapingv1beta2.CustomerManagedEncryptionKey_AliyunKms {
	if kms == nil {
		return nil
	}
	return &openapingv1beta2.CustomerManagedEncryptionKey_AliyunKms{
		KmsKeyArn: kms.GetKmsKeyArn(),
	}
}

func CvtGlobalAliyunCmekPrincipalToOpenapi(
	principal *globalngv1.CmekAccessIamPrincipal_AliyunCmekPrincipal,
) *openapingv1beta2.CmekAccessIamPrincipal_AliyunCmekPrincipal {
	if principal == nil {
		return nil
	}
	return &openapingv1beta2.CmekAccessIamPrincipal_AliyunCmekPrincipal{
		AccountId: principal.GetAccountId(),
	}
}

// CvtOpenapiCmekToGlobalCmek converts an OpenAPI CMEK to a global CMEK for forwarding.
// Unlike BuildGlobalCmek, this only copies key and principal fields — the global service
// resolves org, region, and provider from the cluster context.
func CvtOpenapiCmekToGlobalCmek(
	key *openapingv1beta2.CustomerManagedEncryptionKey,
) *globalngv1.CustomerManagedEncryptionKey {
	if key == nil {
		return nil
	}
	cmek := &globalngv1.CustomerManagedEncryptionKey{}
	switch {
	case key.GetAwsKms() != nil:
		cmek.Key = CvtAwsCmekKeyToGlobal(key.GetAwsKms())
		if key.GetAwsPrincipal() != nil {
			cmek.Principal = CvtAwsCmekPrincipalToGlobal(key.GetAwsPrincipal())
		}
	case key.GetAliyunKms() != nil:
		cmek.Key = CvtAliyunCmekKeyToGlobal(key.GetAliyunKms())
		if key.GetAliyunPrincipal() != nil {
			cmek.Principal = CvtAliyunCmekPrincipalToGlobal(key.GetAliyunPrincipal())
		}
	}
	return cmek
}

// CvtGlobalEncryptionStatusStateToOpenapi maps global ClusterEncryptionStatus.State
// to customer-facing TidbDualLayerEncryption.State.
func CvtGlobalEncryptionStatusStateToOpenapi(
	state globalngv1.ClusterEncryptionStatus_State,
) openapingv1beta2.TidbDualLayerEncryption_State {
	switch state {
	case globalngv1.ClusterEncryptionStatus_MODIFYING:
		return openapingv1beta2.TidbDualLayerEncryption_MODIFYING
	case globalngv1.ClusterEncryptionStatus_ENABLED:
		return openapingv1beta2.TidbDualLayerEncryption_ENABLED
	default:
		return openapingv1beta2.TidbDualLayerEncryption_STATE_UNSPECIFIED
	}
}

// CvtGlobalEncryptionStatusToOpenapi converts a global ClusterEncryptionStatus to an OpenAPI TidbDualLayerEncryption.
func CvtGlobalEncryptionStatusToOpenapi(
	status *globalngv1.ClusterEncryptionStatus,
	tidbId string,
) *openapingv1beta2.TidbDualLayerEncryption {
	result := &openapingv1beta2.TidbDualLayerEncryption{
		TidbId:  tidbId,
		State:   CvtGlobalEncryptionStatusStateToOpenapi(status.GetState()),
		Message: status.GetMessage(),
	}

	if status.GetCurrentEncryption() != nil {
		current, err := CvtGlobalDualLayerDataEncryptionToOpenapi(status.GetCurrentEncryption())
		if err == nil {
			result.Encryption = current
		}
	}

	return result
}
