package aks

import (
	"context"
	"testing"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/stretchr/testify/assert"
	authv1beta1 "github.com/upbound/provider-azure/apis/authorization/v1beta1"
	containerv1beta1 "github.com/upbound/provider-azure/apis/containerservice/v1beta1"
	managedidentityv1beta1 "github.com/upbound/provider-azure/apis/managedidentity/v1beta1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"

	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
)

func fakeAKSManagedIdentity(t *testing.T) *AKSManagedIdentity {
	return NewAKSManagedIdentity(fakeTaskContext(t))
}

func markControlPlaneIdentityReadyForTest(t *testing.T, miTask *AKSManagedIdentity, principalID string) {
	t.Helper()
	identity := &managedidentityv1beta1.UserAssignedIdentity{}
	assert.NoError(t, miTask.Local.Get(context.Background(), types.NamespacedName{Name: aksControlPlaneManagedIdentityName(miTask.APPBox)}, identity))
	identity.Status.AtProvider.PrincipalID = ptr.To(principalID)
	identity.Status.SetConditions(xpv1.Condition{
		Type:   xpv1.TypeReady,
		Status: corev1.ConditionTrue,
	})
	assert.NoError(t, miTask.Local.Status().Update(context.Background(), identity))
}

func markRoleAssignmentsReadyForTest(t *testing.T, miTask *AKSManagedIdentity) {
	t.Helper()
	roleAssignments := &authv1beta1.RoleAssignmentList{}
	assert.NoError(t, miTask.Local.List(context.Background(), roleAssignments))
	for i := range roleAssignments.Items {
		roleAssignments.Items[i].Status.ConditionedStatus.SetConditions(xpv1.Condition{
			Type:   xpv1.TypeReady,
			Status: corev1.ConditionTrue,
		})
		assert.NoError(t, miTask.Local.Status().Update(context.Background(), &roleAssignments.Items[i]))
	}
}

func TestAKSManagedIdentity_Create(t *testing.T) {
	t.Run("create managed identity for new cluster", func(t *testing.T) {
		miTask := fakeAKSManagedIdentity(t)

		result := miTask.Create(context.Background())
		assert.Equal(t, deploy.Done(), result)

		identity := &managedidentityv1beta1.UserAssignedIdentity{}
		assert.NoError(t, miTask.Local.Get(context.Background(), types.NamespacedName{Name: aksControlPlaneManagedIdentityName(miTask.APPBox)}, identity))
		assert.Equal(t, ptr.To(aksControlPlaneManagedIdentityName(miTask.APPBox)), identity.Spec.ForProvider.Name)
		assert.Equal(t, ptr.To(controllers.GetResourceGroupName(miTask.VPC)), identity.Spec.ForProvider.ResourceGroupName)
	})

	t.Run("skip managed identity for existing provisioned cluster without annotation", func(t *testing.T) {
		miTask := fakeAKSManagedIdentity(t)
		assert.NoError(t, miTask.Local.Create(context.Background(), &containerv1beta1.KubernetesCluster{
			ObjectMeta: metav1.ObjectMeta{
				Name: aksClusterName(miTask.APPBox),
			},
			Status: containerv1beta1.KubernetesClusterStatus{
				AtProvider: containerv1beta1.KubernetesClusterObservation{
					ID: ptr.To("/subscriptions/subscription-id-1/resourceGroups/foo/providers/Microsoft.ContainerService/managedClusters/test-aks"),
				},
			},
		}))

		result := miTask.Create(context.Background())
		assert.Equal(t, deploy.Done(), result)

		identity := &managedidentityv1beta1.UserAssignedIdentity{}
		assert.ErrorContains(t, miTask.Local.Get(context.Background(), types.NamespacedName{Name: aksControlPlaneManagedIdentityName(miTask.APPBox)}, identity), "not found")
	})
}

func TestAKSManagedIdentity_PostCreate(t *testing.T) {
	t.Run("create vnet role assignment and wait until ready", func(t *testing.T) {
		miTask := fakeAKSManagedIdentity(t)
		assert.Equal(t, deploy.Done(), miTask.Create(context.Background()))

		markControlPlaneIdentityReadyForTest(t, miTask, "control-plane-principal-id")

		result := miTask.PostCreate(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "role assignment")
		assert.Equal(t, defaultRequeuInterval, res.RequeueAfter)

		roleAssignments := &authv1beta1.RoleAssignmentList{}
		assert.NoError(t, miTask.Local.List(context.Background(), roleAssignments))
		if assert.Len(t, roleAssignments.Items, 1) {
			roleAssignment := roleAssignments.Items[0]
			assert.Equal(t, getManagedIdentityVNetRoleAssignmentName(aksClusterName(miTask.APPBox)), roleAssignment.Name)
			assert.Equal(t, managedIdentityRoleAssignmentValue, roleAssignment.Labels[managedIdentityRoleAssignmentLabel])
			assert.Equal(t, ptr.To(networkContributorRole), roleAssignment.Spec.ForProvider.RoleDefinitionName)
			assert.Equal(t, ptr.To("control-plane-principal-id"), roleAssignment.Spec.ForProvider.PrincipalID)
		}

		markRoleAssignmentsReadyForTest(t, miTask)
		assert.Equal(t, deploy.Done(), miTask.PostCreate(context.Background()))
	})
}

func TestAKSManagedIdentity_Delete(t *testing.T) {
	t.Run("delete identity and managed role assignments", func(t *testing.T) {
		miTask := fakeAKSManagedIdentity(t)
		assert.Equal(t, deploy.Done(), miTask.Create(context.Background()))
		markControlPlaneIdentityReadyForTest(t, miTask, "control-plane-principal-id")
		_ = miTask.PostCreate(context.Background())

		roleAssignments := &authv1beta1.RoleAssignmentList{}
		assert.NoError(t, miTask.Local.List(context.Background(), roleAssignments))
		for i := range roleAssignments.Items {
			roleAssignments.Items[i].Status.ConditionedStatus.SetConditions(xpv1.Condition{
				Type:   xpv1.TypeReady,
				Status: corev1.ConditionTrue,
			})
			assert.NoError(t, miTask.Local.Status().Update(context.Background(), &roleAssignments.Items[i]))
		}

		assert.Equal(t, deploy.Done(), miTask.Delete(context.Background()))

		identity := &managedidentityv1beta1.UserAssignedIdentity{}
		assert.ErrorContains(t, miTask.Local.Get(context.Background(), types.NamespacedName{Name: aksControlPlaneManagedIdentityName(miTask.APPBox)}, identity), "not found")
	})
}
