package encryptionkey

import (
	"context"
	"strconv"

	dataplanemanagerv1 "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/dataplane-manager/v1"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/encryptionkey"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

func (s *service) CreateDefaultKeyOrGrantPermission(ctx context.Context,
	organizationId uint64,
	regionProviderIdentityId uint64,
	dataplaneId string) (*entity.EncryptionKey, bizerr.BizError) {
	if organizationId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "organization id is required")
	}
	if regionProviderIdentityId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "providerIdentity is required")
	}
	if dataplaneId == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "dataplane is required")
	}

	identity, err := s.regionProviderIdentityRepo.Load(ctx, regionProviderIdentityId)
	if err != nil {
		return nil, err
	}
	if identity == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "identity not found")
	}

	region, err := s.regionRepo.Get(ctx, identity.RegionId)
	if err != nil {
		return nil, err
	}

	defaultKey, err := s.encryptionKeyRepo.GetDefaultEncryptionKey(ctx, organizationId,
		identity.RegionId,
		identity.Id)

	if err != nil {
		if err.ErrCode() == bizerr.ErrCodeRecordNotFound {
			tilog.Debug(ctx, "default key not found, create a new one")
			return s.createDefaultKey(ctx, organizationId, identity, region, dataplaneId)
		}
		return nil, err
	}

	err = s.nextgenCli.AddEncryptionKeyTrustedDataplane(ctx, region, &dataplanemanagerv1.AddTrustedDataplaneRequest{
		DataplaneId:        defaultKey.InitialDataplaneId,
		KeyId:              strconv.FormatUint(defaultKey.Id, 10),
		TrustedDataplaneId: dataplaneId,
	})
	if err != nil {
		return nil, cloudResourcesNotReadyError(err, "grant default encryption key to dataplane failed")
	}

	return s.encryptionKeyRepo.Load(ctx, defaultKey.Id, false)
}

func (s *service) ListEncryptionByIds(ctx context.Context,
	encryptionIds []uint64,
) ([]*entity.EncryptionKey, bizerr.BizError) {
	if len(encryptionIds) == 0 {
		return []*entity.EncryptionKey{}, nil
	}
	encryptionKeys, err := s.encryptionKeyRepo.ListEncryptionKeys(ctx, &encryptionkey.Filter{
		Ids: encryptionIds,
	})
	if err != nil {
		return nil, bizerr.WrapError(err, "list encryption keys failed")
	}
	return encryptionKeys, nil
}

func (s *service) createDefaultKey(ctx context.Context,
	organizationId uint64,
	identity *entity.RegionProviderIdentity,
	cloudRegion *entity.Region,
	dataplaneId string) (*entity.EncryptionKey, bizerr.BizError) {
	var encryptionKey *entity.EncryptionKey
	tErr := s.transaction.Transaction(ctx, func(ctx context.Context) error {
		id := s.idGeneratorAdapter.MustNew()
		encryptionKey = &entity.EncryptionKey{
			Id:                 id,
			TenantId:           organizationId,
			ProviderName:       cloudRegion.CloudProviderId,
			RegionName:         cloudRegion.Id,
			ServicePlan:        cloudRegion.ServicePlan,
			ProviderIdentityId: identity.Id,
			CustomerManaged:    false,
			MasterKey:          "",
			InitialDataplaneId: dataplaneId,

			ProviderId: cloudRegion.ProviderID,
			RegionId:   cloudRegion.RegionID,
		}
		innerErr := s.encryptionKeyRepo.Create(ctx, encryptionKey)
		if innerErr != nil {
			return innerErr
		}

		metadataJson, merr := metadata.MarshalRequestMetaFromCtx(ctx)
		if merr != nil {
			tilog.Warn(ctx, "failed to marshal request metadata", zap.Error(merr))
		}

		workflowErr := s.workflow.RunDagWithBizId(
			ctx,
			common.CreateDefaultEncryptionKey,
			strconv.FormatUint(encryptionKey.TenantId, 10),
			"0", // nextgen cluster don't have project id
			strconv.FormatUint(encryptionKey.Id, 10),
			map[string]string{
				workflow.KeyTraceId:       workflow.GetTraceId(ctx),
				workflow.KeyRequestMeta:   string(metadataJson),
				common.KeyEncryptionKeyId: strconv.FormatUint(encryptionKey.Id, 10),
			},
		)
		if workflowErr != nil {
			return bizerr.WrapErrorWithCode(workflowErr, bizerr.ErrCodeUnknown, "create async workflow failed")
		}

		return nil
	})
	if tErr != nil {
		return nil, bizerr.WrapErrorWithCode(tErr, bizerr.GeneralInternalErrorCode, "create default key failed")
	}
	return encryptionKey, nil
}
