//go:build unit || all
// +build unit all

package procedure

import (
	"context"
	"testing"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go/service/autoscaling/autoscalingiface"
	"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
	"github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
	"github.com/aws/aws-sdk-go/service/lambda/lambdaiface"
	"github.com/aws/aws-sdk-go/service/s3/s3iface"
	"github.com/aws/aws-sdk-go/service/sts/stsiface"
	cpv1beta1 "github.com/crossplane-contrib/provider-aws/apis/eks/v1beta1"
	lambdav1alpha1 "github.com/crossplane-contrib/provider-aws/apis/lambda/v1alpha1"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	. "github.com/onsi/gomega"
	eksv1alpha1 "github.com/tidbcloud/eks-provider/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes/scheme"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

const (
	fakeFrpcImageURL = "fake.repository.io/frp/frpc"
)

var (
	fakeLambdaInvokeInfo     ProcedureInfo
	fakeLambdaInvokeFuncName string
	fakeInvokeLambdaEKS      *cpv1beta1.Cluster
	fakeInvokeLambdaFunction *lambdav1alpha1.Function
	fakeInvokeConnection     *eksv1alpha1.EKSConnection
	fakeInvokePrivatelink    *eksv1alpha1.Privatelink
)

func init() {
	fakeLambdaInvokeInfo.Region = "us-west-2"
	fakeLambdaInvokeInfo.Cluster = eksName
	fakeLambdaInvokeInfo.ProviderConfigRef = "123456789012"
	fakeLambdaInvokeInfo.SubnetIDs = []string{"subnet1", "subnet2"}
	fakeLambdaInvokeInfo.eksConnection = &eksv1alpha1.EKSConnection{}
	fakeLambdaInvokeInfo.eksConnection.Spec.Target = &corev1.ObjectReference{}
	fakeLambdaInvokeInfo.eksConnection.Spec.Target.Name = eksName
	fakeLambdaInvokeInfo.eksConnection.Spec.Target.Namespace = eksNamespace
	fakeLambdaInvokeInfo.namespace = ProcedureNamespace
	fakeLambdaInvokeFuncName = namer.UniqueIdentifier(fakeLambdaInvokeInfo.eksConnection.ObjectMeta)
	fakeInvokeLambdaEKS = &cpv1beta1.Cluster{}
	fakeInvokeLambdaEKS.Name = eksName
	fakeInvokeLambdaEKS.Status.AtProvider.Arn = "fake-arn"
	fakeInvokeLambdaEKS.Status.AtProvider.Endpoint = "fake-endpoint"
	fakeInvokeLambdaFunction = &lambdav1alpha1.Function{}
	fakeInvokeLambdaFunction.Annotations = map[string]string{
		meta.AnnotationKeyExternalName: "fake-lambda-function-name",
	}
	fakeInvokeLambdaFunction.Name = namer.UniqueIdentifier(fakeLambdaInvokeInfo.eksConnection.ObjectMeta)
	fakeInvokeLambdaFunction.Status.AtProvider.FunctionARN = aws.String("fake-function-arn")
	fakeInvokePrivatelink = &eksv1alpha1.Privatelink{}
	fakeInvokePrivatelink.Name = namer.UniqueIdentifier(fakeLambdaInvokeInfo.eksConnection.ObjectMeta)
	fakeInvokePrivatelink.Annotations = map[string]string{
		AnnotationKeyEndpointDNS: "fake-endpoint-dns-name",
	}
}

type fakeInvokeTokenMngr struct {
	fakeToken string
	err       error
}

func (f *fakeInvokeTokenMngr) RetrieveToken(ctx context.Context, opt k8s.EKSBearerTokenOpt) (string, error) {
	return f.fakeToken, f.err
}

func fakeTokenMngr(token string, err error) k8s.EKSTokenManager {
	return &fakeInvokeTokenMngr{
		fakeToken: token,
		err:       err,
	}
}

type fakeAwsSvc struct {
	lambda lambdaiface.LambdaAPI
}

func (f *fakeAwsSvc) EC2API(role *AssumedRole) ec2iface.EC2API {
	return nil
}

func (f *fakeAwsSvc) ELBV2API(role *AssumedRole) elbv2iface.ELBV2API {
	return nil
}

func (f *fakeAwsSvc) LambdaAPI(role *AssumedRole) lambdaiface.LambdaAPI {
	return f.lambda
}

func (a *fakeAwsSvc) StsAPI(role *AssumedRole) stsiface.STSAPI {
	return nil
}

func (a *fakeAwsSvc) AutoScalingAPI(role *AssumedRole) autoscalingiface.AutoScalingAPI {
	return nil
}

func (a *fakeAwsSvc) S3API(role *AssumedRole) s3iface.S3API {
	return nil
}

var _ AWSService = &fakeAwsSvc{}

func TestLambdaInvokeDeploy(t *testing.T) {
	cases := map[string]struct {
		info             ProcedureInfo
		objs             []client.Object
		expectErr        bool
		lambdaAPI        lambdaiface.LambdaAPI
		tokMngr          k8s.EKSTokenManager
		fakeSvcDNS       string
		fakeGetSvcDNSErr error
	}{
		"invoke-success": {
			info:      fakeLambdaInvokeInfo,
			expectErr: false,
			objs: []client.Object{
				fakeInvokeLambdaFunction,
				fakeInvokeLambdaEKS,
				fakeLambdaInvokeInfo.eksConnection,
				fakeInvokePrivatelink,
			},
			lambdaAPI:        fakeLambdaAPI(nil),
			tokMngr:          fakeTokenMngr("fake-token", nil),
			fakeSvcDNS:       "fake-svc-dns",
			fakeGetSvcDNSErr: nil,
		},
	}
	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			s := runtime.NewScheme()
			g.Expect(scheme.AddToScheme(s)).To(Succeed())
			g.Expect(cpv1beta1.SchemeBuilder.AddToScheme(s)).To(Succeed())
			g.Expect(lambdav1alpha1.SchemeBuilder.AddToScheme(s)).To(Succeed())
			g.Expect(eksv1alpha1.SchemeBuilder.AddToScheme(s)).To(Succeed())
			cli := fake.NewClientBuilder().WithScheme(s).WithObjects(tc.objs...).Build()
			g.Expect(SetConfigWithKey(context.TODO(), cli, tc.info, frpcImageKey, fakeFrpcImageURL)).To(Succeed())
			p := InvokeLambda(tc.tokMngr, &fakeAwsSvc{lambda: tc.lambdaAPI})
			err := p.Deploy(context.TODO(), cli, cli, tc.info)
			if !tc.expectErr {
				g.Expect(err).To(Succeed())
			} else {
				g.Expect(err).NotTo(Succeed())
			}
		})
	}
}
