package cmek

import (
	"context"
	"fmt"
	"strconv"

	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.opentelemetry.io/otel"
	"google.golang.org/protobuf/types/known/emptypb"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

var tracer = otel.Tracer("App.Dedicated.CMEK")

func (s serverImpl) CreateCustomerManagedEncryptionKey(
	ctx context.Context,
	request *openapidedicatedv1beta1.CreateCustomerManagedEncryptionKeyRequest,
) (*openapidedicatedv1beta1.CustomerManagedEncryptionKey, error) {
	ctx, span := tracer.Start(ctx, "CreateCustomerManagedEncryptionKey")
	defer span.End()
	var (
		err     error
		reqMeta *metadata.RequestMeta
	)

	detailsFunc := func() auditlog.Details {
		details := eventbus.NewAuditLogDetails(eventbus.TierTypeDedicated)
		if request.GetCustomerManagedEncryptionKey() != nil {
			details[auditlog.KeyProjectName] = request.GetCustomerManagedEncryptionKey().GetProjectId()
			details[auditlog.KeyRegion] = request.GetCustomerManagedEncryptionKey().GetRegionId()
			provider, _, parseErr := common.ParseOpenApiRegionNameOrId(request.GetCustomerManagedEncryptionKey().GetRegionId())
			if parseErr == nil {
				details[auditlog.KeyCloudProvider] = provider
			}
		}
		return details
	}
	defer func() {
		s.auditLogSender.SubmitAuditLog(ctx, auditlog.EventUpdateCluster, reqMeta, detailsFunc, err)
	}()

	reqMeta, err = metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	globalCmek, err := cmekToGlobal(request.GetCustomerManagedEncryptionKey(), reqMeta)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to convert cmek for creating")
	}

	resp, err := s.dedicatedCli.CreateCustomerManagedEncryptionKey(
		ctx,
		&globaldedicatedv1.CreateCustomerManagedEncryptionKeyRequest{
			CustomerManagedEncryptionKey: globalCmek,
		},
	)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to create customer managed encryption key")
	}
	key, err := cmekToOpenApi(resp)

	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to convert cmek after creating")
	}

	return key, nil
}

func (s serverImpl) GetCustomerManagedEncryptionKey(
	ctx context.Context,
	request *openapidedicatedv1beta1.GetCustomerManagedEncryptionKeyRequest,
) (*openapidedicatedv1beta1.CustomerManagedEncryptionKey, error) {
	ctx, span := tracer.Start(ctx, "GetCustomerManagedEncryptionKey")
	defer span.End()

	_, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	cmekId, err := strconv.ParseUint(request.CustomerManagedEncryptionKeyId, 10, 64)
	if err != nil {
		return nil, bizerror.NewError(err, bizerr.ErrCodeParams, "invalid CustomerManagedEncryptionKeyId")
	}

	globalDedicatedCmek, err := s.dedicatedCli.GetCustomerManagedEncryptionKey(
		ctx,
		&globaldedicatedv1.GetCustomerManagedEncryptionKeyRequest{
			CustomerManagedEncryptionKeyId: cmekId,
		},
	)

	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to get customer managed encryption key")
	}

	cmek, err := cmekToOpenApi(globalDedicatedCmek)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to convert cmek after getting")
	}
	return cmek, nil
}

func (s serverImpl) ListCustomerManagedEncryptionKeys(
	ctx context.Context,
	request *openapidedicatedv1beta1.ListCustomerManagedEncryptionKeysRequest,
) (*openapidedicatedv1beta1.ListCustomerManagedEncryptionKeysResponse, error) {
	ctx, span := tracer.Start(ctx, "ListCustomerManagedEncryptionKeys")
	defer span.End()

	reqMeta, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	globalDedicatedReq := &globaldedicatedv1.ListCustomerManagedEncryptionKeysRequest{
		OrganizationId: reqMeta.OrgID,
	}

	projectId := reqMeta.DefaultProjectID
	if request.GetProjectId() != "" {
		projectId, err = strconv.ParseUint(request.GetProjectId(), 10, 64)
		if err != nil {
			return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid project id")
		}
	}

	if projectId == 0 {
		return nil, bizerror.NewError(nil, bizerr.ErrCodeParams, "No project id provided")
	}

	globalDedicatedReq.ProjectIds = []uint64{projectId}

	globalDedicatedResp, err := s.dedicatedCli.ListCustomerManagedEncryptionKeys(ctx, globalDedicatedReq)

	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to list customer managed encryption keys")
	}

	cmeks := make(
		[]*openapidedicatedv1beta1.CustomerManagedEncryptionKey,
		0,
		len(globalDedicatedResp.CustomerManagedEncryptionKeys),
	)

	for _, cmek := range globalDedicatedResp.CustomerManagedEncryptionKeys {
		oCmek, err := cmekToOpenApi(cmek)
		if err != nil {
			return nil, bizerror.NewInternal(err, "failed to convert cmek after listing")
		}
		cmeks = append(cmeks, oCmek)
	}

	resp := &openapidedicatedv1beta1.ListCustomerManagedEncryptionKeysResponse{}
	resp.Cmeks = cmeks
	return resp, nil
}

func (s serverImpl) DeleteCustomerManagedEncryptionKey(
	ctx context.Context,
	request *openapidedicatedv1beta1.DeleteCustomerManagedEncryptionKeyRequest,
) (*emptypb.Empty, error) {
	ctx, span := tracer.Start(ctx, "DeleteCustomerManagedEncryptionKey")
	defer span.End()
	var (
		err     error
		reqMeta *metadata.RequestMeta
	)

	detailsFunc := func() auditlog.Details {
		details := eventbus.NewAuditLogDetails(eventbus.TierTypeDedicated)
		details[auditlog.KeyDescription] = request.GetCustomerManagedEncryptionKeyId()
		return details
	}
	defer func() {
		s.auditLogSender.SubmitAuditLog(ctx, auditlog.EventUpdateCluster, reqMeta, detailsFunc, err)
	}()

	reqMeta, err = metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	cmekId, err := strconv.ParseUint(request.CustomerManagedEncryptionKeyId, 10, 64)
	if err != nil {
		return nil, bizerror.NewError(err, bizerr.ErrCodeParams, "invalid CustomerManagedEncryptionKeyId")
	}

	_, err = s.dedicatedCli.DeleteCustomerManagedEncryptionKey(
		ctx,
		&globaldedicatedv1.DeleteCustomerManagedEncryptionKeyRequest{
			CustomerManagedEncryptionKeyId: cmekId,
		},
	)

	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to delete customer managed encryption key")
	}

	return &emptypb.Empty{}, nil
}

func (s serverImpl) ShowCmekAccessIamPrincipal(
	ctx context.Context,
	request *openapidedicatedv1beta1.ShowCmekAccessIamPrincipalRequest,
) (*openapidedicatedv1beta1.ShowCmekAccessIamPrincipalResponse, error) {
	ctx, span := tracer.Start(ctx, "ShowCmekAccessIamPrincipal")
	defer span.End()

	reqMeta, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	globalRequest := &globaldedicatedv1.GetCmekAccessIamPrincipalRequest{
		OrganizationId: reqMeta.OrgID,
	}

	cloudProvider, region, err := common.ParseOpenApiRegionNameOrId(request.GetRegionId())
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid region id")
	}

	globalProvider, err := enum.FromString[globalcommonv1.CloudProvider_Name](cloudProvider)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "provider %s is not supported", cloudProvider)
	}

	globalRegion, err := enum.FromString[globalcommonv1.Region_Name](region)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "region %s is not supported", region)
	}

	globalRequest.RegionId = globalcommonv1.Region_Name(globalRegion)
	globalRequest.CloudProviderId = globalcommonv1.CloudProvider_Name(globalProvider)

	projectId := reqMeta.DefaultProjectID
	if request.GetProjectId() != "" {
		projectId, err = strconv.ParseUint(request.GetProjectId(), 10, 64)
		if err != nil {
			return nil, bizerror.NewError(err, bizerr.ErrCodeParams, "invalid ProjectId")
		}
	}

	globalRequest.ProjectId = projectId

	resp, err := s.dedicatedCli.GetCmekAccessIamPrincipal(ctx, globalRequest)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to get cmek access iam principal")
	}

	o := &openapidedicatedv1beta1.ShowCmekAccessIamPrincipalResponse{
		CloudProvider: request.CloudProvider,
		ProjectId:     request.ProjectId,
		RegionId:      request.RegionId,
	}
	switch globalRequest.CloudProviderId {
	case globalcommonv1.CloudProvider_AWS:
		o.Principal = &openapidedicatedv1beta1.ShowCmekAccessIamPrincipalResponse_AwsCmekPrincipal_{
			AwsCmekPrincipal: &openapidedicatedv1beta1.ShowCmekAccessIamPrincipalResponse_AwsCmekPrincipal{
				AccountId: resp.GetIamPrincipal(),
			},
		}
	case globalcommonv1.CloudProvider_AZURE:
		o.Principal = &openapidedicatedv1beta1.ShowCmekAccessIamPrincipalResponse_AzureCmekPrincipal_{
			AzureCmekPrincipal: &openapidedicatedv1beta1.ShowCmekAccessIamPrincipalResponse_AzureCmekPrincipal{
				AppRegistration: appRegistrationToOpenApi(
					resp.GetAzureCmekPrincipal().GetAzureAppRegistration(),
				),
				KeyVaultKeySuffix: resp.GetAzureCmekPrincipal().GetAzureKeyVaultKeySuffix(),
			},
		}
	default:
		return nil, bizerror.NewError(nil, bizerr.ErrCodeParams, "unsupported cloud provider")
	}

	return o, nil
}

func cmekToGlobal(
	o *openapidedicatedv1beta1.CustomerManagedEncryptionKey,
	reqMeta *metadata.RequestMeta,
) (*globaldedicatedv1.CustomerManagedEncryptionKey, error) {
	if o == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "customer managed encryption key is nil")
	}

	orgId := reqMeta.OrgID
	projectId := reqMeta.DefaultProjectID

	if o.ProjectId != "" {
		pid, err := strconv.ParseUint(o.ProjectId, 10, 64)
		if err != nil {
			return nil, fmt.Errorf("failed to parse ProjectId: %w", err)
		}
		projectId = pid
	}

	cloudProvider, region, err := common.ParseOpenApiRegionNameOrId(o.GetRegionId())
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid region id")
	}

	globalProvider, err := enum.FromString[globalcommonv1.CloudProvider_Name](cloudProvider)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "provider %s is not supported", cloudProvider)
	}

	globalRegion, err := enum.FromString[globalcommonv1.Region_Name](region)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "region %s is not supported", region)
	}

	g := &globaldedicatedv1.CustomerManagedEncryptionKey{
		ProjectId:       projectId,
		OrganizationId:  orgId,
		RegionId:        globalcommonv1.Region_Name(globalRegion),
		CloudProviderId: globalcommonv1.CloudProvider_Name(globalProvider),
	}

	switch g.CloudProviderId {
	case globalcommonv1.CloudProvider_AWS:
		if o.GetAwsKms() != nil {
			g.MasterKey = &globaldedicatedv1.CustomerManagedEncryptionKey_AwsKeyArn{
				AwsKeyArn: o.GetAwsKms().KmsKeyArn,
			}
		}
	case globalcommonv1.CloudProvider_AZURE:
		if o.GetAzureKeyVault() != nil {
			g.MasterKey = &globaldedicatedv1.CustomerManagedEncryptionKey_AzureKeyVault{
				AzureKeyVault: azureKeyVaultToGlobal(o.GetAzureKeyVault()),
			}
		}
	default:
		return nil, fmt.Errorf("unsupported cloud provider: %s", g.CloudProviderId.String())
	}
	return g, nil
}

func cmekToOpenApi(
	g *globaldedicatedv1.CustomerManagedEncryptionKey,
) (*openapidedicatedv1beta1.CustomerManagedEncryptionKey, error) {
	if g == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "customer managed encryption key is nil")
	}

	_, regionId, _, err := common.BuildOpenApiRegionName(
		g.GetCloudProviderId().String(),
		g.GetRegionId().String(),
	)
	if err != nil {
		return nil, err
	}

	o := &openapidedicatedv1beta1.CustomerManagedEncryptionKey{
		Name:                           fmt.Sprintf("cmeks/%d", g.Id),
		CustomerManagedEncryptionKeyId: fmt.Sprintf("%d", g.Id),
		ProjectId:                      fmt.Sprintf("%d", g.ProjectId),
		RegionId:                       regionId,
		RegionDisplayName:              g.GetRegionDisplayName(),
		CreateTime:                     g.GetCreateTime(),
	}

	switch g.GetCloudProviderId() {
	case globalcommonv1.CloudProvider_AWS:
		o.Key = &openapidedicatedv1beta1.CustomerManagedEncryptionKey_AwsKms{
			AwsKms: &openapidedicatedv1beta1.AwsKms{
				KmsKeyArn: g.GetMasterKey().(*globaldedicatedv1.CustomerManagedEncryptionKey_AwsKeyArn).AwsKeyArn,
			},
		}
	case globalcommonv1.CloudProvider_AZURE:
		if g.GetMasterKey() != nil {
			o.Key = &openapidedicatedv1beta1.CustomerManagedEncryptionKey_AzureKeyVault{
				AzureKeyVault: azureKeyToOpenApi(
					g.GetMasterKey().(*globaldedicatedv1.CustomerManagedEncryptionKey_AzureKeyVault).AzureKeyVault,
				),
			}
		}
	}

	return o, nil
}

func azureKeyVaultToGlobal(o *openapidedicatedv1beta1.AzureKeyVault) *globaldedicatedv1.AzureKeyVault {
	if o == nil {
		return nil
	}
	return &globaldedicatedv1.AzureKeyVault{
		KeyVaultKey:          o.KeyVaultKey,
		KeyVaultName:         o.KeyVaultName,
		AzureAppRegistration: appRegistrationToGlobal(o.AppRegistration),
	}
}

func azureKeyToOpenApi(g *globaldedicatedv1.AzureKeyVault) *openapidedicatedv1beta1.AzureKeyVault {
	if g == nil {
		return nil
	}
	return &openapidedicatedv1beta1.AzureKeyVault{
		KeyVaultKey:     g.KeyVaultKey,
		KeyVaultName:    g.KeyVaultName,
		AppRegistration: appRegistrationToOpenApi(g.AzureAppRegistration),
	}
}

func appRegistrationToGlobal(
	o *openapidedicatedv1beta1.AzureAppRegistration,
) *globaldedicatedv1.AzureAppRegistration {
	if o == nil {
		return nil
	}
	return &globaldedicatedv1.AzureAppRegistration{
		AppRegistrationId:   o.AppRegistrationId,
		AppRegistrationName: o.AppRegistrationName,
	}
}

func appRegistrationToOpenApi(g *globaldedicatedv1.AzureAppRegistration) *openapidedicatedv1beta1.AzureAppRegistration {
	if g == nil {
		return nil
	}
	return &openapidedicatedv1beta1.AzureAppRegistration{
		AppRegistrationId:   g.AppRegistrationId,
		AppRegistrationName: g.AppRegistrationName,
	}
}
