package common

import (
	"context"
	"testing"

	"github.com/go-logr/logr"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/upbound/provider-aws/apis/ec2/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"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/internet/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/namer"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/repository"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
)

func fakeTaskContext(t *testing.T) *task.Context {
	s := runtime.NewScheme()
	v1alpha1.AddToScheme(s)
	v1beta1.SchemeBuilder.AddToScheme(s)
	fakeVPC := &v1alpha1.VPC{}
	bs, err := testdata.LoadDefaultVPCYAML()
	require.NoError(t, err)
	require.NoError(t, yaml.Unmarshal(bs, fakeVPC))
	fakeInternet := &v1alpha1.Internet{}
	bs, err = testdata.LoadEKSInternetYAML()
	require.NoError(t, err)
	assert.NoError(t, yaml.Unmarshal(bs, fakeInternet))
	cli := fake.NewClientBuilder().
		WithScheme(s).
		WithObjects(fakeVPC, fakeInternet).
		WithStatusSubresource(fakeVPC, fakeInternet).
		WithIndex(&v1alpha1.APPBox{}, controllers.VPCNameField, func(o client.Object) []string {
			c := o.(*v1alpha1.APPBox)
			var res []string
			if c.Spec.VPCRef != nil {
				res = append(res, c.Spec.VPCRef.Name)
			}
			return res
		}).Build()
	c := &task.Context{
		Local:       k8s.NewMutClientWithOwner(cli, fakeVPC.DeepCopy(), namer.NewNamer()).Client(),
		Logger:      logr.Discard(),
		VPC:         fakeVPC.DeepCopy(),
		VPCR:        repository.NewVPCReader(cli),
		APPBoxR:     repository.NewAPPBoxReader(cli),
		VPCPeeringR: repository.NewVPCPeeringReader(cli),
		Config:      config.RootConfig{},
		Internet:    fakeInternet.DeepCopy(),
		InternetRW:  repository.NewInternetReaderWriter(cli),
	}
	c, err = c.Default()
	assert.Nil(t, err)
	return c
}

func TestGuardian_Create(t *testing.T) {
	s := runtime.NewScheme()
	v1alpha1.AddToScheme(s)
	gu := NewGuardian(fakeTaskContext(t))
	t.Run("AddFinalizer", func(t *testing.T) {
		assert.Nil(t, gu.Internet.GetFinalizers())
		result := gu.Create(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.NotNil(t, retriableErr)
		assert.Equal(t, ctrl.Result{Requeue: true, RequeueAfter: 0}, res)
		assert.Equal(t, []string{v1alpha1.Finalizer}, gu.Internet.GetFinalizers())
	})
	t.Run("set internet phase", func(t *testing.T) {
		casts := []struct {
			name          string
			vpcPhase      v1alpha1.VPCPhase
			internetPhase v1alpha1.InternetPhase
			expectedPhase v1alpha1.InternetPhase
			done          bool
		}{
			{"vpc active and internet pending", v1alpha1.VPCPhaseActive, v1alpha1.InternetPhasePending, v1alpha1.InternetPhaseReadyForCreate, false},
			{"vpc inactive and internet pending", v1alpha1.VPCPhasePending, v1alpha1.InternetPhasePending, v1alpha1.InternetPhasePending, false},
			{"vpc active and internet ready for create", v1alpha1.VPCPhaseActive, v1alpha1.InternetPhaseReadyForCreate, v1alpha1.InternetPhaseCreating, true},
			{"vpc active and internet creating", v1alpha1.VPCPhaseActive, v1alpha1.InternetPhaseCreating, v1alpha1.InternetPhaseCreating, true},
			{"vpc active and internet active", v1alpha1.VPCPhaseActive, v1alpha1.InternetPhaseActive, v1alpha1.InternetPhaseActive, true},
			{"vpc active and internet failed", v1alpha1.VPCPhaseActive, v1alpha1.InternetPhaseFailed, v1alpha1.InternetPhaseCreating, true},
		}
		// set finalizer
		gu.Internet.Finalizers = []string{}
		gu.Create(context.Background())
		assert.Equal(t, []string{v1alpha1.Finalizer}, gu.Internet.GetFinalizers())
		for _, cast := range casts {
			t.Run(cast.name, func(t *testing.T) {
				err := gu.Local.Get(context.Background(), types.NamespacedName{Namespace: gu.Internet.Namespace, Name: gu.Internet.Name}, gu.Internet)
				gu.Internet.Finalizers = []string{v1alpha1.Finalizer}
				assert.Nil(t, err)
				gu.VPC.Status.Phase = cast.vpcPhase
				gu.Internet.Status.Phase = cast.internetPhase
				result := gu.Create(context.Background())
				assert.Equal(t, cast.done, result.IsDoneState())
				assert.Equal(t, cast.expectedPhase, gu.Internet.Status.Phase)
			})
		}
	})
}

func TestGuardian_waitVPCActive(t *testing.T) {
	gu := NewGuardian(fakeTaskContext(t))
	cases := []struct {
		vpcPhase v1alpha1.VPCPhase
		expected bool
	}{
		{v1alpha1.VPCPhaseActive, true},
		{v1alpha1.VPCPhaseCreating, false},
		{v1alpha1.VPCPhaseFailed, false},
		{v1alpha1.VPCPhasePending, false},
		{v1alpha1.VPCPhaseReadyForCreate, false},
		{v1alpha1.VPCPhaseReadyForDelete, false},
	}

	for _, c := range cases {
		t.Run(string(c.vpcPhase), func(t *testing.T) {
			gu.VPC.Status.Phase = c.vpcPhase
			active := gu.waitVPCActive(context.Background())
			assert.Equal(t, c.expected, active)
		})
	}
}

func TestGuardian_Delete(t *testing.T) {
	gu := NewGuardian(fakeTaskContext(t))
	t.Run("no related appbox", func(t *testing.T) {
		result := gu.Delete(context.Background())
		t.Log(result.Error())
		assert.True(t, result.IsDoneState())
	})
	t.Run("related appbox", func(t *testing.T) {
		ab := &v1alpha1.APPBox{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "appbox",
				Namespace: gu.Internet.Namespace,
			},
			Spec: v1alpha1.APPBoxSpec{
				VPCRef: &v1alpha1.Reference{
					Name:      gu.Internet.Spec.VPCRef.Name,
					Namespace: gu.Internet.Spec.VPCRef.Namespace,
				},
			},
		}
		assert.Nil(t, gu.Local.Create(context.Background(), ab))
		result := gu.Delete(context.Background())
		t.Log(result.Error())
		assert.False(t, result.IsDoneState())
	})
}
