package eks

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

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	"github.com/stretchr/testify/assert"
	"github.com/upbound/provider-aws/apis/ec2/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/pointer"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"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/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/namer"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
)

func fakeIPAddressTask(t *testing.T) *IPAddress {
	return NewIPAddress(fakeTaskContext(t))
}

func TestIPAddress_Create(t *testing.T) {
	t.Run("create success", func(t *testing.T) {
		task := fakeIPAddressTask(t)
		zones := task.GetNodeZones()
		name := task.InternetName()
		for _, zone := range zones {
			task.VPC.Status.PrivateSubnets = append(task.VPC.Status.PrivateSubnets, v1alpha1.Subnet{
				Name:               namer.PrivateSubnetName(name, zone),
				AvailabilityZone:   zone,
				AvailabilityZoneID: fmt.Sprintf("%s-id", zone),
			})
			assert.Nil(t, task.Local.Create(context.Background(), &v1beta1.Subnet{
				ObjectMeta: metav1.ObjectMeta{
					Name: namer.PrivateSubnetName(name, zone),
					Annotations: map[string]string{
						meta.AnnotationKeyExternalName: fmt.Sprintf("id-%s", zone),
					},
				},
			}))
		}
		assert.Nil(t, task.Local.Status().Update(context.Background(), task.VPC))
		t.Log(task.VPC.Status.PrivateSubnets)
		result := task.Create(context.Background())
		assert.True(t, result.IsDoneState())
		assert.Equal(t, len(zones), len(task.ipAddresses))
	})
}

func TestIPAddress_createIP(t *testing.T) {
	t.Run("no private subnet found", func(t *testing.T) {
		task := fakeIPAddressTask(t)
		tags := cloudv1.TagsFromLabels(task.Internet.Labels)
		_, err := task.createIP(context.Background(), "test", tags, "zone", task.VPC)
		assert.ErrorContains(t, err, "no private subnet found")
	})
	t.Run("create success", func(t *testing.T) {
		task := fakeIPAddressTask(t)
		zones := task.GetNodeZones()
		name := task.InternetName()
		tags := cloudv1.TagsFromLabels(task.Internet.Labels)
		for _, zone := range zones {
			task.VPC.Status.PrivateSubnets = append(task.VPC.Status.PrivateSubnets, v1alpha1.Subnet{
				Name:               namer.PrivateSubnetName(name, zone),
				AvailabilityZone:   zone,
				AvailabilityZoneID: fmt.Sprintf("%s-id", zone),
			})
			assert.Nil(t, task.Local.Create(context.Background(), &v1beta1.Subnet{
				ObjectMeta: metav1.ObjectMeta{
					Name: namer.PrivateSubnetName(name, zone),
					Annotations: map[string]string{
						meta.AnnotationKeyExternalName: fmt.Sprintf("id-%s", zone),
					},
				},
			}))
			ip, err := task.createIP(context.Background(), name, tags, zone, task.VPC)
			assert.Nil(t, err)
			assert.Equal(t, namer.PublicSubnetName(name, zone), ip.Name)
			assert.Equal(t, v1beta1.EIPSpec{
				ResourceSpec: xpv1.ResourceSpec{
					ProviderConfigReference: &xpv1.Reference{Name: task.ProviderConfigName()},
					ManagementPolicies:      controllers.GetResourceManagementPolicies(),
				},
				ForProvider: v1beta1.EIPParameters{
					Region: pointer.String(task.Internet.Spec.Region),
					Tags:   util.ToMapPointer(tags),
				},
				InitProvider: v1beta1.EIPInitParameters{
					Domain: pointer.String("vpc"),
				},
			}, ip.Spec)
		}
	})
}

func TestIPAddress_PostCreate(t *testing.T) {
	task := fakeIPAddressTask(t)
	zones := task.GetNodeZones()
	name := task.InternetName()
	for _, zone := range zones {
		task.VPC.Status.PrivateSubnets = append(task.VPC.Status.PrivateSubnets, v1alpha1.Subnet{
			Name:               namer.PrivateSubnetName(name, zone),
			AvailabilityZone:   zone,
			AvailabilityZoneID: fmt.Sprintf("%s-id", zone),
		})
		assert.Nil(t, task.Local.Create(context.Background(), &v1beta1.Subnet{
			ObjectMeta: metav1.ObjectMeta{
				Name: namer.PrivateSubnetName(name, zone),
				Annotations: map[string]string{
					meta.AnnotationKeyExternalName: fmt.Sprintf("id-%s", zone),
				},
			},
		}))
	}
	assert.Nil(t, task.Local.Status().Update(context.Background(), task.VPC))
	t.Run("ip not allocated", func(t *testing.T) {
		result := task.Create(context.Background())
		assert.True(t, result.IsDoneState())
		assert.Equal(t, len(zones), len(task.ipAddresses))
		result = task.PostCreate(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "failed to get IP")
		assert.Equal(t, time.Second*30, res.RequeueAfter)
	})
	t.Run("create success", func(t *testing.T) {
		for _, ip := range task.ipAddresses {
			ipRes := &v1beta1.EIP{}
			assert.Nil(t, task.Local.Get(context.Background(), types.NamespacedName{Name: ip.Name}, ipRes))
			ipRes.SetAnnotations(map[string]string{
				meta.AnnotationKeyExternalName: fmt.Sprintf("id-%s", ip.Name),
			})
			assert.Nil(t, task.Local.Update(context.Background(), ipRes))
		}
		result := task.PostCreate(context.Background())
		assert.True(t, result.IsDoneState())
		t.Log(result.Error())
	})
}

func TestIPAddress_deleteIP(t *testing.T) {
	t.Run("ip not found", func(t *testing.T) {
		task := fakeIPAddressTask(t)
		err := task.deleteIP(context.Background(), "not-exist", "zone")
		assert.Nil(t, err)
	})
	t.Run("delete success", func(t *testing.T) {
		task := fakeIPAddressTask(t)
		zones := task.GetNodeZones()
		name := task.InternetName()
		tags := cloudv1.TagsFromLabels(task.Internet.Labels)
		for _, zone := range zones {
			task.VPC.Status.PrivateSubnets = append(task.VPC.Status.PrivateSubnets, v1alpha1.Subnet{
				Name:               namer.PrivateSubnetName(name, zone),
				AvailabilityZone:   zone,
				AvailabilityZoneID: fmt.Sprintf("%s-id", zone),
			})
			assert.Nil(t, task.Local.Create(context.Background(), &v1beta1.Subnet{
				ObjectMeta: metav1.ObjectMeta{
					Name: namer.PrivateSubnetName(name, zone),
					Annotations: map[string]string{
						meta.AnnotationKeyExternalName: fmt.Sprintf("id-%s", zone),
					},
				},
			}))
			_, err := task.createIP(context.Background(), name, tags, zone, task.VPC)
			assert.Nil(t, err)
			assert.Nil(t, task.deleteIP(context.Background(), name, zone))
		}
	})
}

func TestIPAddress_Delete(t *testing.T) {
	t.Run("delete success", func(t *testing.T) {
		task := fakeIPAddressTask(t)
		zones := task.GetNodeZones()
		name := task.InternetName()
		tags := cloudv1.TagsFromLabels(task.Internet.Labels)
		for _, zone := range zones {
			task.VPC.Status.PrivateSubnets = append(task.VPC.Status.PrivateSubnets, v1alpha1.Subnet{
				Name:               namer.PrivateSubnetName(name, zone),
				AvailabilityZone:   zone,
				AvailabilityZoneID: fmt.Sprintf("%s-id", zone),
			})
			assert.Nil(t, task.Local.Create(context.Background(), &v1beta1.Subnet{
				ObjectMeta: metav1.ObjectMeta{
					Name: namer.PrivateSubnetName(name, zone),
					Annotations: map[string]string{
						meta.AnnotationKeyExternalName: fmt.Sprintf("id-%s", zone),
					},
				},
			}))
			_, err := task.createIP(context.Background(), name, tags, zone, task.VPC)
			assert.Nil(t, err)
		}
		assert.Nil(t, task.Local.Status().Update(context.Background(), task.VPC))
		result := task.Delete(context.Background())
		assert.True(t, result.IsDoneState())
	})
}
