package common

import (
	"context"
	"testing"

	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/appbox"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	. "github.com/onsi/gomega"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
)

func TestCreate(t *testing.T) {
	cases := map[string]struct {
		appbox      *v1alpha1.APPBox
		vpc         *v1alpha1.VPC
		initObjects []runtime.Object
		expect      func(g *WithT, t *Guardian, res deploy.Result)
	}{
		"appbox finalizer not set": {
			appbox: defaultAPPBox(),
			vpc:    defaultVPC(),
			expect: func(g *WithT, t *Guardian, res deploy.Result) {
				err := res.Error()
				res.IsDoneState()
				g.Expect(err).To(HaveOccurred())
				g.Expect(k8s.ShouldRequeue(err).Requeue).To(BeTrue())
			},
		},
		"vpc not ready": {
			appbox: defaultAPPBox(withAppBoxFinalizer()),
			vpc:    defaultVPC(withVPCStatus(v1alpha1.VPCPhaseCreating)),
			expect: func(g *WithT, t *Guardian, res deploy.Result) {
				err := res.Error()
				g.Expect(err).To(HaveOccurred())
				g.Expect(k8s.ShouldRequeue(err).Requeue).To(BeTrue())
				g.Expect(t.APPBox.Status.Phase).To(Equal(v1alpha1.APPBoxPhasePending))
			},
		},
		"vpc ready": {
			appbox: defaultAPPBox(withAppBoxFinalizer()),
			vpc:    defaultVPC(withVPCStatus(v1alpha1.VPCPhaseActive)),
			expect: func(g *WithT, t *Guardian, res deploy.Result) {
				g.Expect(res.Error()).To(Succeed())
				g.Expect(t.APPBox.Status.Phase).To(Equal(v1alpha1.APPBoxPhaseCreating))
			},
		},
		"unknown appbox status": {
			appbox: defaultAPPBox(withAppBoxFinalizer(), withAppBoxStatus("unknown")),
			vpc:    defaultVPC(withVPCStatus(v1alpha1.VPCPhaseActive)),
			expect: func(g *WithT, t *Guardian, res deploy.Result) {
				err := res.Error()
				g.Expect(err).To(HaveOccurred())
				g.Expect(k8s.ShouldRequeue(err).Requeue).To(BeFalse())
			},
		},
		"have another appbox in same project": {
			appbox: defaultAPPBox(withAppBoxFinalizer(), withAppBoxStatus(v1alpha1.APPBoxPhasePending)),
			vpc:    defaultVPC(withVPCStatus(v1alpha1.VPCPhaseActive)),
			initObjects: []runtime.Object{
				&v1alpha1.APPBox{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "appbox2",
						Namespace: "test",
					},
					Spec: v1alpha1.APPBoxSpec{
						VPCRef: &v1alpha1.Reference{
							Name: "vpc",
						},
					},
				},
			},
			expect: func(g *WithT, t *Guardian, res deploy.Result) {
				g.Expect(res.Error()).Error()
				g.Expect(t.APPBox.Status.Phase).To(Equal(v1alpha1.APPBoxPhasePending))
			},
		},
	}

	for name, c := range cases {
		t.Run(name, func(t *testing.T) {
			ctx := context.Background()
			s := runtime.NewScheme()
			utilruntime.Must(v1alpha1.SchemeBuilder.AddToScheme(s))
			g := NewWithT(t)
			gtask := NewGuardian(&task.Context{})
			c.initObjects = append(c.initObjects, c.appbox, c.vpc)
			cli := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.initObjects...).WithIndex(&v1alpha1.APPBox{}, controllers.VPCNameField, func(o client.Object) []string {
				ap := o.(*v1alpha1.APPBox)
				return []string{ap.Spec.VPCRef.Name}
			}).Build()
			gtask.Local = cli
			gtask.APPBoxRW = appbox.NewAPPBoxReaderWriter(cli)
			gtask.APPBox = c.appbox
			gtask.VPC = c.vpc
			res := gtask.Create(ctx)
			c.expect(g, gtask, res)
		})
	}
}

func defaultAPPBox(opts ...func(*v1alpha1.APPBox)) *v1alpha1.APPBox {
	o := &v1alpha1.APPBox{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "appbox",
			Namespace: "test",
		},
		Spec: v1alpha1.APPBoxSpec{
			VPCRef: &v1alpha1.Reference{
				Name: "vpc",
			},
		},
	}

	for _, f := range opts {
		f(o)
	}
	return o
}

func withAppBoxStatus(status v1alpha1.APPBoxPhase) func(*v1alpha1.APPBox) {
	return func(o *v1alpha1.APPBox) {
		o.Status.Phase = status
	}
}

func withAppBoxProvider(provider string) func(*v1alpha1.APPBox) {
	return func(o *v1alpha1.APPBox) {
		o.Spec.Provider = provider
	}
}

func withAppBoxFinalizer() func(*v1alpha1.APPBox) {
	return func(o *v1alpha1.APPBox) {
		o.Finalizers = []string{task.Finalizer}
	}
}

func defaultVPC(opts ...func(*v1alpha1.VPC)) *v1alpha1.VPC {
	o := &v1alpha1.VPC{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "vpc",
			Namespace: "test",
		},
	}

	for _, f := range opts {
		f(o)
	}
	return o
}

func withVPCStatus(status v1alpha1.VPCPhase) func(*v1alpha1.VPC) {
	return func(o *v1alpha1.VPC) {
		o.Status.Phase = status
	}
}
