package eks_test

import (
	"context"
	"testing"

	"github.com/go-logr/logr"
	"github.com/stretchr/testify/require"
	ec2v1beta1 "github.com/upbound/provider-aws/apis/ec2/v1beta1"
	eksv1beta1 "github.com/upbound/provider-aws/apis/eks/v1beta1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
	taskeks "github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task/eks"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/repository"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
)

func TestIsSafeToApplyEKS(t *testing.T) {
	s := runtime.NewScheme()
	require.Nil(t, v1alpha1.SchemeBuilder.AddToScheme(s))
	require.Nil(t, ec2v1beta1.SchemeBuilder.AddToScheme(s))
	require.Nil(t, eksv1beta1.SchemeBuilder.AddToScheme(s))

	config, err := config.LoadRootConfig("../../../../../examples/root-config-eks.yaml")
	require.Nil(t, err)
	defaultAPPBox := new(v1alpha1.APPBox)
	defaultVPC := new(v1alpha1.VPC)
	{
		bs, err := testdata.LoadEKSAPPBoxYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultAPPBox))
	}
	{
		bs, err := testdata.LoadEKSVPCYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultVPC))
	}
	{
		appbox := defaultAPPBox.DeepCopy()
		vpc := defaultVPC.DeepCopy()
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(vpc, appbox).WithStatusSubresource(vpc, appbox).Build()
		c, err := (&task.Context{
			Local:    kubeClient,
			Config:   config,
			VPC:      vpc,
			APPBox:   appbox,
			Logger:   logr.Discard(),
			APPBoxRW: repository.NewAPPBoxReaderWriter(kubeClient),
			VPCR:     repository.NewVPCReader(kubeClient),
		}).Default()
		require.Nil(t, err)
		// for now we do not have a way to mock aws.Client properly.
		eksTask := taskeks.NewEKS(c, nil, nil)
		cluster, err := eksTask.BuildEKS(context.Background())
		require.Nil(t, err)
		{
			ok, err := eksTask.IsSafeToApplyEKS(context.Background(), cluster)
			require.Nil(t, err)
			require.True(t, ok)
		}
		{
			// IsSafeToApplyEKS does not check status, label and annotation
			cluster.Status.AtProvider.Version = ptr.To("1.33.0")
			if cluster.Annotations == nil {
				cluster.Annotations = map[string]string{}
			}
			if cluster.Labels == nil {
				cluster.Labels = map[string]string{}
			}
			cluster.Annotations["foo"] = "foo"
			cluster.Labels["foo"] = "foo"
			ok, err := eksTask.IsSafeToApplyEKS(context.Background(), cluster)
			require.Nil(t, err)
			require.True(t, ok)
		}
		{
			// IsSafeToApplyEKS does not check status, label and annotation
			cluster.Spec.ForProvider.Version = ptr.To("1.33.0")
			ok, err := eksTask.IsSafeToApplyEKS(context.Background(), cluster)
			require.Nil(t, err)
			require.False(t, ok)
		}
	}
}

func TestCheckEKSVersionReady(t *testing.T) {
	s := runtime.NewScheme()
	require.Nil(t, v1alpha1.SchemeBuilder.AddToScheme(s))
	require.Nil(t, eksv1beta1.SchemeBuilder.AddToScheme(s))

	ctx := context.Background()

	t.Run("cluster not found", func(t *testing.T) {
		kubeClient := fake.NewClientBuilder().WithScheme(s).Build()
		err := taskeks.CheckEKSVersionReady(ctx, kubeClient, "non-existent-cluster", "1.34")
		require.Error(t, err)
		require.Contains(t, err.Error(), "failed to get EKS cluster")
	})

	t.Run("cluster is creating (status is CREATING)", func(t *testing.T) {
		cluster := &eksv1beta1.Cluster{}
		cluster.Name = "test-cluster"
		cluster.Spec.ForProvider.Version = ptr.To("1.34")
		cluster.Status.AtProvider.Version = ptr.To("1.33")
		cluster.Status.AtProvider.Status = ptr.To("CREATING")

		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(cluster).WithStatusSubresource(cluster).Build()
		err := taskeks.CheckEKSVersionReady(ctx, kubeClient, "test-cluster", "1.34")
		require.NoError(t, err)
	})

	t.Run("cluster is creating (status version is nil)", func(t *testing.T) {
		cluster := &eksv1beta1.Cluster{}
		cluster.Name = "test-cluster"
		cluster.Spec.ForProvider.Version = ptr.To("1.34")
		// Status.AtProvider.Version is nil (cluster is being created)

		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(cluster).Build()
		err := taskeks.CheckEKSVersionReady(ctx, kubeClient, "test-cluster", "1.34")
		require.NoError(t, err)
	})

	t.Run("cluster status is nil", func(t *testing.T) {
		cluster := &eksv1beta1.Cluster{}
		cluster.Name = "test-cluster"
		cluster.Spec.ForProvider.Version = ptr.To("1.34")
		cluster.Status.AtProvider.Version = ptr.To("1.34")
		// Status.AtProvider.Status is nil

		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(cluster).WithStatusSubresource(cluster).Build()
		err := taskeks.CheckEKSVersionReady(ctx, kubeClient, "test-cluster", "1.34")
		require.NoError(t, err)
	})

	t.Run("cluster is upgrading", func(t *testing.T) {
		cluster := &eksv1beta1.Cluster{}
		cluster.Name = "test-cluster"
		cluster.Spec.ForProvider.Version = ptr.To("1.34")
		cluster.Status.AtProvider.Version = ptr.To("1.33")
		cluster.Status.AtProvider.Status = ptr.To("ACTIVE")

		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(cluster).WithStatusSubresource(cluster).Build()
		err := taskeks.CheckEKSVersionReady(ctx, kubeClient, "test-cluster", "1.34")
		require.Error(t, err)
		require.Contains(t, err.Error(), "EKS cluster is upgrading from version 1.33 to 1.34")
	})

	t.Run("cluster version not match target version", func(t *testing.T) {
		cluster := &eksv1beta1.Cluster{}
		cluster.Name = "test-cluster"
		cluster.Spec.ForProvider.Version = ptr.To("1.33")
		cluster.Status.AtProvider.Version = ptr.To("1.33")
		cluster.Status.AtProvider.Status = ptr.To("ACTIVE")

		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(cluster).WithStatusSubresource(cluster).Build()
		err := taskeks.CheckEKSVersionReady(ctx, kubeClient, "test-cluster", "1.34")
		require.Error(t, err)
		require.Contains(t, err.Error(), "EKS cluster version 1.33 is not ready for target version 1.34")
	})

	t.Run("cluster version matches target version", func(t *testing.T) {
		cluster := &eksv1beta1.Cluster{}
		cluster.Name = "test-cluster"
		cluster.Spec.ForProvider.Version = ptr.To("1.34")
		cluster.Status.AtProvider.Version = ptr.To("1.34")
		cluster.Status.AtProvider.Status = ptr.To("ACTIVE")

		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(cluster).WithStatusSubresource(cluster).Build()
		err := taskeks.CheckEKSVersionReady(ctx, kubeClient, "test-cluster", "1.34")
		require.NoError(t, err)
	})
}

func TestBuildEKSIncludesControlPlaneSubnets(t *testing.T) {
	s := runtime.NewScheme()
	require.Nil(t, v1alpha1.SchemeBuilder.AddToScheme(s))
	require.Nil(t, ec2v1beta1.SchemeBuilder.AddToScheme(s))
	require.Nil(t, eksv1beta1.SchemeBuilder.AddToScheme(s))

	config, err := config.LoadRootConfig("../../../../../examples/root-config-eks.yaml")
	require.Nil(t, err)

	defaultAPPBox := new(v1alpha1.APPBox)
	defaultVPC := new(v1alpha1.VPC)
	{
		bs, loadErr := testdata.LoadEKSAPPBoxYAML()
		require.NoError(t, loadErr)
		require.NoError(t, yaml.Unmarshal(bs, defaultAPPBox))
	}
	{
		bs, loadErr := testdata.LoadEKSVPCYAML()
		require.NoError(t, loadErr)
		require.NoError(t, yaml.Unmarshal(bs, defaultVPC))
	}

	appbox := defaultAPPBox.DeepCopy()
	vpc := defaultVPC.DeepCopy()
	vpc.Status.NodeSubnets = []v1alpha1.Subnet{
		{Name: "node-subnet-b", SubnetID: "subnet-node-b", AvailabilityZone: "us-west-2b"},
	}
	vpc.Status.ControlPlaneSubnets = []v1alpha1.Subnet{
		{Name: "cp-subnet-a", SubnetID: "subnet-cp-a", AvailabilityZone: "us-west-2a"},
	}

	kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(vpc, appbox).WithStatusSubresource(vpc, appbox).Build()
	c, err := (&task.Context{
		Local:    kubeClient,
		Config:   config,
		VPC:      vpc,
		APPBox:   appbox,
		Logger:   logr.Discard(),
		APPBoxRW: repository.NewAPPBoxReaderWriter(kubeClient),
		VPCR:     repository.NewVPCReader(kubeClient),
	}).Default()
	require.Nil(t, err)

	eksTask := taskeks.NewEKS(c, nil, nil)
	cluster, err := eksTask.BuildEKS(context.Background())
	require.NoError(t, err)
	require.Len(t, cluster.Spec.ForProvider.VPCConfig, 1)
	require.Len(t, cluster.Spec.ForProvider.VPCConfig[0].SubnetIDRefs, 2)
	require.Equal(t, "cp-subnet-a", cluster.Spec.ForProvider.VPCConfig[0].SubnetIDRefs[0].Name)
	require.Equal(t, "node-subnet-b", cluster.Spec.ForProvider.VPCConfig[0].SubnetIDRefs[1].Name)
}

func TestBuildEKSKeepsRawCloudTagsButSanitizesMetadataLabels(t *testing.T) {
	s := runtime.NewScheme()
	require.NoError(t, v1alpha1.SchemeBuilder.AddToScheme(s))
	require.NoError(t, ec2v1beta1.SchemeBuilder.AddToScheme(s))
	require.NoError(t, eksv1beta1.SchemeBuilder.AddToScheme(s))

	rootConfig, err := config.LoadRootConfig("../../../../../examples/root-config-eks.yaml")
	require.NoError(t, err)
	rootConfig.AdditionalCloudTags = map[string]string{
		"cp9-owner":             "xuyifan02@pingcap.com",
		"cp9-session":           "cp9-david-263-1404",
		"cp9-ttl":               "2026-05-25T14:18:12Z",
		"aws:invalid-label-key": "must-not-be-projected",
		"empty-after-sanitize":  "!!!",
	}

	appbox := new(v1alpha1.APPBox)
	bs, err := testdata.LoadEKSAPPBoxYAML()
	require.NoError(t, err)
	require.NoError(t, yaml.Unmarshal(bs, appbox))

	vpc := new(v1alpha1.VPC)
	bs, err = testdata.LoadEKSVPCYAML()
	require.NoError(t, err)
	require.NoError(t, yaml.Unmarshal(bs, vpc))

	kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(vpc, appbox).WithStatusSubresource(vpc, appbox).Build()
	c, err := (&task.Context{
		Local:    kubeClient,
		Config:   rootConfig,
		VPC:      vpc,
		APPBox:   appbox,
		Logger:   logr.Discard(),
		APPBoxRW: repository.NewAPPBoxReaderWriter(kubeClient),
		VPCR:     repository.NewVPCReader(kubeClient),
	}).Default()
	require.NoError(t, err)

	cluster, err := taskeks.NewEKS(c, nil, nil).BuildEKS(context.Background())
	require.NoError(t, err)

	require.Equal(t, "xuyifan02@pingcap.com", ptr.Deref(cluster.Spec.ForProvider.Tags["cp9-owner"], ""))
	require.Equal(t, "2026-05-25T14:18:12Z", ptr.Deref(cluster.Spec.ForProvider.Tags["cp9-ttl"], ""))
	require.Equal(t, "must-not-be-projected", ptr.Deref(cluster.Spec.ForProvider.Tags["aws:invalid-label-key"], ""))

	require.Equal(t, "xuyifan02-pingcap.com", cluster.Labels["cp9-owner"])
	require.Equal(t, "2026-05-25T14-18-12Z", cluster.Labels["cp9-ttl"])
	require.Equal(t, "unknown", cluster.Labels["empty-after-sanitize"])
	require.NotContains(t, cluster.Labels, "aws:invalid-label-key")
}
