package procedure

import (
	"context"

	"github.com/aws/aws-sdk-go-v2/aws"
	lambdav1alpha1 "github.com/crossplane/provider-aws/apis/lambda/v1alpha1"
	"github.com/tidbcloud/eks-provider/pkg/consts"

	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

const (
	lambdaFuncName        = "lambda-func-svc"
	lambdaFuncDescription = "TiDB cloud lambda function used to establish network connectivity between TiDB cloud and EKS."
	lambdaFuncHandler     = "main"
	lambdaFuncRuntime     = "go1.x"
	lambdaFuncBucketKey   = "s3-func-bucket"
	lambdaFuncObjectKey   = "s3-func-obj"
	lambdaTimeout         = 3
)

type lambdaFunc struct{}

// Deploy deploys resource for the specified tenant.
func (f *lambdaFunc) Deploy(ctx context.Context, baseCli, infraCli client.Client, info ProcedureInfo) error {
	baseReader := getReader(baseCli)
	s3FuncBucket, err := GetConfigWithKey(ctx, baseReader, info, lambdaFuncBucketKey)
	if err != nil {
		return err
	}
	s3FuncObject, err := GetConfigWithKey(ctx, baseReader, info, lambdaFuncObjectKey)
	if err != nil {
		return err
	}
	function := getLambdaFunc(info, s3FuncBucket, s3FuncObject)
	_, err = controllerutil.CreateOrUpdate(ctx, infraCli, function, func() error {
		mutateLambdaFuncSpec(function, info, s3FuncBucket, s3FuncObject)
		return nil
	})
	return err
}

func mutateLambdaFuncSpec(lambda *lambdav1alpha1.Function, info ProcedureInfo, s3FuncBucket, s3FuncObject string) {
	lambda.Spec.ProviderConfigReference = GetCrossplaneProvider(info)
	lambda.Spec.ForProvider.Region = info.Region
	lambda.Spec.ForProvider.Description = aws.String(lambdaFuncDescription)
	lambda.Spec.ForProvider.Handler = aws.String(lambdaFuncHandler)
	lambda.Spec.ForProvider.Runtime = aws.String(lambdaFuncRuntime)
	lambda.Spec.ForProvider.Timeout = aws.Int64(lambdaTimeout)
	lambda.Spec.ForProvider.CustomFunctionVPCConfigParameters = &lambdav1alpha1.CustomFunctionVPCConfigParameters{}
	lambda.Spec.ForProvider.CustomFunctionVPCConfigParameters.SubnetIDs = aws.StringSlice(info.SubnetIDs)
	lambda.Spec.ForProvider.CustomFunctionVPCConfigParameters.SecurityGroupIDs = aws.StringSlice(info.SecurityGroupIDs)
	lambda.Spec.ForProvider.CustomFunctionCodeParameters.S3Bucket = aws.String(s3FuncBucket)
	lambda.Spec.ForProvider.CustomFunctionCodeParameters.S3Key = aws.String(s3FuncObject)
	lambda.Spec.ForProvider.Role = aws.String(info.eksConnection.Spec.AssumeRole)
	lambda.Spec.ForProvider.Tags = map[string]*string{consts.CrossplaneVendorKey: aws.String(consts.CrossplaneVendorValue)}
}

func getLambdaFunc(info ProcedureInfo, s3FuncBucket, s3FuncObject string) *lambdav1alpha1.Function {
	key := ResourceKey(info, lambdaFuncName)
	lambda := &lambdav1alpha1.Function{}
	lambda.Name = key.Name
	mutateLambdaFuncSpec(lambda, info, s3FuncBucket, s3FuncObject)
	setOwnerRef(&lambda.ObjectMeta, info)
	return lambda
}

// Status retrieves the status for the deploying step.
func (f *lambdaFunc) Status(ctx context.Context, baseCli, infraCli client.Reader, info ProcedureInfo) (bool, error) {
	key := ResourceKey(info, lambdaFuncName)
	key.Namespace = ""
	lambda := &lambdav1alpha1.Function{}
	err := infraCli.Get(ctx, key, lambda)
	if err != nil {
		return false, err
	}
	return isLambdaFuncReady(lambda), nil
}

// isLambdaFuncReady return true if the lambda function is ready.
func isLambdaFuncReady(lambda *lambdav1alpha1.Function) bool {
	return IsCrossplaneObjReady(lambda.Status.ConditionedStatus)
}

// Name returns the name of the step.
func (f *lambdaFunc) Name() string {
	return lambdaFuncName
}

// NewLambdaFunc returns the procedure for frps deployment.
func NewLambdaFunc() *lambdaFunc {
	return &lambdaFunc{}
}

var _ Procedure = &lambdaFunc{}
