package common

import (
	"context"
	"fmt"
	"testing"

	. "github.com/onsi/gomega"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

const (
	serviceName = "proxy-cluster-proxy"
)

func TestCPPostCreate(t *testing.T) {
	cases := map[string]struct {
		provier string
		service *corev1.Service
		expect  func(g *WithT, task ClusterProxy, res deploy.Result)
	}{
		"service notfound": {
			provier: "eks",
			service: &corev1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "notfound",
					Namespace: "notfound",
				},
			},
			expect: func(g *WithT, task ClusterProxy, res deploy.Result) {
				err := res.Error()
				g.Expect(err).To(HaveOccurred())
				g.Expect(err.Error()).To(ContainSubstring("not found"))
			},
		},
		"service created but lb not ready": {
			provier: "eks",
			service: &corev1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      serviceName,
					Namespace: "tidb-admin",
				},
				Spec: corev1.ServiceSpec{
					Type: corev1.ServiceTypeLoadBalancer,
				},
			},
			expect: func(g *WithT, task ClusterProxy, res deploy.Result) {
				g.Expect(res).To(Equal(deploy.Error(fmt.Errorf("waiting for proxy load balancer to be provisioned"))))
			},
		},
		"eks service and lb ready": {
			provier: "eks",
			service: &corev1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      serviceName,
					Namespace: "tidb-admin",
				},
				Spec: corev1.ServiceSpec{
					Type: corev1.ServiceTypeLoadBalancer,
				},
				Status: corev1.ServiceStatus{
					LoadBalancer: corev1.LoadBalancerStatus{
						Ingress: []corev1.LoadBalancerIngress{
							{
								IP:       "1.2.3.4",
								Hostname: "cplb.aws.com",
							},
						},
					},
				},
			},
			expect: func(g *WithT, task ClusterProxy, res deploy.Result) {
				g.Expect(res.Error()).Should(Succeed())
				g.Expect(task.APPBox.Status.ProxyBaseUrl).To(Equal("cplb.aws.com"))
			},
		},
		"gke service and lb ready": {
			provier: "gke",
			service: &corev1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      serviceName,
					Namespace: "tidb-admin",
				},
				Spec: corev1.ServiceSpec{
					Type: corev1.ServiceTypeLoadBalancer,
				},
				Status: corev1.ServiceStatus{
					LoadBalancer: corev1.LoadBalancerStatus{
						Ingress: []corev1.LoadBalancerIngress{
							{
								IP:       "1.2.3.4",
								Hostname: "cplb.aws.com",
							},
						},
					},
				},
			},
			expect: func(g *WithT, task ClusterProxy, res deploy.Result) {
				g.Expect(res.Error()).Should(Succeed())
				g.Expect(task.APPBox.Status.ProxyBaseUrl).To(Equal("1.2.3.4"))
			},
		},
	}

	for n, c := range cases {
		t.Run(n, func(t *testing.T) {
			g := NewWithT(t)
			cli := fake.NewClientBuilder().Build()
			g.Expect(cli.Create(context.Background(), c.service)).To(Succeed())
			g.Expect(cli.Create(context.Background(), cpDeployment())).To(Succeed())

			task := NewClusterProxy(&task.Context{
				Remote: &controllers.MockClientGetter{Client: cli},
				APPBox: defaultAPPBox(withAppBoxProvider(c.provier)),
				DedicatedTmplConfig: &config.DedicatedTemplateConfig{
					ClusterProxy: config.InfraComponentConfig{
						ReleaseName: "proxy",
						Namespace:   "tidb-admin",
					},
				},
			})
			c.expect(g, *task, task.PostCreate(context.Background()))
		})
	}
}

func TestCPDelete(t *testing.T) {
	cases := map[string]struct {
		service *corev1.Service
		expect  func(g *WithT, res deploy.Result)
	}{
		"empty client": {
			service: &corev1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      serviceName,
					Namespace: "tidb-admin",
				},
			},
			expect: func(g *WithT, res deploy.Result) {
				g.Expect(res).To(Equal(deploy.Done()))
			},
		},
		"service present": {
			service: &corev1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      serviceName,
					Namespace: "tidb-admin",
				},
			},
			expect: func(g *WithT, res deploy.Result) {
				g.Expect(res.Error()).ShouldNot(BeNil())
				g.Expect(res.Error().Error()).To(ContainSubstring(fmt.Sprintf("%v still exist", serviceName)))
			},
		},
		"service deleted": {
			service: nil,
			expect: func(g *WithT, res deploy.Result) {
				g.Expect(res).To(Equal(deploy.Done()))
			},
		},
	}

	for n, c := range cases {
		t.Run(n, func(t *testing.T) {
			g := NewWithT(t)
			cli := fake.NewClientBuilder().Build()
			if c.service != nil {
				g.Expect(cli.Create(context.Background(), c.service)).To(Succeed())
			}
			task := NewClusterProxy(&task.Context{
				Remote: &controllers.MockClientGetter{Client: cli},
				APPBox: defaultAPPBox(),
				DedicatedTmplConfig: &config.DedicatedTemplateConfig{
					ClusterProxy: config.InfraComponentConfig{
						ReleaseName: "proxy",
						Namespace:   "tidb-admin",
					},
				},
			})
			if n == "empty client" {
				task.Remote = &k8s.EmptyGetter{}
			}
			c.expect(g, task.Delete(context.Background()))
		})
	}
}

func TestCPToHelmValues(t *testing.T) {
	cfg := clusterProxyConfig{
		InfraComponentConfig: config.InfraComponentConfig{
			ReleaseName: "proxy",
			Namespace:   "tidb-admin",
			HelmValues: map[string]interface{}{
				"service": map[string]interface{}{},
			},
		},
		lbServiceTags: map[string]string{
			"key": "value",
		},
	}
	vals := cfg.ToHelmValues()

	g := NewWithT(t)
	g.Expect(vals["service"].(map[string]interface{})["annotations"].(map[string]string)["service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags"]).To(Equal("key=value"))
	g.Expect(cfg.InfraComponentConfig.HelmValues["service"]).To(HaveLen(0))
}

func cpDeployment() *appsv1.Deployment {
	return &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      serviceName,
			Namespace: "tidb-admin",
		},
		Status: appsv1.DeploymentStatus{
			Conditions: []appsv1.DeploymentCondition{
				{
					Type:   appsv1.DeploymentAvailable,
					Status: "True",
				},
			},
		},
	}
}
