package gke

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

	gcpv1beta1 "github.com/crossplane-contrib/provider-gcp/apis/compute/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"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/regional-server/pkg/controllers"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/pointer"
)

func fakeSubnetTask(t *testing.T) (*Subnet, *VPC) {
	subnetTask := NewSubnet(fakeTaskContext(t))
	vpcTask := NewVPC(subnetTask.Context)
	return subnetTask, vpcTask
}

func TestSubnet_Create(t *testing.T) {
	t.Run("gke network not ready", func(t *testing.T) {
		subnetTask, vpcTask := fakeSubnetTask(t)
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		result = subnetTask.Create(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, time.Second*30, res.RequeueAfter)
	})
	t.Run("create subnet success", func(t *testing.T) {
		subnetTask, vpcTask := fakeSubnetTask(t)
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		name := vpcTask.GetNetworkName()
		networkRes := &gcpv1beta1.Network{}
		assert.Nil(t, subnetTask.Local.Get(context.Background(), types.NamespacedName{Name: name}, networkRes))
		networkRes.Status.ConditionedStatus.SetConditions(xpv1.Condition{
			Type:   xpv1.TypeReady,
			Status: corev1.ConditionTrue,
		})
		assert.Nil(t, subnetTask.Local.Status().Update(context.Background(), networkRes))
		result = subnetTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		cidrDispatcher, err := controllers.GetGKECIDRDispatcher(vpcTask.VPC, subnetTask.Config)
		assert.Nil(t, err)
		subnetName := subnetTask.GetGKESubnetName()
		subnetRes := &gcpv1beta1.Subnetwork{}
		assert.Nil(t, subnetTask.Local.Get(context.Background(), types.NamespacedName{Name: subnetName}, subnetRes))
		tags := cloudv1.TagsFromLabels(vpcTask.VPC.Labels)
		assert.Equal(t, gcpv1beta1.SubnetworkSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: subnetTask.ProviderConfigName(),
				},
			},
			ForProvider: gcpv1beta1.SubnetworkParameters{
				IPCidrRange:           cidrDispatcher.NodeIPv4CIDRBlock(),
				Region:                vpcTask.VPC.Spec.Region,
				Description:           pointer.String(fmt.Sprintf("Tags: %v", tags)),
				PrivateIPGoogleAccess: pointer.Bool(true),
				NetworkRef: &xpv1.Reference{
					Name: networkRes.Name,
				},
			},
		}, subnetRes.Spec)
	})
}

func TestSubnet_PostCreate(t *testing.T) {
	t.Run("subnet not ready", func(t *testing.T) {
		subnetTask, vpcTask := fakeSubnetTask(t)
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		name := vpcTask.GetNetworkName()
		networkRes := &gcpv1beta1.Network{}
		assert.Nil(t, subnetTask.Local.Get(context.Background(), types.NamespacedName{Name: name}, networkRes))
		networkRes.Status.ConditionedStatus.SetConditions(xpv1.Condition{
			Type:   xpv1.TypeReady,
			Status: corev1.ConditionTrue,
		})
		assert.Nil(t, subnetTask.Local.Status().Update(context.Background(), networkRes))
		result = subnetTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		result = subnetTask.PostCreate(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "subnet status not ready")
		assert.Equal(t, time.Second*30, res.RequeueAfter)
	})
	t.Run("subnet ready", func(t *testing.T) {
		subnetTask, vpcTask := fakeSubnetTask(t)
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		name := vpcTask.GetNetworkName()
		networkRes := &gcpv1beta1.Network{}
		assert.Nil(t, subnetTask.Local.Get(context.Background(), types.NamespacedName{Name: name}, networkRes))
		networkRes.Status.ConditionedStatus.SetConditions(xpv1.Condition{
			Type:   xpv1.TypeReady,
			Status: corev1.ConditionTrue,
		})
		assert.Nil(t, subnetTask.Local.Status().Update(context.Background(), networkRes))
		result = subnetTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		subnetName := subnetTask.GetGKESubnetName()
		subnetRes := &gcpv1beta1.Subnetwork{}
		assert.Nil(t, subnetTask.Local.Get(context.Background(), types.NamespacedName{Name: subnetName}, subnetRes))
		subnetRes.Status.ConditionedStatus.SetConditions(xpv1.Condition{
			Type:   xpv1.TypeReady,
			Status: corev1.ConditionTrue,
		})
		assert.Nil(t, subnetTask.Local.Status().Update(context.Background(), subnetRes))
		result = subnetTask.PostCreate(context.Background())
		assert.True(t, result.IsDoneState())
	})
}

func TestSubnet_Delete(t *testing.T) {
	t.Run("delete subnet success", func(t *testing.T) {
		subnetTask, vpcTask := fakeSubnetTask(t)
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		name := vpcTask.GetNetworkName()
		networkRes := &gcpv1beta1.Network{}
		assert.Nil(t, subnetTask.Local.Get(context.Background(), types.NamespacedName{Name: name}, networkRes))
		networkRes.Status.ConditionedStatus.SetConditions(xpv1.Condition{
			Type:   xpv1.TypeReady,
			Status: corev1.ConditionTrue,
		})
		assert.Nil(t, subnetTask.Local.Status().Update(context.Background(), networkRes))
		result = subnetTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		result = subnetTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())
		// repeat delete subnet
		result = subnetTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())
	})
}
