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

package privatelink

import (
	"context"
	"fmt"
	"testing"

	ec2v1alpha1 "github.com/crossplane-contrib/provider-aws/apis/ec2/v1alpha1"
	cpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"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/namer"
	"github.com/tidbcloud/infra-provider/pkg/procedure"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/kubernetes/scheme"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

const (
	fakeEpSvcArn           = "fake-ep-svc-arn"
	fakeVpcEndpointSvcName = "vpce-svc-12345678901234567"
)

var (
	fakeVpcEndpointSvcInfo ProcedureInfo
	fakeVpcEndpointSvcKey  types.NamespacedName
)

func init() {
	fakeVpcEndpointSvcInfo.Region = "us-west-2"
	fakeVpcEndpointSvcInfo.VpcID = "vpc-123456789012"
	fakeVpcEndpointSvcInfo.privatelink = &eksv1alpha1.Privatelink{}
	fakeVpcEndpointSvcInfo.namespace = procedure.ProcedureNamespace
	fakeVpcEndpointSvcKey = types.NamespacedName{Name: namer.UniqueIdentifier(fakeVpcEndpointSvcInfo.privatelink.ObjectMeta)}
}

type fakeVpcEpSvcArnGetterGetter struct {
	arn string
	err error
}

func (f *fakeVpcEpSvcArnGetterGetter) getSvcNlbArn(ctx context.Context, cli client.Reader, info ProcedureInfo) (string, error) {
	return f.arn, f.err
}

func (d *fakeVpcEpSvcArnGetterGetter) addAllowedPrincipal(ctx context.Context, cli client.Reader, info ProcedureInfo) error {
	return nil
}

func fakeVpcEpSvcArnGetter(arn string, err error) vpcPrivateLinkClient {
	return &fakeVpcEpSvcArnGetterGetter{
		arn: arn,
		err: err,
	}
}

func TestVpcEndpointSvcDeploy(t *testing.T) {
	cases := map[string]struct {
		info      ProcedureInfo
		objs      []client.Object
		arnGetter vpcPrivateLinkClient
		expectErr bool
	}{
		"create-success": {
			info:      fakeVpcEndpointSvcInfo,
			arnGetter: fakeVpcEpSvcArnGetter(fakeEpSvcArn, nil),
			expectErr: false,
		},
		"ep-service-already-exist": {
			info:      fakeVpcEndpointSvcInfo,
			arnGetter: fakeVpcEpSvcArnGetter(fakeEpSvcArn, nil),
			objs: []client.Object{
				&ec2v1alpha1.VPCEndpointServiceConfiguration{
					ObjectMeta: metav1.ObjectMeta{
						Name: fakeVpcEndpointSvcKey.Name,
					},
				},
			},
			expectErr: false,
		},
		"ep-service-get-nlb-arn-err": {
			info:      fakeVpcEndpointSvcInfo,
			arnGetter: fakeVpcEpSvcArnGetter("", fmt.Errorf("error")),
			objs: []client.Object{
				&ec2v1alpha1.VPCEndpointServiceConfiguration{
					ObjectMeta: metav1.ObjectMeta{
						Name: fakeVpcEndpointSvcKey.Name,
					},
				},
			},
			expectErr: true,
		},
	}
	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(ec2v1alpha1.SchemeBuilder.AddToScheme(s)).To(Succeed())
			g.Expect(eksv1alpha1.SchemeBuilder.AddToScheme(s)).To(Succeed())
			cli := fake.NewClientBuilder().WithScheme(s).WithObjects(tc.objs...).Build()
			p := NewVpcEpSvc(tc.arnGetter, s)
			err := p.Deploy(context.TODO(), cli, cli, tc.info)
			if !tc.expectErr {
				g.Expect(err).To(Succeed())
			} else {
				g.Expect(err).NotTo(Succeed())
			}
		})
	}
}

func TestVpcEndpointSvcStatus(t *testing.T) {
	addCondition := func(obj *ec2v1alpha1.VPCEndpointServiceConfiguration, cond cpv1.Condition) *ec2v1alpha1.VPCEndpointServiceConfiguration {
		obj.Status.ConditionedStatus.SetConditions(cond)
		return obj
	}
	setExternalName := func(obj *ec2v1alpha1.VPCEndpointServiceConfiguration, name string) *ec2v1alpha1.VPCEndpointServiceConfiguration {
		if obj.Annotations == nil {
			obj.Annotations = make(map[string]string)
		}
		obj.Annotations[meta.AnnotationKeyExternalName] = name
		return obj
	}
	cases := map[string]struct {
		info        ProcedureInfo
		objs        []client.Object
		arnGetter   vpcPrivateLinkClient
		expectReady bool
		expectErr   bool
	}{
		"not-exist": {
			info:        fakeVpcEndpointSvcInfo,
			expectErr:   true,
			expectReady: false,
		},
		"endpoint-svc-ready": {
			info:      fakeVpcEndpointSvcInfo,
			arnGetter: fakeVpcEpSvcArnGetter(fakeEpSvcArn, nil),
			objs: []client.Object{
				addCondition(
					setExternalName(getVpcEndpointSvc(fakeVpcEndpointSvcInfo, fakeEpSvcArn), fakeVpcEndpointSvcName),
					cpv1.Condition{
						Type:   cpv1.TypeReady,
						Status: corev1.ConditionTrue,
					},
				),
			},
			expectErr:   false,
			expectReady: true,
		},
		"endpoint-svc-not-ready": {
			info:      fakeVpcEndpointSvcInfo,
			arnGetter: fakeVpcEpSvcArnGetter(fakeEpSvcArn, nil),
			objs: []client.Object{
				addCondition(
					getVpcEndpointSvc(fakeVpcEndpointSvcInfo, fakeEpSvcArn),
					cpv1.Condition{
						Type:   cpv1.TypeReady,
						Status: corev1.ConditionFalse,
					},
				),
			},
			expectErr:   false,
			expectReady: false,
		},
	}
	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(ec2v1alpha1.SchemeBuilder.AddToScheme(s)).To(Succeed())
			g.Expect(eksv1alpha1.SchemeBuilder.AddToScheme(s)).To(Succeed())
			cli := fake.NewClientBuilder().WithScheme(s).WithObjects(tc.objs...).Build()
			p := NewVpcEpSvc(tc.arnGetter, s)
			ready, err := p.Status(context.TODO(), cli, cli, tc.info)
			if !tc.expectErr {
				g.Expect(err).To(Succeed())
			} else {
				g.Expect(err).NotTo(Succeed())
				return
			}
			g.Expect(ready).To(Equal(tc.expectReady))
		})
	}
}
