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

package frps

import (
	"context"
	"testing"

	. "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/apimachinery/pkg/util/intstr"
	"k8s.io/client-go/kubernetes/scheme"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

const (
	fakeFrpsSvcIngressIP   = "10.10.10.10"
	fakeFrpsSvcClusterIP   = "20.20.20.20"
	fakeFrpsSvcIngressHost = "tidbcloud.com"
	fakefrpsSvcNodePort    = 30000
)

var (
	fakeFrpsSvcInfo ProcedureInfo
	fakeFrpsSvcKey  types.NamespacedName
)

func init() {
	fakeFrpsSvcInfo.frps = &eksv1alpha1.Frpserver{}
	fakeFrpsSvcInfo.frps.Name = "fake-frps"
	fakeFrpsSvcInfo.frps.Spec.BindPort = 8080
	fakeFrpsSvcInfo.namespace = procedure.ProcedureNamespace
	fakeFrpsSvcKey = types.NamespacedName{Name: namer.UniqueIdentifier(fakeFrpsSvcInfo.frps.ObjectMeta, namer.WithPrefix(internal)), Namespace: fakeFrpsSvcInfo.namespace}
}

func TestFrpsSvcDeploy(t *testing.T) {
	cases := map[string]struct {
		info      ProcedureInfo
		objs      []client.Object
		expectErr bool
	}{
		"create-success": {
			info:      fakeFrpsSvcInfo,
			expectErr: false,
		},
		"service-already-exist": {
			info: fakeFrpsSvcInfo,
			objs: []client.Object{
				&corev1.Service{
					ObjectMeta: metav1.ObjectMeta{
						Name:      fakeFrpsSvcKey.Name,
						Namespace: fakeFrpsSvcKey.Namespace,
					},
				},
			},
			expectErr: false,
		},
		"service-port-already-set": {
			info: fakeFrpsSvcInfo,
			objs: []client.Object{
				&corev1.Service{
					ObjectMeta: metav1.ObjectMeta{
						Name:      fakeFrpsSvcKey.Name,
						Namespace: fakeFrpsSvcKey.Namespace,
					},
					Spec: corev1.ServiceSpec{
						Ports: []corev1.ServicePort{
							{
								TargetPort: intstr.FromInt(fakeFrpsSvcInfo.frps.Spec.BindPort),
								Port:       int32(fakeFrpsSvcInfo.frps.Spec.BindPort),
								NodePort:   fakefrpsSvcNodePort,
							},
						},
					},
				},
			},
			expectErr: 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())
			cli := fake.NewClientBuilder().WithScheme(s).WithObjects(tc.objs...).Build()
			p := NewFrpsSVC()
			err := p.Deploy(context.TODO(), cli, tc.info)
			if !tc.expectErr {
				g.Expect(err).To(Succeed())
			} else {
				g.Expect(err).NotTo(Succeed())
			}
		})
	}
}

func TestFrpsSvcStatus(t *testing.T) {
	addIngress := func(svc *corev1.Service, ingress []corev1.LoadBalancerIngress) *corev1.Service {
		svc.Status.LoadBalancer.Ingress = append(svc.Status.LoadBalancer.Ingress, ingress...)
		return svc
	}
	readyInternalSvc := getFrpsInternalService(fakeFrpsSvcInfo)
	readyInternalSvc.Spec.ClusterIP = fakeFrpsSvcClusterIP
	cases := map[string]struct {
		info        ProcedureInfo
		objs        []client.Object
		expectReady bool
		expectErr   bool
	}{
		"not-exist": {
			info:        fakeFrpsSvcInfo,
			expectErr:   true,
			expectReady: false,
		},
		"service-ready": {
			info: fakeFrpsSvcInfo,
			objs: []client.Object{
				addIngress(
					getFrpsService(fakeFrpsSvcInfo),
					[]corev1.LoadBalancerIngress{{
						IP:       fakeFrpsSvcIngressIP,
						Hostname: fakeFrpsSvcIngressHost,
					}},
				),
				readyInternalSvc,
			},
			expectErr:   false,
			expectReady: 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(eksv1alpha1.AddToScheme(s)).To(Succeed())
			cli := fake.NewClientBuilder().WithScheme(s).WithObjects(tc.objs...).Build()
			p := NewFrpsSVC()
			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))
		})
	}
}
