package aks

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

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	"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/namer"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"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/test/kubemock"
	"github.com/upbound/provider-azure/apis/network/v1beta1"
	"go.uber.org/mock/gomock"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
)

func fakeVNetTask(t *testing.T) *VNet {
	return NewVNet(fakeTaskContext(t))
}

func Test_getVNetName(t *testing.T) {
	vpc := &v1alpha1.VPC{
		ObjectMeta: metav1.ObjectMeta{
			Name: "vpc",
		},
	}
	assert.Equal(t, namer.UniqueIdentifier(vpc.ObjectMeta, namer.WithLimit(controllers.VNetNameLimit)), getVNetName(vpc))
}

func TestVNet_Create(t *testing.T) {
	vnetTask := fakeVNetTask(t)
	t.Run("Create vnet success", func(t *testing.T) {
		result := vnetTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		vnet := &v1beta1.VirtualNetwork{}
		assert.Nil(t, vnetTask.Local.Get(context.Background(), types.NamespacedName{Name: getVNetName(vnetTask.VPC)}, vnet))
		tags := cloudv1.TagsFromLabels(vnetTask.VPC.Labels)
		labels := map[string]string{
			v1alpha1.VPCNameKey: vnetTask.VPC.Name,
		}
		assert.Equal(t, labels, vnet.Labels)
		assert.Equal(t, v1beta1.VirtualNetworkSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: vnetTask.ProviderConfigName(),
				},
			},
			ForProvider: v1beta1.VirtualNetworkParameters{
				AddressSpace: []*string{
					ptr.To(vnetTask.VPC.Spec.CIDR),
				},
				Location:             ptr.To(vnetTask.VPC.Spec.Region),
				ResourceGroupNameRef: &xpv1.Reference{Name: controllers.GetResourceGroupName(vnetTask.VPC)},
				Tags:                 util.ToMapPointer(tags),
			},
		}, vnet.Spec)
	})
	t.Run("Create vnet failed", func(t *testing.T) {
		ctl := gomock.NewController(t)
		mockcli := kubemock.NewMockClient(ctl)
		vnetTask.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 := vnetTask.Create(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.NotNil(t, retriableErr)
		assert.Equal(t, defaultRequeuInterval, res.RequeueAfter)
	})
}

func Test_setVNetStatus(t *testing.T) {
	vnetTask := fakeVNetTask(t)
	t.Run("id is empty", func(t *testing.T) {
		vnetName := "vnet-test"
		assert.Nil(t, vnetTask.setVNetStatus(context.Background(), &v1beta1.VirtualNetwork{
			ObjectMeta: metav1.ObjectMeta{
				Name: vnetName,
			},
		}))
		vpc := &v1alpha1.VPC{}
		assert.Nil(t, vnetTask.Local.Get(context.Background(), types.NamespacedName{
			Name:      vnetTask.VPC.Name,
			Namespace: vnetTask.VPC.Namespace,
		}, vpc))
		assert.Equal(t, vpc.Status.VPC, v1alpha1.VPCRes{
			Name: vnetName,
		})
	})
	t.Run("id is not empty", func(t *testing.T) {
		vnetName := "vnet-test"
		vnetID := "vnet-test-id"
		assert.Nil(t, vnetTask.setVNetStatus(context.Background(), &v1beta1.VirtualNetwork{
			ObjectMeta: metav1.ObjectMeta{
				Name: vnetName,
				Annotations: map[string]string{
					meta.AnnotationKeyExternalName: vnetID,
				},
			},
		}))
		vpc := &v1alpha1.VPC{}
		assert.Nil(t, vnetTask.Local.Get(context.Background(), types.NamespacedName{
			Name:      vnetTask.VPC.Name,
			Namespace: vnetTask.VPC.Namespace,
		}, vpc))
		assert.Equal(t, vpc.Status.VPC, v1alpha1.VPCRes{
			Name:  vnetName,
			VPCID: vnetID,
		})
	})
}

func TestVNet_PostCreate(t *testing.T) {
	t.Run("vnet is not found", func(t *testing.T) {
		vnetTask := fakeVNetTask(t)
		result := vnetTask.PostCreate(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, fmt.Sprintf("failed to get vnet %s", getVNetName(vnetTask.VPC)))
		assert.Equal(t, defaultRequeuInterval, res.RequeueAfter)
	})
	t.Run("vnet is not ready", func(t *testing.T) {
		vnetTask := fakeVNetTask(t)
		vnet := &v1beta1.VirtualNetwork{
			ObjectMeta: metav1.ObjectMeta{
				Name: getVNetName(vnetTask.VPC),
			},
		}
		assert.Nil(t, vnetTask.Local.Create(context.Background(), vnet))
		result := vnetTask.PostCreate(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "vpc status not ready")
		assert.Equal(t, defaultRequeuInterval, res.RequeueAfter)
	})
	t.Run("PostCreate vnet success", func(t *testing.T) {
		vnetTask := fakeVNetTask(t)
		vnet := &v1beta1.VirtualNetwork{
			ObjectMeta: metav1.ObjectMeta{
				Name: getVNetName(vnetTask.VPC),
			},
			Status: v1beta1.VirtualNetworkStatus{
				ResourceStatus: xpv1.ResourceStatus{
					ConditionedStatus: xpv1.ConditionedStatus{
						Conditions: []xpv1.Condition{
							{
								Type:   xpv1.TypeReady,
								Status: corev1.ConditionTrue,
							},
						},
					},
				},
			},
		}
		assert.Nil(t, vnetTask.Local.Create(context.Background(), vnet))
		result := vnetTask.PostCreate(context.Background())
		assert.True(t, result.IsDoneState())
	})
}

func TestVNet_Delete(t *testing.T) {
	vnetTask := fakeVNetTask(t)
	t.Run("Delete vnet success", func(t *testing.T) {
		vnet := &v1beta1.VirtualNetwork{
			ObjectMeta: metav1.ObjectMeta{
				Name: getVNetName(vnetTask.VPC),
			},
		}
		assert.Nil(t, vnetTask.Local.Create(context.Background(), vnet))
		result := vnetTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())
		err := vnetTask.Local.Get(context.Background(), types.NamespacedName{Name: getVNetName(vnetTask.VPC)}, vnet)
		assert.True(t, apierrors.IsNotFound(err))
	})
	t.Run("vnet failed", func(t *testing.T) {
		ctl := gomock.NewController(t)
		mockcli := kubemock.NewMockClient(ctl)
		vnetTask.Local = mockcli
		mockcli.EXPECT().Delete(gomock.Any(), gomock.Any()).Return(apierrors.NewNotFound(schema.GroupResource{}, "not found")).Times(1)
		result := vnetTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())
	})
}

func TestVNet_PostDelete(t *testing.T) {
	t.Run("vnet is not found", func(t *testing.T) {
		vnetTask := fakeVNetTask(t)
		result := vnetTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		result = vnetTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())
		result = vnetTask.PostDelete(context.Background())
		assert.True(t, result.IsDoneState())
	})
	t.Run("vnet is not deleted", func(t *testing.T) {
		vnetTask := fakeVNetTask(t)
		result := vnetTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		result = vnetTask.PostDelete(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.NotNil(t, err)
		assert.Nil(t, retriableErr)
		assert.Equal(t, time.Second*0, res.RequeueAfter)
	})
}
