package gke

import (
	"context"
	"fmt"
	"testing"
	"time"

	gcpv1alpha1 "github.com/crossplane-contrib/provider-gcp/apis/compute/v1alpha1"
	"github.com/crossplane-contrib/provider-gcp/apis/compute/v1beta1"
	gcpv1beta1 "github.com/crossplane-contrib/provider-gcp/apis/compute/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	"github.com/go-logr/logr"
	"github.com/stretchr/testify/assert"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"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/vpc/task"
	resvpc "github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/vpc"
	"github.com/tidbcloud/infra-provider/regional-server/test/kubemock"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
	"go.uber.org/mock/gomock"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"
)

func fakeTaskContext(t *testing.T) *task.Context {
	s := runtime.NewScheme()
	v1alpha1.AddToScheme(s)
	gcpv1beta1.SchemeBuilder.AddToScheme(s)
	gcpv1alpha1.SchemeBuilder.AddToScheme(s)

	fakeVPC := &v1alpha1.VPC{}
	bs, err := testdata.LoadGKEVPCYAML()
	assert.Nil(t, err)
	assert.Nil(t, yaml.Unmarshal(bs, fakeVPC))
	cli := fake.NewClientBuilder().WithScheme(s).WithObjects(fakeVPC).WithStatusSubresource(fakeVPC, &gcpv1beta1.Network{}, &gcpv1beta1.Subnetwork{}).Build()
	c := &task.Context{
		Local:  k8s.NewMutClientWithOwner(cli, fakeVPC.DeepCopy(), namer.NewNamer()).Client(),
		Logger: logr.Discard(),
		VPC:    fakeVPC.DeepCopy(),
		VPCRW:  resvpc.NewVPCReaderWriter(cli),
		Config: config.RootConfig{},
	}
	c, err = c.Default()
	assert.Nil(t, err)
	return c
}

func fakeVPCTask(t *testing.T) *VPC {
	return NewVPC(fakeTaskContext(t))
}

func TestVPC_Create(t *testing.T) {
	vpcTask := fakeVPCTask(t)
	t.Run("Create gke network success", func(t *testing.T) {
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		network := &gcpv1beta1.Network{}
		err := vpcTask.Local.Get(context.Background(), types.NamespacedName{Name: vpcTask.GetNetworkName()}, network)
		assert.Nil(t, err)
		tags := cloudv1.TagsFromLabels(vpcTask.VPC.Labels)
		assert.Equal(t, gcpv1beta1.NetworkSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: vpcTask.ProviderConfigName(),
				},
			},
			ForProvider: gcpv1beta1.NetworkParameters{
				AutoCreateSubnetworks: pointer.Bool(false),
				Description:           pointer.String(fmt.Sprintf("Tags: %v", tags)),
				RoutingConfig: &v1beta1.NetworkRoutingConfig{
					RoutingMode: "REGIONAL",
				},
			},
		}, network.Spec)
	})
	t.Run("Create gke network failed", func(t *testing.T) {
		ctl := gomock.NewController(t)
		mockcli := kubemock.NewMockClient(ctl)
		vpcTask.Local = mockcli
		mockcli.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(apierrors.NewNotFound(schema.GroupResource{}, "not found")).Times(1)
		mockcli.EXPECT().Create(gomock.Any(), gomock.Any()).Return(assert.AnError).Times(1)
		result := vpcTask.Create(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.NotNil(t, retriableErr)
		assert.Equal(t, time.Second*30, res.RequeueAfter)
	})
}

func TestVPC_setVPCStatus(t *testing.T) {
	vpcTask := fakeVPCTask(t)
	networkName := "test-network"
	networkExternalName := "test-network-x"
	t.Run("Set vpc status success", func(t *testing.T) {
		err := vpcTask.setVPCStatus(context.Background(), &gcpv1beta1.Network{
			ObjectMeta: metav1.ObjectMeta{
				Name: networkName,
				Annotations: map[string]string{
					meta.AnnotationKeyExternalName: networkExternalName,
				},
			},
		})
		assert.Nil(t, err)
		assert.Equal(t, v1alpha1.VPCRes{
			VPCID: networkExternalName,
			Name:  networkName,
		}, vpcTask.VPC.Status.VPC)
	})
}

func TestVPC_Delete(t *testing.T) {
	vpcTask := fakeVPCTask(t)
	t.Run("Delete gke network success", func(t *testing.T) {
		result := vpcTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())
		network := &gcpv1beta1.Network{}
		err := vpcTask.Local.Get(context.Background(), types.NamespacedName{Name: vpcTask.GetNetworkName()}, network)
		assert.True(t, apierrors.IsNotFound(err))
	})
	t.Run("gke network failed", func(t *testing.T) {
		ctl := gomock.NewController(t)
		mockcli := kubemock.NewMockClient(ctl)
		vpcTask.Local = mockcli
		mockcli.EXPECT().Delete(gomock.Any(), gomock.Any()).Return(apierrors.NewNotFound(schema.GroupResource{}, "not found")).Times(1)
		result := vpcTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())
	})
}

func TestVPC_PostDelete(t *testing.T) {
	t.Run("gke network is not found", func(t *testing.T) {
		vpcTask := fakeVPCTask(t)
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		result = vpcTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())
		result = vpcTask.PostDelete(context.Background())
		assert.True(t, result.IsDoneState())
	})
	t.Run("gke network is not deleted", func(t *testing.T) {
		vpcTask := fakeVPCTask(t)
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		result = vpcTask.PostDelete(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.NotNil(t, err)
		assert.Nil(t, retriableErr)
		assert.False(t, res.Requeue)
	})
}
