package cluster_test

import (
	"errors"
	"testing"

	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/aws/eks-anywhere/internal/test"
	anywherev1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1"
	"github.com/aws/eks-anywhere/pkg/cluster"
	"github.com/aws/eks-anywhere/pkg/templater"
	"github.com/aws/eks-anywhere/pkg/utils/ptr"
)

func TestConfigManagerParseSuccess(t *testing.T) {
	g := NewWithT(t)
	c := cluster.NewConfigManager()
	g.Expect(c.RegisterMapping(anywherev1.DockerDatacenterKind, func() cluster.APIObject {
		return &anywherev1.DockerDatacenterConfig{}
	})).To(Succeed())
	c.RegisterProcessors(func(c *cluster.Config, ol cluster.ObjectLookup) {
		d := ol.GetFromRef(c.Cluster.APIVersion, c.Cluster.Spec.DatacenterRef)
		c.DockerDatacenter = d.(*anywherev1.DockerDatacenterConfig)
	})

	kubeVersion := anywherev1.KubernetesVersion("1.20")
	yamlManifest := []byte(test.ReadFile(t, "testdata/docker_cluster_oidc_awsiam_flux.yaml"))
	wantCluster := &anywherev1.Cluster{
		TypeMeta: metav1.TypeMeta{
			Kind:       anywherev1.ClusterKind,
			APIVersion: anywherev1.SchemeBuilder.GroupVersion.String(),
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: "m-docker",
		},
		Spec: anywherev1.ClusterSpec{
			KubernetesVersion: "1.21",
			ManagementCluster: anywherev1.ManagementCluster{
				Name: "m-docker",
			},
			ControlPlaneConfiguration: anywherev1.ControlPlaneConfiguration{
				Count: 1,
			},
			WorkerNodeGroupConfigurations: []anywherev1.WorkerNodeGroupConfiguration{
				{
					Name:              "workers-1",
					Count:             ptr.Int(1),
					KubernetesVersion: &kubeVersion,
				},
			},
			DatacenterRef: anywherev1.Ref{
				Kind: anywherev1.DockerDatacenterKind,
				Name: "m-docker",
			},
			ClusterNetwork: anywherev1.ClusterNetwork{
				Pods: anywherev1.Pods{
					CidrBlocks: []string{"192.168.0.0/16"},
				},
				Services: anywherev1.Services{
					CidrBlocks: []string{"10.96.0.0/12"},
				},
				CNI: "cilium",
			},
			IdentityProviderRefs: []anywherev1.Ref{
				{
					Kind: anywherev1.OIDCConfigKind,
					Name: "eksa-unit-test",
				},
				{
					Kind: anywherev1.AWSIamConfigKind,
					Name: "eksa-unit-test",
				},
			},
			GitOpsRef: &anywherev1.Ref{
				Kind: anywherev1.FluxConfigKind,
				Name: "eksa-unit-test",
			},
		},
	}
	wantDockerDatacenter := &anywherev1.DockerDatacenterConfig{
		TypeMeta: metav1.TypeMeta{
			Kind:       anywherev1.DockerDatacenterKind,
			APIVersion: anywherev1.SchemeBuilder.GroupVersion.String(),
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: "m-docker",
		},
	}
	wantConfig := &cluster.Config{
		Cluster:          wantCluster,
		DockerDatacenter: wantDockerDatacenter,
	}

	config, err := c.Parse(yamlManifest)
	g.Expect(err).To(BeNil())
	g.Expect(config).To(Equal(wantConfig))
}

func TestConfigManagerParseMissingCluster(t *testing.T) {
	g := NewWithT(t)
	c := cluster.NewConfigManager()
	_, err := c.Parse([]byte{})
	g.Expect(err).To(MatchError(ContainSubstring("no Cluster found in manifest")))
}

func TestConfigManagerParseTwoClusters(t *testing.T) {
	g := NewWithT(t)
	manifest := `apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: Cluster
metadata:
  name: eksa-unit-test
---
apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: Cluster
metadata:
  name: eksa-unit-test-2
`
	c := cluster.NewConfigManager()
	_, err := c.Parse([]byte(manifest))
	g.Expect(err).To(MatchError(ContainSubstring("only one Cluster per yaml manifest is allowed")))
}

func TestConfigManagerParseUnknownKind(t *testing.T) {
	g := NewWithT(t)
	manifest := `apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: Cluster
metadata:
  name: eksa-unit-test
---
apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: MysteryCRD
`

	wantCluster := &anywherev1.Cluster{
		TypeMeta: metav1.TypeMeta{
			Kind:       anywherev1.ClusterKind,
			APIVersion: anywherev1.SchemeBuilder.GroupVersion.String(),
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: "eksa-unit-test",
		},
	}
	wantConfig := &cluster.Config{
		Cluster: wantCluster,
	}

	c := cluster.NewConfigManager()
	g.Expect(c.Parse([]byte(manifest))).To(Equal(wantConfig))
}

func TestConfigManagerMissingAPIVersion(t *testing.T) {
	g := NewWithT(t)
	c := cluster.NewConfigManager()
	g.Expect(c.RegisterMapping(anywherev1.DockerDatacenterKind, func() cluster.APIObject {
		return &anywherev1.DockerDatacenterConfig{}
	})).To(Succeed())
	c.RegisterProcessors(func(c *cluster.Config, ol cluster.ObjectLookup) {
		d := ol.GetFromRef(c.Cluster.APIVersion, c.Cluster.Spec.DatacenterRef)
		c.DockerDatacenter = d.(*anywherev1.DockerDatacenterConfig)
	})

	wantCluster := &anywherev1.Cluster{
		TypeMeta: metav1.TypeMeta{
			Kind:       anywherev1.ClusterKind,
			APIVersion: anywherev1.SchemeBuilder.GroupVersion.String(),
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: "m-docker",
		},
		Spec: anywherev1.ClusterSpec{
			KubernetesVersion: "1.21",
			ManagementCluster: anywherev1.ManagementCluster{
				Name: "m-docker",
			},
			ControlPlaneConfiguration: anywherev1.ControlPlaneConfiguration{
				Count: 1,
			},
			WorkerNodeGroupConfigurations: []anywherev1.WorkerNodeGroupConfiguration{
				{
					Name:  "workers-1",
					Count: ptr.Int(1),
				},
			},
			DatacenterRef: anywherev1.Ref{
				Kind: anywherev1.DockerDatacenterKind,
				Name: "m-docker",
			},
			ClusterNetwork: anywherev1.ClusterNetwork{
				Pods: anywherev1.Pods{
					CidrBlocks: []string{"192.168.0.0/16"},
				},
				Services: anywherev1.Services{
					CidrBlocks: []string{"10.96.0.0/12"},
				},
				CNI: "cilium",
			},
			IdentityProviderRefs: []anywherev1.Ref{
				{
					Kind: anywherev1.OIDCConfigKind,
					Name: "eksa-unit-test",
				},
				{
					Kind: anywherev1.AWSIamConfigKind,
					Name: "eksa-unit-test",
				},
			},
			GitOpsRef: &anywherev1.Ref{
				Kind: anywherev1.FluxConfigKind,
				Name: "eksa-unit-test",
			},
		},
	}
	wantDockerDatacenter := &anywherev1.DockerDatacenterConfig{
		TypeMeta: metav1.TypeMeta{
			Kind: anywherev1.DockerDatacenterKind,
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: "m-docker",
		},
	}

	yamlManifest, err := templater.ObjectsToYaml(wantCluster, wantDockerDatacenter)
	g.Expect(err).To(BeNil())

	_, err = c.Parse(yamlManifest)
	g.Expect(err).To(MatchError(ContainSubstring("apiVersion not set for Kind")))
}

func TestConfigManagerSetDefaultsSuccess(t *testing.T) {
	g := NewWithT(t)
	defaultNamespace := "default"
	c := cluster.NewConfigManager()
	c.RegisterDefaulters(func(c *cluster.Config) error {
		if c.Cluster.Namespace == "" {
			c.Cluster.Namespace = defaultNamespace
		}
		return nil
	})

	config := &cluster.Config{
		Cluster: &anywherev1.Cluster{
			TypeMeta: metav1.TypeMeta{
				Kind:       anywherev1.ClusterKind,
				APIVersion: anywherev1.SchemeBuilder.GroupVersion.String(),
			},
			ObjectMeta: metav1.ObjectMeta{
				Name: "eksa-unit-test",
			},
		},
	}

	g.Expect(c.SetDefaults(config)).To(Succeed())
	g.Expect(config.Cluster.Namespace).To(Equal(defaultNamespace))
}

func TestConfigManagerValidateMultipleErrors(t *testing.T) {
	g := NewWithT(t)
	c := cluster.NewConfigManager()
	c.RegisterValidations(
		func(c *cluster.Config) error {
			if c.Cluster.Namespace == "" {
				return errors.New("cluster namespace can't be empty")
			}
			return nil
		},
		func(c *cluster.Config) error {
			if c.Cluster.Name == "" {
				return errors.New("cluster name can't be empty")
			}
			return nil
		},
		func(c *cluster.Config) error {
			if c.Cluster.Spec.KubernetesVersion == "" {
				return errors.New("cluster kubernetes version can't be empty")
			}
			return nil
		},
	)

	config := &cluster.Config{
		Cluster: &anywherev1.Cluster{
			TypeMeta: metav1.TypeMeta{
				Kind:       anywherev1.ClusterKind,
				APIVersion: anywherev1.SchemeBuilder.GroupVersion.String(),
			},
			ObjectMeta: metav1.ObjectMeta{
				Name: "eksa-unit-test",
			},
		},
	}

	g.Expect(c.Validate(config)).To(MatchError(ContainSubstring(
		"invalid cluster config: [cluster namespace can't be empty, cluster kubernetes version can't be empty]",
	)))
}

func TestConfigManagerValidateMultipleSuccess(t *testing.T) {
	g := NewWithT(t)
	c := cluster.NewConfigManager()
	c.RegisterValidations(
		func(c *cluster.Config) error {
			if c.Cluster.Name == "" {
				return errors.New("cluster name can't be empty")
			}
			return nil
		},
	)

	config := &cluster.Config{
		Cluster: &anywherev1.Cluster{
			TypeMeta: metav1.TypeMeta{
				Kind:       anywherev1.ClusterKind,
				APIVersion: anywherev1.SchemeBuilder.GroupVersion.String(),
			},
			ObjectMeta: metav1.ObjectMeta{
				Name: "eksa-unit-test",
			},
		},
	}

	g.Expect(c.Validate(config)).To(Succeed())
}
