package ack

import (
	"context"
	"testing"

	alivpcv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/vpc/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"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func newFakeRoute(ctx *task.Context) *routeTask {
	return &routeTask{BaseTask: &task.BaseTask{Context: ctx}}
}

func newFakeRouteTask(t *testing.T) *routeTask {
	c := fakeTaskContext(t)
	return newFakeRoute(c)
}

func TestRoute_createRouteTable(t *testing.T) {
	rTask := newFakeRouteTask(t)
	name := rTask.getPublicRouteTableName()
	tags := controllers.ConvertToCloudTags(rTask.Internet.Labels, consts.ProviderTypeACK)
	rt, err := rTask.createRouteTable(context.Background(), name, tags)
	assert.NoError(t, err)

	expectedParams := alivpcv1alpha1.RouteTableParameters{
		RouteTableName: ptr.To(name),
		Region:         ptr.To(rTask.Internet.Spec.Region),
		VPCIDRef: &xpv1.Reference{
			Name: rTask.VPCName(),
		},
		AssociateType: ptr.To(routeTableAssociateType),
		Tags:          util.ToMapPointer(tags),
	}
	assert.Equal(t, name, rt.Name)
	assert.Equal(t, expectedParams, rt.Spec.ForProvider)
}

func TestRoute_createPrivateRoute(t *testing.T) {
	t.Run("nat not found", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		rTask.Internet.Status.NATs = nil
		err := rTask.createPrivateRoute(context.Background(), &alivpcv1alpha1.RouteTable{})
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "no nat gateway found")
	})

	t.Run("create private route successfully", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		rtName := rTask.getPrivateRouteTableName()
		tags := controllers.ConvertToCloudTags(rTask.Internet.Labels, consts.ProviderTypeACK)
		rt, err := rTask.createRouteTable(context.Background(), rtName, tags)
		assert.NoError(t, err)

		err = rTask.createPrivateRoute(context.Background(), rt)
		assert.NoError(t, err)

		expectedRouteName := rTask.getPrivateRouteName(rtName)
		routeEntry := &alivpcv1alpha1.RouteEntry{}
		err = rTask.Local.Get(context.Background(), client.ObjectKey{Name: expectedRouteName}, routeEntry)
		assert.NoError(t, err)

		expectedParams := alivpcv1alpha1.RouteEntryParameters{
			Name:   ptr.To(expectedRouteName),
			Region: ptr.To(rTask.Internet.Spec.Region),
			RouteTableIDRef: &xpv1.Reference{
				Name: rt.Name,
			},
			NexthopType:          ptr.To(routeNextHopTypeNAT),
			NexthopID:            ptr.To(rTask.Internet.Status.NATs[0].NATGatewayID),
			DestinationCidrblock: ptr.To("0.0.0.0/0"),
		}
		assert.Equal(t, expectedParams, routeEntry.Spec.ForProvider)
	})
}

func TestRoute_createPublicRoute(t *testing.T) {
	t.Run("internet gateway not found", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		rTask.Internet.Status.InternetGateway = v1alpha1.InternetGateway{}
		err := rTask.createPublicRoute(context.Background(), &alivpcv1alpha1.RouteTable{})
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "no internet gateway found")
	})

	t.Run("create public route successfully", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		rtName := rTask.getPublicRouteTableName()
		tags := controllers.ConvertToCloudTags(rTask.Internet.Labels, consts.ProviderTypeACK)
		rt, err := rTask.createRouteTable(context.Background(), rtName, tags)
		assert.NoError(t, err)

		err = rTask.createPublicRoute(context.Background(), rt)
		assert.NoError(t, err)

		expectedRouteName := rTask.getPublicRouteName(rtName)
		routeEntry := &alivpcv1alpha1.RouteEntry{}
		err = rTask.Local.Get(context.Background(), client.ObjectKey{Name: expectedRouteName}, routeEntry)
		assert.NoError(t, err)

		expectedParams := alivpcv1alpha1.RouteEntryParameters{
			Name:   ptr.To(expectedRouteName),
			Region: ptr.To(rTask.Internet.Spec.Region),
			RouteTableIDRef: &xpv1.Reference{
				Name: rt.Name,
			},
			NexthopType:          ptr.To(routeNextHopTypeIpv4Gateway),
			NexthopID:            ptr.To(rTask.Internet.Status.InternetGateway.InternetGatewayID),
			DestinationCidrblock: ptr.To("0.0.0.0/0"),
		}
		assert.Equal(t, expectedParams, routeEntry.Spec.ForProvider)
	})
}

func TestRoute_createRouteTableAttachment(t *testing.T) {
	rTask := newFakeRouteTask(t)
	initFakeSubnets(rTask.VPC)
	rtName := rTask.getPublicRouteTableName()
	tags := controllers.ConvertToCloudTags(rTask.Internet.Labels, consts.ProviderTypeACK)
	rt, err := rTask.createRouteTable(context.Background(), rtName, tags)
	assert.NoError(t, err)
	subnet := &rTask.VPC.Status.NodeSubnets[0]
	err = rTask.createRouteTableAttachment(context.Background(), rt, subnet)
	assert.NoError(t, err)

	expectedRtaName := rTask.getRouteTableAttachmentName(rt.Name, subnet.AvailabilityZone)
	rta := &alivpcv1alpha1.RouteTableAttachment{}
	err = rTask.Local.Get(context.Background(), client.ObjectKey{Name: expectedRtaName}, rta)
	assert.NoError(t, err)

	expectedParams := alivpcv1alpha1.RouteTableAttachmentParameters{
		Region: ptr.To(rTask.Internet.Spec.Region),
		RouteTableIDRef: &xpv1.Reference{
			Name: rt.Name,
		},
		VswitchIDRef: &xpv1.Reference{
			Name: subnet.Name,
		},
	}
	assert.Equal(t, expectedParams, rta.Spec.ForProvider)
}

func TestRoute_createPrivateRouteTable(t *testing.T) {
	rTask := newFakeRouteTask(t)
	initFakeSubnets(rTask.VPC)
	tags := controllers.ConvertToCloudTags(rTask.Internet.Labels, consts.ProviderTypeACK)
	err := rTask.createPrivateRouteTable(context.Background(), tags)
	assert.NoError(t, err)

	rtName := rTask.getPrivateRouteTableName()
	rt := &alivpcv1alpha1.RouteTable{}
	err = rTask.Local.Get(context.Background(), client.ObjectKey{Name: rtName}, rt)
	assert.NoError(t, err)
	reName := rTask.getPrivateRouteName(rtName)
	re := &alivpcv1alpha1.RouteEntry{}
	err = rTask.Local.Get(context.Background(), client.ObjectKey{Name: reName}, re)
	assert.NoError(t, err)
	for i := range rTask.VPC.Status.NodeSubnets {
		subnet := &rTask.VPC.Status.NodeSubnets[i]
		rtaName := rTask.getRouteTableAttachmentName(rt.Name, subnet.AvailabilityZone)
		rta := &alivpcv1alpha1.RouteTableAttachment{}
		err = rTask.Local.Get(context.Background(), client.ObjectKey{Name: rtaName}, rta)
		assert.NoError(t, err)
		assert.Equal(t, subnet.Name, rta.Spec.ForProvider.VswitchIDRef.Name)
	}
}

func TestRoute_createPublicRouteTable(t *testing.T) {
	rTask := newFakeRouteTask(t)
	initFakeSubnets(rTask.VPC)
	tags := controllers.ConvertToCloudTags(rTask.Internet.Labels, consts.ProviderTypeACK)
	err := rTask.createPublicRouteTable(context.Background(), tags)
	assert.NoError(t, err)

	rtName := rTask.getPublicRouteTableName()
	rt := &alivpcv1alpha1.RouteTable{}
	err = rTask.Local.Get(context.Background(), client.ObjectKey{Name: rtName}, rt)
	assert.NoError(t, err)
	reName := rTask.getPublicRouteName(rtName)
	re := &alivpcv1alpha1.RouteEntry{}
	err = rTask.Local.Get(context.Background(), client.ObjectKey{Name: reName}, re)
	assert.NoError(t, err)
	for i := range rTask.VPC.Status.PublicSubnets {
		subnet := &rTask.VPC.Status.PublicSubnets[i]
		rtaName := rTask.getRouteTableAttachmentName(rt.Name, subnet.AvailabilityZone)
		rta := &alivpcv1alpha1.RouteTableAttachment{}
		err = rTask.Local.Get(context.Background(), client.ObjectKey{Name: rtaName}, rta)
		assert.NoError(t, err)
		assert.Equal(t, subnet.Name, rta.Spec.ForProvider.VswitchIDRef.Name)
	}
}

func TestRoute_Create(t *testing.T) {
	rTask := newFakeRouteTask(t)
	initFakeSubnets(rTask.VPC)
	result := rTask.Create(context.Background())
	assert.True(t, result.IsDoneState())

	rtList := &alivpcv1alpha1.RouteTableList{}
	err := rTask.Local.List(context.Background(), rtList)
	assert.NoError(t, err)
	assert.Len(t, rtList.Items, 2)

	reList := &alivpcv1alpha1.RouteEntryList{}
	err = rTask.Local.List(context.Background(), reList)
	assert.NoError(t, err)
	assert.Len(t, reList.Items, 2)

	rtaList := &alivpcv1alpha1.RouteTableAttachmentList{}
	err = rTask.Local.List(context.Background(), rtaList)
	assert.NoError(t, err)
	assert.Len(t, rtaList.Items, 6)
}

func TestRoute_PostCreate(t *testing.T) {
	t.Run("route resources not found", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		result := rTask.PostCreate(context.Background())
		assert.Error(t, result.Error())
		assert.Contains(t, result.Error().Error(), "not found")
	})

	t.Run("not all route tables ready", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		initFakeSubnets(rTask.VPC)
		result := rTask.Create(context.Background())
		assert.True(t, result.IsDoneState())

		rtList := &alivpcv1alpha1.RouteTableList{}
		err := rTask.Local.List(context.Background(), rtList)
		assert.NoError(t, err)
		rt := &rtList.Items[0]
		rt.Status.SetConditions(xpv1.Available())
		rt.Status.AtProvider.ID = ptr.To(rt.Name + "-id")
		err = rTask.Local.Update(context.Background(), rt)
		assert.NoError(t, err)

		result = rTask.PostCreate(context.Background())
		assert.Error(t, result.Error())
		assert.Contains(t, result.Error().Error(), "route table")
	})

	t.Run("not all route entries ready", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		initFakeSubnets(rTask.VPC)
		result := rTask.Create(context.Background())
		assert.True(t, result.IsDoneState())

		rtList := &alivpcv1alpha1.RouteTableList{}
		err := rTask.Local.List(context.Background(), rtList)
		assert.NoError(t, err)
		for i := range rtList.Items {
			rt := &rtList.Items[i]
			rt.Status.SetConditions(xpv1.Available())
			rt.Status.AtProvider.ID = ptr.To(rt.Name + "-id")
			err := rTask.Local.Update(context.Background(), rt)
			assert.NoError(t, err)
		}
		reList := &alivpcv1alpha1.RouteEntryList{}
		err = rTask.Local.List(context.Background(), reList)
		assert.NoError(t, err)
		re := &reList.Items[0]
		re.Status.SetConditions(xpv1.Available())
		err = rTask.Local.Update(context.Background(), re)
		assert.NoError(t, err)

		result = rTask.PostCreate(context.Background())
		assert.Error(t, result.Error())
		assert.Contains(t, result.Error().Error(), "route entry")
	})

	t.Run("not all route table attachments ready", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		initFakeSubnets(rTask.VPC)
		result := rTask.Create(context.Background())
		assert.True(t, result.IsDoneState())

		rtList := &alivpcv1alpha1.RouteTableList{}
		err := rTask.Local.List(context.Background(), rtList)
		assert.NoError(t, err)
		for i := range rtList.Items {
			rt := &rtList.Items[i]
			rt.Status.SetConditions(xpv1.Available())
			rt.Status.AtProvider.ID = ptr.To(rt.Name + "-id")
			err := rTask.Local.Update(context.Background(), rt)
			assert.NoError(t, err)
		}
		reList := &alivpcv1alpha1.RouteEntryList{}
		err = rTask.Local.List(context.Background(), reList)
		assert.NoError(t, err)
		for i := range reList.Items {
			re := &reList.Items[i]
			re.Status.SetConditions(xpv1.Available())
			err := rTask.Local.Update(context.Background(), re)
			assert.NoError(t, err)
		}
		rtaList := &alivpcv1alpha1.RouteTableAttachmentList{}
		err = rTask.Local.List(context.Background(), rtaList)
		assert.NoError(t, err)
		rta := &rtaList.Items[0]
		rta.Status.SetConditions(xpv1.Available())
		err = rTask.Local.Update(context.Background(), rta)
		assert.NoError(t, err)

		result = rTask.PostCreate(context.Background())
		assert.Error(t, result.Error())
		assert.Contains(t, result.Error().Error(), "route table attachment")
	})

	t.Run("all route resources ready", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		initFakeSubnets(rTask.VPC)
		result := rTask.Create(context.Background())
		assert.True(t, result.IsDoneState())

		rtList := &alivpcv1alpha1.RouteTableList{}
		err := rTask.Local.List(context.Background(), rtList)
		assert.NoError(t, err)
		for i := range rtList.Items {
			rt := &rtList.Items[i]
			rt.Status.SetConditions(xpv1.Available())
			rt.Status.AtProvider.ID = ptr.To(rt.Name + "-id")
			err := rTask.Local.Update(context.Background(), rt)
			assert.NoError(t, err)
		}
		reList := &alivpcv1alpha1.RouteEntryList{}
		err = rTask.Local.List(context.Background(), reList)
		assert.NoError(t, err)
		for i := range reList.Items {
			re := &reList.Items[i]
			re.Status.SetConditions(xpv1.Available())
			err := rTask.Local.Update(context.Background(), re)
			assert.NoError(t, err)
		}
		rtaList := &alivpcv1alpha1.RouteTableAttachmentList{}
		err = rTask.Local.List(context.Background(), rtaList)
		assert.NoError(t, err)
		for i := range rtaList.Items {
			rta := &rtaList.Items[i]
			rta.Status.SetConditions(xpv1.Available())
			err := rTask.Local.Update(context.Background(), rta)
			assert.NoError(t, err)
		}

		result = rTask.PostCreate(context.Background())
		assert.True(t, result.IsDoneState())
	})
}

func TestRoute_Delete(t *testing.T) {
	t.Run("route resources are deleted", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		result := rTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())
	})

	t.Run("delete route resources successfully", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		initFakeSubnets(rTask.VPC)
		result := rTask.Create(context.Background())
		assert.True(t, result.IsDoneState())

		routeTables, routes, routeTableAttachments, err := rTask.listRouteTables(context.Background())
		assert.NoError(t, err)
		assert.NotEmpty(t, routeTables)
		assert.NotEmpty(t, routes)
		assert.NotEmpty(t, routeTableAttachments)

		result = rTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())

		routeTables, routes, routeTableAttachments, err = rTask.listRouteTables(context.Background())
		assert.NoError(t, err)
		assert.Empty(t, routeTables)
		assert.Empty(t, routes)
		assert.Empty(t, routeTableAttachments)
	})
}

func TestRoute_PostDelete(t *testing.T) {
	t.Run("not all route tables deleted", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		initFakeSubnets(rTask.VPC)
		result := rTask.Create(context.Background())
		assert.True(t, result.IsDoneState())

		rtList := &alivpcv1alpha1.RouteTableList{}
		err := rTask.Local.List(context.Background(), rtList)
		assert.NoError(t, err)
		rt := &rtList.Items[0]
		err = rTask.Local.Delete(context.Background(), rt)
		assert.NoError(t, err)

		result = rTask.PostDelete(context.Background())
		assert.Error(t, result.Error())
		assert.Contains(t, result.Error().Error(), "route tables")
	})

	t.Run("not all route entries deleted", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		initFakeSubnets(rTask.VPC)
		result := rTask.Create(context.Background())
		assert.True(t, result.IsDoneState())

		rtList := &alivpcv1alpha1.RouteTableList{}
		err := rTask.Local.List(context.Background(), rtList)
		assert.NoError(t, err)
		for i := range rtList.Items {
			rt := &rtList.Items[i]
			err := rTask.Local.Delete(context.Background(), rt)
			assert.NoError(t, err)
		}
		reList := &alivpcv1alpha1.RouteEntryList{}
		err = rTask.Local.List(context.Background(), reList)
		assert.NoError(t, err)
		re := &reList.Items[0]
		err = rTask.Local.Delete(context.Background(), re)
		assert.NoError(t, err)

		result = rTask.PostDelete(context.Background())
		assert.Error(t, result.Error())
		assert.Contains(t, result.Error().Error(), "route entries")
	})

	t.Run("not all route table attachments deleted", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		initFakeSubnets(rTask.VPC)
		result := rTask.Create(context.Background())
		assert.True(t, result.IsDoneState())

		rtList := &alivpcv1alpha1.RouteTableList{}
		err := rTask.Local.List(context.Background(), rtList)
		assert.NoError(t, err)
		for i := range rtList.Items {
			rt := &rtList.Items[i]
			err := rTask.Local.Delete(context.Background(), rt)
			assert.NoError(t, err)
		}
		reList := &alivpcv1alpha1.RouteEntryList{}
		err = rTask.Local.List(context.Background(), reList)
		assert.NoError(t, err)
		for i := range reList.Items {
			re := &reList.Items[i]
			err := rTask.Local.Delete(context.Background(), re)
			assert.NoError(t, err)
		}
		rtaList := &alivpcv1alpha1.RouteTableAttachmentList{}
		err = rTask.Local.List(context.Background(), rtaList)
		assert.NoError(t, err)
		rta := &rtaList.Items[0]
		err = rTask.Local.Delete(context.Background(), rta)
		assert.NoError(t, err)

		result = rTask.PostDelete(context.Background())
		assert.Error(t, result.Error())
		assert.Contains(t, result.Error().Error(), "route table attachments")
	})

	t.Run("route resources are deleted", func(t *testing.T) {
		rTask := newFakeRouteTask(t)
		result := rTask.PostDelete(context.Background())
		assert.True(t, result.IsDoneState())
	})
}
