package procedure

import (
	"context"

	"github.com/aws/aws-sdk-go-v2/aws"
	lambdav1alpha1 "github.com/crossplane-contrib/provider-aws/apis/lambda/v1alpha1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/util/collections"
)

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 {
	scheme *runtime.Scheme
}

// Deploy deploys resource for the specified tenant.
func (f *lambdaFunc) Deploy(ctx context.Context, baseCli, infraCli client.Client, info ProcedureInfo) error {
	baseReader := AsReader(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)
		err := controllerutil.SetControllerReference(info.eksConnection, function, f.scheme)
		if IsAlreadyOwnedError(err) {
			return nil
		}
		return err

	})
	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.assumeRole)
	lambda.Spec.ForProvider.Tags = map[string]*string{consts.TagKeyCrossplaneVendor: aws.String(consts.TagValCrossplaneVendor)}
	// lambda owner is eks connection, so we need add eks connection labels to lambda labels
	lambda.Labels = collections.MergeMaps(info.eksConnection.Labels, info.eksConnection.Labels)
}

func getLambdaFunc(info ProcedureInfo, s3FuncBucket, s3FuncObject string) *lambdav1alpha1.Function {
	lambda := &lambdav1alpha1.Function{}
	lambda.Name = namer.UniqueIdentifier(info.eksConnection.ObjectMeta)
	mutateLambdaFuncSpec(lambda, info, s3FuncBucket, s3FuncObject)
	return lambda
}

// Status retrieves the status for the deploying step.
func (f *lambdaFunc) Status(ctx context.Context, baseCli client.Reader, infraCli client.Client, info ProcedureInfo) (bool, error) {
	lambda := &lambdav1alpha1.Function{}
	err := infraCli.Get(ctx, types.NamespacedName{Name: namer.UniqueIdentifier(info.eksConnection.ObjectMeta)}, 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(s *runtime.Scheme) *lambdaFunc {
	return &lambdaFunc{scheme: s}
}

var _ Procedure = &lambdaFunc{}
