package aks

import (
	"context"
	"testing"
	"time"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"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/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/pkg/controllers/privatelinkservice/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/privatelinkservice"
	resvpc "github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/vpc"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
	azurev1beta1 "github.com/upbound/provider-azure/apis/azure/v1beta1"
	netv1beta1 "github.com/upbound/provider-azure/apis/network/v1beta1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"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)
	azurev1beta1.SchemeBuilder.AddToScheme(s)
	netv1beta1.SchemeBuilder.AddToScheme(s)
	fakeVPC := &v1alpha1.VPC{}
	bs, err := testdata.LoadAKSVPCYAML()
	assert.Nil(t, err)
	assert.Nil(t, yaml.Unmarshal(bs, fakeVPC))
	fakePrivateLinkService := &v1alpha1.PrivateLinkService{}
	bs, err = testdata.LoadAKSPrivateLinkService()
	assert.Nil(t, err)
	assert.Nil(t, yaml.Unmarshal(bs, fakePrivateLinkService))
	cli := fake.NewClientBuilder().WithScheme(s).WithObjects(fakeVPC, fakePrivateLinkService).WithStatusSubresource(
		fakeVPC,
		fakePrivateLinkService,
		&azurev1beta1.ResourceGroup{},
		&netv1beta1.VirtualNetwork{},
		&netv1beta1.Subnet{}).Build()
	c := &task.Context{
		Local:                k8s.NewMutClientWithOwner(cli, fakePrivateLinkService.DeepCopy(), namer.NewNamer()).Client(),
		Logger:               logr.Discard(),
		VPC:                  fakeVPC.DeepCopy(),
		VPCR:                 resvpc.NewVPCReader(cli),
		PrivateLinkService:   fakePrivateLinkService.DeepCopy(),
		PrivateLinkServiceRW: privatelinkservice.NewPrivateLinkServiceReaderWriter(cli),
	}
	c, err = c.Default()
	assert.Nil(t, err)
	return c
}

func fakePrivateLinkService(t *testing.T) *PrivateLinkService {
	return NewPrivateLinkService(fakeTaskContext(t))
}

func TestPrivateLinkService_Create(t *testing.T) {
	t.Run("no plsNAT subnet found", func(t *testing.T) {
		task := fakePrivateLinkService(t)
		task.VPC.Status.PLSNATSubnets = []v1alpha1.Subnet{}
		assert.Nil(t, task.Local.Status().Update(context.Background(), task.VPC))
		result := task.Create(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "no plsNAT subnet found")
		assert.Equal(t, time.Second*30, res.RequeueAfter)
	})
	t.Run("no load balancer found", func(t *testing.T) {
		task := fakePrivateLinkService(t)
		result := task.Create(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "no load balancer found")
		assert.Equal(t, time.Second*30, res.RequeueAfter)
	})
	t.Run("create private link service success", func(t *testing.T) {
		task := fakePrivateLinkService(t)
		plsSubnetName := "dedicated-dev-us-west1-63b221910b88f2645bd6d72c4-5f649bd9-pls"
		lbResouceID := "/subscriptions/subscription-id/resourceGroups/resource-group-name/providers/Microsoft.Network/loadBalancers/load-balancer-name"
		task.PrivateLinkService.Status.LoadBalancers = []v1alpha1.LoadBalancerStatus{
			{
				ID:              "lb-id",
				CloudResourceId: lbResouceID,
			},
		}
		assert.Nil(t, task.PrivateLinkServiceRW.UpdateStatus(context.Background(), task.PrivateLinkService))
		result := task.Create(context.Background())
		assert.True(t, result.IsDoneState())
		ves := &netv1beta1.PrivateLinkService{}
		assert.Nil(t, task.Local.Get(context.Background(), types.NamespacedName{Name: getPrivateLinkServiceResourceName(task.PrivateLinkService)}, ves))
		assert.Equal(t, map[string]string{
			v1alpha1.PrivateLinkServiceNameKey: task.PrivateLinkService.Name,
			v1alpha1.VPCNameKey:                task.VPC.Name,
		}, ves.GetLabels())
		tags := cloudv1.TagsFromLabels(task.PrivateLinkService.Labels)
		tags = util.AddResourceName(tags, task.PrivateLinkService.Name)
		assert.Equal(t, netv1beta1.PrivateLinkServiceSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: task.ProviderConfigName()},
			},
			ForProvider: netv1beta1.PrivateLinkServiceParameters{
				LoadBalancerFrontendIPConfigurationIds: []*string{ptr.To(lbResouceID)},
				ResourceGroupNameRef: &xpv1.Reference{
					Name: controllers.GetResourceGroupName(task.VPC),
				},
				Location: ptr.To(task.PrivateLinkService.Spec.Region),
				NATIPConfiguration: []netv1beta1.NATIPConfigurationParameters{
					{
						Name:        ptr.To(plsSubnetName),
						SubnetIDRef: &xpv1.Reference{Name: plsSubnetName},
						Primary:     ptr.To(true),
					},
				},
				Tags: util.ToMapPointer(tags),
			},
		}, ves.Spec)
	})
}

func TestPrivateLinkService_PostCreate(t *testing.T) {
	t.Run("get private link service failed", func(t *testing.T) {
		task := fakePrivateLinkService(t)
		result := task.PostCreate(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "not found")
		assert.Equal(t, defaultRequeuInterval, res.RequeueAfter)
	})
	t.Run("private link service not ready", func(t *testing.T) {
		task := fakePrivateLinkService(t)
		ves := &netv1beta1.PrivateLinkService{
			ObjectMeta: metav1.ObjectMeta{
				Name: getPrivateLinkServiceResourceName(task.PrivateLinkService),
			},
		}
		assert.Nil(t, task.Local.Create(context.Background(), ves))
		result := task.PostCreate(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "private link service is not ready")
		assert.Equal(t, defaultRequeuInterval, res.RequeueAfter)
	})
	t.Run("private link service is ready", func(t *testing.T) {
		task := fakePrivateLinkService(t)
		task.Config.PublicHostedZoneDomain = "public-hosted-zone-domain.com"
		task.PrivateLinkService.Status.DNSIdentify = "test-dns-identify"
		ves := &netv1beta1.PrivateLinkService{
			ObjectMeta: metav1.ObjectMeta{
				Name: getPrivateLinkServiceResourceName(task.PrivateLinkService),
				Labels: map[string]string{
					v1alpha1.PrivateLinkServiceNameKey: "pls-name",
				},
				Annotations: map[string]string{
					"crossplane.io/external-name": "pls-external-name",
				},
			},
			Status: netv1beta1.PrivateLinkServiceStatus{
				AtProvider: netv1beta1.PrivateLinkServiceObservation{
					Alias: ptr.To("alias-name"),
					ID:    ptr.To("pls-id"),
				},
				ResourceStatus: xpv1.ResourceStatus{
					ConditionedStatus: xpv1.ConditionedStatus{
						Conditions: []xpv1.Condition{
							{
								Type:   xpv1.TypeReady,
								Status: corev1.ConditionTrue,
							},
						},
					},
				},
			},
		}
		assert.Nil(t, task.Local.Create(context.Background(), ves))
		result := task.PostCreate(context.Background())
		assert.Equal(t, deploy.Done(), result)
		pls := &v1alpha1.PrivateLinkService{}
		assert.Nil(t, task.PrivateLinkServiceRW.Get(context.Background(), client.ObjectKeyFromObject(task.PrivateLinkService), pls))
		assert.Equal(t, v1alpha1.AzurePrivateLinkServiceStatus{
			Name:           "pls-external-name",
			ID:             "pls-id",
			Alias:          "alias-name",
			ServiceDNSName: "test-dns-identify.public-hosted-zone-domain.com",
		}, pls.Status.AzurePrivateLinkService)
	})
}

func TestPrivateLinkService_Delete(t *testing.T) {
	task := fakePrivateLinkService(t)
	ves := &netv1beta1.PrivateLinkService{
		ObjectMeta: metav1.ObjectMeta{
			Name: getPrivateLinkServiceResourceName(task.PrivateLinkService),
			Labels: map[string]string{
				v1alpha1.PrivateLinkServiceNameKey: "pls-name",
			},
			Annotations: map[string]string{
				"crossplane.io/external-name": "pls-external-name",
			},
		},
		Status: netv1beta1.PrivateLinkServiceStatus{
			AtProvider: netv1beta1.PrivateLinkServiceObservation{
				Alias: ptr.To("alias-name"),
				ID:    ptr.To("pls-id"),
			},
			ResourceStatus: xpv1.ResourceStatus{
				ConditionedStatus: xpv1.ConditionedStatus{
					Conditions: []xpv1.Condition{
						{
							Type:   xpv1.TypeReady,
							Status: corev1.ConditionTrue,
						},
					},
				},
			},
		},
	}
	assert.Nil(t, task.Local.Create(context.Background(), ves))
	result := task.Delete(context.Background())
	assert.Equal(t, deploy.Done(), result)

	ves = &netv1beta1.PrivateLinkService{}
	assert.Error(t, task.Local.Get(context.Background(), types.NamespacedName{Name: getPrivateLinkServiceResourceName(task.PrivateLinkService)}, ves))

	result = task.Delete(context.Background())
	assert.Equal(t, deploy.Done(), result)
}
