package ack

import (
	"context"
	"testing"

	alinatv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/natgateway/v1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/internet/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func newFakeNAT(ctx *task.Context) *natTask {
	return &natTask{BaseTask: &task.BaseTask{Context: ctx}}
}

func newFakeNATTask(t *testing.T) *natTask {
	c := fakeTaskContext(t)
	return newFakeNAT(c)
}

func initFakeSubnets(vpc *v1alpha1.VPC) {
	vpc.Status.PublicSubnets = []v1alpha1.Subnet{
		{
			Name:               "public-ap-southeast-1a",
			SubnetID:           "subnet-id-public-ap-southeast-1a",
			AvailabilityZone:   "ap-southeast-1a",
			AvailabilityZoneID: "ap-southeast-1a",
		},
		{
			Name:               "public-ap-southeast-1b",
			SubnetID:           "subnet-id-public-ap-southeast-1b",
			AvailabilityZone:   "ap-southeast-1b",
			AvailabilityZoneID: "ap-southeast-1b",
		},
		{
			Name:               "public-ap-southeast-1c",
			SubnetID:           "subnet-id-public-ap-southeast-1c",
			AvailabilityZone:   "ap-southeast-1c",
			AvailabilityZoneID: "ap-southeast-1c",
		},
	}
	vpc.Status.PrivateSubnets = []v1alpha1.Subnet{
		{
			Name:               "private-ap-southeast-1a",
			SubnetID:           "subnet-id-private-ap-southeast-1a",
			AvailabilityZone:   "ap-southeast-1a",
			AvailabilityZoneID: "ap-southeast-1a",
		},
		{
			Name:               "private-ap-southeast-1b",
			SubnetID:           "subnet-id-private-ap-southeast-1b",
			AvailabilityZone:   "ap-southeast-1b",
			AvailabilityZoneID: "ap-southeast-1b",
		},
		{
			Name:               "private-ap-southeast-1c",
			SubnetID:           "subnet-id-private-ap-southeast-1c",
			AvailabilityZone:   "ap-southeast-1c",
			AvailabilityZoneID: "ap-southeast-1c",
		},
	}
	vpc.Status.NodeSubnets = []v1alpha1.Subnet{
		{
			Name:               "node-ap-southeast-1a",
			SubnetID:           "subnet-id-node-ap-southeast-1a",
			AvailabilityZone:   "ap-southeast-1a",
			AvailabilityZoneID: "ap-southeast-1a",
		},
		{
			Name:               "node-ap-southeast-1b",
			SubnetID:           "subnet-id-node-ap-southeast-1b",
			AvailabilityZone:   "ap-southeast-1b",
			AvailabilityZoneID: "ap-southeast-1b",
		},
		{
			Name:               "node-ap-southeast-1c",
			SubnetID:           "subnet-id-node-ap-southeast-1c",
			AvailabilityZone:   "ap-southeast-1c",
			AvailabilityZoneID: "ap-southeast-1c",
		},
	}
}

func initFakeSingleNodeZoneSubnets(vpc *v1alpha1.VPC, zone string) {
	initFakeSubnets(vpc)
	vpc.Spec.NodeZones = []string{zone}
	vpc.Status.PublicSubnets = filterSubnetsByZone(vpc.Status.PublicSubnets, zone)
	vpc.Status.PrivateSubnets = filterSubnetsByZone(vpc.Status.PrivateSubnets, zone)
	vpc.Status.NodeSubnets = filterSubnetsByZone(vpc.Status.NodeSubnets, zone)
}

func filterSubnetsByZone(subnets []v1alpha1.Subnet, zone string) []v1alpha1.Subnet {
	filtered := make([]v1alpha1.Subnet, 0, 1)
	for _, subnet := range subnets {
		if subnet.AvailabilityZone == zone {
			filtered = append(filtered, subnet)
		}
	}
	return filtered
}

func TestNAT_createNAT(t *testing.T) {
	t.Run("subnet not found", func(t *testing.T) {
		nTask := newFakeNATTask(t)
		natName := "test-nat"
		zone := "ap-southeast-1a"
		tags := controllers.ConvertToCloudTags(nTask.Internet.Labels, consts.ProviderTypeACK)
		err := nTask.createNAT(context.Background(), natName, zone, tags)
		assert.Error(t, err)
	})

	t.Run("create nat successfully", func(t *testing.T) {
		nTask := newFakeNATTask(t)
		initFakeSubnets(nTask.VPC)
		natName := "test-nat"
		zone := "ap-southeast-1a"
		tags := controllers.ConvertToCloudTags(nTask.Internet.Labels, consts.ProviderTypeACK)
		err := nTask.createNAT(context.Background(), natName, zone, tags)
		assert.NoError(t, err)

		nat := &alinatv1alpha1.NATGateway{}
		err = nTask.Local.Get(context.Background(), client.ObjectKey{Name: natName}, nat)
		assert.NoError(t, err)

		expectedParams := alinatv1alpha1.NATGatewayParameters{
			NATGatewayName:     ptr.To(natName),
			Region:             ptr.To(nTask.Internet.Spec.Region),
			VPCIDRef:           &xpv1.Reference{Name: nTask.VPCName()},
			VswitchIDRef:       &xpv1.Reference{Name: "public-ap-southeast-1a"},
			NATType:            ptr.To(natType),
			EIPBindMode:        ptr.To(natEIPBindMode),
			PaymentType:        ptr.To(natPaymentType),
			InternetChargeType: ptr.To(natChargeType),
			NetworkType:        ptr.To(natNetworkType),
			Tags:               util.ToMapPointer(tags),
		}
		assert.Equal(t, natName, nat.Name)
		assert.Equal(t, expectedParams, nat.Spec.ForProvider)
	})
}

func TestNAT_Create(t *testing.T) {
	nTask := newFakeNATTask(t)
	initFakeSubnets(nTask.VPC)
	result := nTask.Create(context.Background())
	assert.True(t, result.IsDoneState())

	natName := nTask.getNATName("ap-southeast-1a")
	nat := &alinatv1alpha1.NATGateway{}
	err := nTask.Local.Get(context.Background(), client.ObjectKey{Name: natName}, nat)
	assert.NoError(t, err)
}

func TestNAT_CreateUsesNodeZones(t *testing.T) {
	nTask := newFakeNATTask(t)
	initFakeSingleNodeZoneSubnets(nTask.VPC, "ap-southeast-1c")
	result := nTask.Create(context.Background())
	assert.True(t, result.IsDoneState())

	natName := nTask.getNATName("ap-southeast-1c")
	nat := &alinatv1alpha1.NATGateway{}
	err := nTask.Local.Get(context.Background(), client.ObjectKey{Name: natName}, nat)
	assert.NoError(t, err)
	assert.Equal(t, "public-ap-southeast-1c", nat.Spec.ForProvider.VswitchIDRef.Name)
}

func TestNAT_PostCreate(t *testing.T) {
	t.Run("nat not found", func(t *testing.T) {
		nTask := newFakeNATTask(t)
		result := nTask.PostCreate(context.Background())
		assert.Error(t, result.Error())
	})

	t.Run("nat is not ready", func(t *testing.T) {
		nTask := newFakeNATTask(t)
		initFakeSubnets(nTask.VPC)
		result := nTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		result = nTask.PostCreate(context.Background())
		assert.Error(t, result.Error())
		assert.Contains(t, result.Error().Error(), "nat gateway")
	})

	t.Run("nat is ready", func(t *testing.T) {
		nTask := newFakeNATTask(t)
		initFakeSubnets(nTask.VPC)
		result := nTask.Create(context.Background())
		assert.True(t, result.IsDoneState())

		natName := nTask.getNATName("ap-southeast-1a")
		nat := &alinatv1alpha1.NATGateway{}
		err := nTask.Local.Get(context.Background(), client.ObjectKey{Name: natName}, nat)
		assert.NoError(t, err)
		nat.Status.SetConditions(xpv1.Available())
		expectedNatID := "nat-id"
		nat.Status.AtProvider.ID = ptr.To(expectedNatID)
		err = nTask.Local.Update(context.Background(), nat)
		assert.NoError(t, err)

		result = nTask.PostCreate(context.Background())
		assert.True(t, result.IsDoneState())
		assert.Len(t, nTask.Internet.Status.NATs, 1)
		assert.Equal(t, natName, nTask.Internet.Status.NATs[0].Name)
		assert.Equal(t, expectedNatID, nTask.Internet.Status.NATs[0].NATGatewayID)
	})
}

func TestNAT_Delete(t *testing.T) {
	t.Run("nat is deleted", func(t *testing.T) {
		nTask := newFakeNATTask(t)
		result := nTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())
	})
	t.Run("delete nat successfully", func(t *testing.T) {
		nTask := newFakeNATTask(t)
		initFakeSubnets(nTask.VPC)
		result := nTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		result = nTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())

		natName := nTask.getNATName("ap-southeast-1a")
		nat := &alinatv1alpha1.NATGateway{}
		err := nTask.Local.Get(context.Background(), client.ObjectKey{Name: natName}, nat)
		assert.Error(t, err)
		assert.True(t, apierrors.IsNotFound(err))
	})
}

func TestNAT_PostDelete(t *testing.T) {
	t.Run("nat exists", func(t *testing.T) {
		nTask := newFakeNATTask(t)
		initFakeSubnets(nTask.VPC)
		result := nTask.Create(context.Background())
		assert.True(t, result.IsDoneState())

		result = nTask.PostDelete(context.Background())
		assert.Error(t, result.Error())
		assert.Contains(t, result.Error().Error(), alinatv1alpha1.NATGateway{}.Kind)
	})

	t.Run("nat is deleted", func(t *testing.T) {
		nTask := newFakeNATTask(t)
		result := nTask.PostDelete(context.Background())
		assert.True(t, result.IsDoneState())
	})
}
