package jumppod

import (
	"context"
	"os"
	"testing"
	"time"

	"github.com/go-logr/logr"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"

	jmpent "github.com/tidbcloud/devops/entity/jump"
	"github.com/tidbcloud/devops/pkg/infra/clients/coresvccli"
	"github.com/tidbcloud/devops/pkg/infra/clients/dedicatedcli"
	"github.com/tidbcloud/devops/pkg/infra/config"
	"github.com/tidbcloud/devops/pkg/infra/utils/k8s"
	k8sutil "github.com/tidbcloud/devops/pkg/infra/utils/k8s"
	"github.com/tidbcloud/devops/pkg/infra/utils/test"
	"github.com/tidbcloud/devops/pkg/service/jump/jumppod/types"
	ctrlfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestJumpPodManager(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Jump Pod Manager Suite", test.ReporterConfig())
}

var _ = Describe("[ProviderAdapter]", func() {
	Context("[NewProviderAdapter]", func() {
		It("creates EKS adapter", func() {
			adapter, err := NewProviderAdapter(config.JumpServiceJumpPodConfig{
				Provider:            "eks",
				JumpPodNamespace:    "jump-ns",
				JumpPodRoleARN:      "arn:aws:iam::123456789012:role/jump",
				JumpPodSharedFSAddr: "efs.example.local",
			})
			Expect(err).To(BeNil())
			Expect(adapter).To(BeAssignableToTypeOf(&AdapterEKS{}))
		})

		It("creates ACK adapter", func() {
			adapter, err := NewProviderAdapter(config.JumpServiceJumpPodConfig{
				Provider:            "ack",
				JumpPodNamespace:    "jump-ns",
				JumpPodRoleARN:      "jump-role",
				JumpPodSharedFSAddr: "nas.example.local",
			})
			Expect(err).To(BeNil())
			Expect(adapter).To(BeAssignableToTypeOf(&AdapterACK{}))
		})

		It("returns error for unsupported provider", func() {
			adapter, err := NewProviderAdapter(config.JumpServiceJumpPodConfig{Provider: "gke"})
			Expect(err).ToNot(BeNil())
			Expect(err.Error()).To(ContainSubstring("unsupported provider"))
			Expect(adapter).To(BeNil())
		})
	})

	Context("[EKSAdapter]", func() {
		It("mutates shared PV/PVC and returns role annotation", func() {
			adapter := &AdapterEKS{conf: config.JumpServiceJumpPodConfig{
				Provider:            "eks",
				JumpPodNamespace:    "jump-ns",
				JumpPodRoleARN:      "arn:aws:iam::123456789012:role/jump",
				JumpPodSharedFSAddr: "efs.example.local",
			}}

			pv := &corev1.PersistentVolume{}
			pvc := &corev1.PersistentVolumeClaim{}

			Expect(adapter.MutateSharedFSPV(context.Background(), pv)).To(Succeed())
			Expect(adapter.MutateSharedFSPVC(context.Background(), pvc)).To(Succeed())

			Expect(pv.Spec.PersistentVolumeSource.NFS).ToNot(BeNil())
			Expect(pv.Spec.PersistentVolumeSource.NFS.Server).To(Equal("efs.example.local"))
			Expect(pv.Spec.StorageClassName).To(Equal("jumppod-efs-jump-ns"))
			Expect(pv.Spec.AccessModes).To(ContainElement(corev1.ReadWriteMany))

			Expect(pvc.Spec.StorageClassName).ToNot(BeNil())
			Expect(*pvc.Spec.StorageClassName).To(Equal("jumppod-efs-jump-ns"))
			Expect(pvc.Spec.AccessModes).To(ContainElement(corev1.ReadWriteMany))

			annoKey, annoVal := adapter.ServiceAccountAnnoForRole()
			Expect(annoKey).To(Equal("eks.amazonaws.com/role-arn"))
			Expect(annoVal).To(Equal("arn:aws:iam::123456789012:role/jump"))
		})
	})

	Context("[ACKAdapter]", func() {
		It("mutates shared PV/PVC and returns role annotation", func() {
			adapter := &AdapterACK{conf: config.JumpServiceJumpPodConfig{
				Provider:            "ack",
				JumpPodNamespace:    "jump-ns",
				JumpPodRoleARN:      "ack-jump-role",
				JumpPodSharedFSAddr: "nas.example.local",
			}}

			pv := &corev1.PersistentVolume{}
			pv.Name = "jump-pod-shared-pv-jump-ns"
			pvc := &corev1.PersistentVolumeClaim{}

			Expect(adapter.MutateSharedFSPV(context.Background(), pv)).To(Succeed())
			Expect(adapter.MutateSharedFSPVC(context.Background(), pvc)).To(Succeed())

			Expect(pv.Spec.PersistentVolumeSource.CSI).ToNot(BeNil())
			Expect(pv.Spec.PersistentVolumeSource.CSI.Driver).To(Equal("nasplugin.csi.alibabacloud.com"))
			Expect(pv.Spec.PersistentVolumeSource.CSI.VolumeHandle).To(Equal("jump-pod-shared-pv-jump-ns"))
			Expect(pv.Spec.PersistentVolumeSource.CSI.VolumeAttributes["server"]).To(Equal("nas.example.local"))
			Expect(pv.Spec.StorageClassName).To(Equal("jumppod-nas-jump-ns"))

			Expect(pvc.Spec.StorageClassName).ToNot(BeNil())
			Expect(*pvc.Spec.StorageClassName).To(Equal("jumppod-nas-jump-ns"))

			annoKey, annoVal := adapter.ServiceAccountAnnoForRole()
			Expect(annoKey).To(Equal("pod-identity.alibabacloud.com/role-name"))
			Expect(annoVal).To(Equal("ack-jump-role"))
		})
	})
})

var _ = Describe("[JumpPodManager]", func() {
	Describe("[ListJumpPods]", func() {
		It("lists only pods in manager namespace that have jump name label", func() {
			scheme := runtime.NewScheme()
			Expect(corev1.AddToScheme(scheme)).To(Succeed())

			targetPod := &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "jump-pod-target",
					Namespace: "jump-ns",
					Labels: map[string]string{
						"jump-service.devops.cloud.pingcap.com/name": "target.jump",
					},
				},
			}
			nonJumpPod := &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "non-jump-pod",
					Namespace: "jump-ns",
					Labels: map[string]string{
						"app": "not-jump",
					},
				},
			}
			otherNamespacePod := &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "jump-pod-other-ns",
					Namespace: "other-ns",
					Labels: map[string]string{
						"jump-service.devops.cloud.pingcap.com/name": "other.jump",
					},
				},
			}

			ctrlCli := ctrlfake.NewClientBuilder().
				WithScheme(scheme).
				WithObjects(targetPod, nonJumpPod, otherNamespacePod).
				Build()

			m := &manager{
				namespace: "jump-ns",
				logger:    logr.Discard(),
				cluster: &k8sutil.UnionClient{
					Ctrl: ctrlCli,
				},
			}

			jps, err := m.ListJumpPods(context.Background())
			Expect(err).To(BeNil())
			Expect(jps).To(HaveLen(1))
			Expect(jps[0].Name()).To(Equal("target.jump"))
		})
	})

	Describe("[Have a try]", func() {
		fw := test.Framework()
		ctx := context.Background()
		var m *manager
		name := "sihao.yu_pingcap.com"
		clusterID := uint64(10235832784988412444)
		os.Setenv("AWS_PROFILE", "0414")
		os.Setenv("PATH", "$PATH:/usr/local/cloud/bin/:/usr/local/cloud/google-cloud-sdk/bin/")

		BeforeEach(func() {
			infraConfig := "/root/.kube/dev-infra-apiserver.yaml"
			data, err := os.ReadFile(infraConfig)
			Expect(err).To(BeNil())
			infraCli, err := dedicatedcli.NewInfraAPIClient(data)
			Expect(err).To(BeNil())

			baseConfig := "/root/.kube/dev-base-us-west-2.yaml"
			data, err = os.ReadFile(baseConfig)
			Expect(err).To(BeNil())
			baseCli, err := k8s.NewUnionClientFromKubeConfig(data, nil)
			Expect(err).To(BeNil())

			coresvcAddr := "127.0.0.1:10102"
			mgmtsvcAddr := "127.0.0.1:10001"
			nextGenGlobalAddr := "127.0.0.1:10002"
			coreCli, err := coresvccli.NewCoreSvcCli(coresvcAddr, mgmtsvcAddr, nextGenGlobalAddr)
			Expect(err).To(BeNil())

			param := ManagerParam{
				Conf: config.JumpServiceConfig{
					Dedicated: config.JumpServiceDedicatedConfig{
						DedicatedNewArchAccessRole: "arn:aws:iam::385595570414:role/devops-server-dev",
					},
					JumpPod: config.JumpServiceJumpPodConfig{
						JumpPodImage: "385595570414.dkr.ecr.us-west-2.amazonaws.com/dbaas/staging/jump-pod:97c385fddcda9eae24b9a51b9ff327ea4110e6fd",
					},
				},
				InfraClient:  infraCli,
				SecretSource: nil,
				CoreSvc:      coreCli,
				Logger:       fw.Logger,
				JumpCluster: &k8s.UnionClient{
					Ctrl: baseCli.Ctrl,
					Kube: baseCli.Kube,
				},
			}
			m, err = NewManager(param)
			Expect(err).To(BeNil())

		})

		Context("[JumpPod]", func() {
			PIt("SetUpJumpPod", func() {
				_, err := m.SetUpJumpPod(ctx, name)
				Expect(err).To(BeNil())
			})

			PIt("TearDownJumpPod", func() {
				err := m.TearDownJumpPod(ctx, name)
				Expect(err).To(BeNil())
			})

			PIt("GenerateAccessToken", func() {
				jp, err := m.GetJumpPod(ctx, name)
				Expect(err).To(BeNil())

				token, err := jp.GenerateAccessToken(ctx, &types.GenerateAccessTokenParam{
					GenerateTokenParam: types.GenerateTokenParam{
						ExpireAt: time.Now().Add(time.Minute * 180),
					},
				})
				Expect(err).To(BeNil())
				Expect(token).NotTo(BeEmpty())
				test.Framework().Logger.Info("generated a token", "token", token)
			})
		})

		Context("[Jump]", func() {
			Context("[PrepareJump]", func() {
				PIt("Dedicated", func() {
					jp, err := m.GetJumpPod(ctx, name)
					Expect(err).To(BeNil())

					dest := &jmpent.DestinationUnion{
						DedicatedCluster: &jmpent.DestinationDedicatedCluster{ClusterID: clusterID},
					}
					jparam := &types.PrepareJumpParam{
						Privilege: jmpent.JumpPrivilegeAdmin,
						GenerateTokenParam: types.GenerateTokenParam{
							ExpireAt: time.Now().Add(time.Minute * 180),
						},
					}
					token, err := jp.PrepareJump(ctx, dest.Extract(), jparam)
					Expect(err).To(BeNil())
					Expect(token).ToNot(BeNil())
				})

				PIt("ControlPlane", func() {
					jp, err := m.GetJumpPod(ctx, name)
					Expect(err).To(BeNil())

					dest := &jmpent.DestinationUnion{
						ControlPlane: &jmpent.DestinationControlPlane{
							Name: "seed-azure-eastus",
						},
					}
					jparam := &types.PrepareJumpParam{
						Privilege: jmpent.JumpPrivilegeAdmin,
						GenerateTokenParam: types.GenerateTokenParam{
							ExpireAt: time.Now().Add(time.Minute * 180),
						},
					}
					token, err := jp.PrepareJump(ctx, dest.Extract(), jparam)
					Expect(err).To(BeNil())
					Expect(token).ToNot(BeNil())
				})

				PIt("Serverless", func() {
					jp, err := m.GetJumpPod(ctx, name)
					Expect(err).To(BeNil())

					dest := &jmpent.DestinationUnion{
						ServerlessCluster: &jmpent.DestinationServerlessCluster{
							Name: "dev-us-east-1-f01",
						},
					}
					jparam := &types.PrepareJumpParam{
						Privilege: jmpent.JumpPrivilegeAdmin,
						GenerateTokenParam: types.GenerateTokenParam{
							ExpireAt: time.Now().Add(time.Minute * 180),
						},
					}
					token, err := jp.PrepareJump(ctx, dest.Extract(), jparam)
					Expect(err).To(BeNil())
					Expect(token).ToNot(BeNil())
				})
			})

			Context("[FinalizeJump]", func() {
				PIt("Dedicated", func() {
					jp, err := m.GetJumpPod(ctx, name)
					Expect(err).To(BeNil())

					dest := &jmpent.DestinationUnion{
						DedicatedCluster: &jmpent.DestinationDedicatedCluster{ClusterID: clusterID},
					}
					err = jp.FinalizeJump(ctx, dest.Extract(), jmpent.JumpAttributes{})
					Expect(err).To(BeNil())
				})

				PIt("ControlPlane", func() {
					jp, err := m.GetJumpPod(ctx, name)
					Expect(err).To(BeNil())

					dest := &jmpent.DestinationUnion{
						ControlPlane: &jmpent.DestinationControlPlane{
							Name: "base-aws-us-west-2",
						},
					}
					err = jp.FinalizeJump(ctx, dest.Extract(), jmpent.JumpAttributes{})
					Expect(err).To(BeNil())
				})

			})

		})

	})
})
