package clusterapi

import (
	"fmt"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
	addons "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1"
	"sigs.k8s.io/yaml"

	"github.com/aws/eks-anywhere/pkg/templater"
)

type ClusterResourceSet struct {
	resources   map[string][]byte
	clusterName string
	namespace   string
}

func NewClusterResourceSet(clusterName string) *ClusterResourceSet {
	return &ClusterResourceSet{
		clusterName: clusterName,
		namespace:   "default",
		resources:   make(map[string][]byte),
	}
}

func (c ClusterResourceSet) AddResource(name string, content []byte) {
	c.resources[name] = content
}

func (c ClusterResourceSet) ToYaml() ([]byte, error) {
	if len(c.resources) == 0 {
		return nil, nil
	}

	return marshall(append(c.buildResourceConfigMaps(), c.buildSet())...)
}

func (c ClusterResourceSet) buildSet() *addons.ClusterResourceSet {
	return &addons.ClusterResourceSet{
		TypeMeta: metav1.TypeMeta{
			APIVersion: addons.GroupVersion.Identifier(),
			Kind:       "ClusterResourceSet",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: fmt.Sprintf("%s-crs", c.clusterName),
			Labels: map[string]string{
				clusterv1.ClusterNameLabel: c.clusterName,
			},
			Namespace: c.namespace,
		},
		Spec: addons.ClusterResourceSetSpec{
			ClusterSelector: metav1.LabelSelector{
				MatchLabels: map[string]string{
					clusterv1.ClusterNameLabel: c.clusterName,
				},
			},
			Resources: c.resourceRefs(),
		},
	}
}

func (c ClusterResourceSet) resourceRefs() []addons.ResourceRef {
	refs := make([]addons.ResourceRef, 0, len(c.resources))

	for name := range c.resources {
		refs = append(refs, addons.ResourceRef{Name: name, Kind: string(addons.ConfigMapClusterResourceSetResourceKind)})
	}

	return refs
}

func (c ClusterResourceSet) buildResourceConfigMaps() []interface{} {
	cms := make([]interface{}, 0, len(c.resources))

	for name, content := range c.resources {
		cm := corev1.ConfigMap{
			TypeMeta: metav1.TypeMeta{
				APIVersion: "v1",
				Kind:       "ConfigMap",
			},
			ObjectMeta: metav1.ObjectMeta{
				Name:      name,
				Namespace: c.namespace,
			},
			Data: map[string]string{
				"data": string(content),
			},
		}
		cms = append(cms, cm)
	}

	return cms
}

func marshall(objects ...interface{}) ([]byte, error) {
	bytes := make([][]byte, 0, len(objects))
	for _, o := range objects {
		b, err := yaml.Marshal(o)
		if err != nil {
			return nil, fmt.Errorf("marshalling object for cluster resource set: %v", err)
		}

		bytes = append(bytes, b)
	}

	return templater.AppendYamlResources(bytes...), nil
}
