package cilium

import (
	"context"
	"strings"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

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

const (
	// DaemonSetName is the default name for the Cilium DS installed in EKS-A clusters.
	DaemonSetName = "cilium"
	// PreflightDaemonSetName is the default name for the Cilium preflight DS installed
	// in EKS-A clusters during Cilium upgrades.
	PreflightDaemonSetName = "cilium-pre-flight-check"
	// DeploymentName is the default name for the Cilium Operator installed in EKS-A clusters.
	DeploymentName = "cilium-operator"
	// PreflightDeploymentName is the default name for the Cilium preflight deployment used during upgrades.
	PreflightDeploymentName = "cilium-pre-flight-check"
	// ConfigMapName is the default name for the Cilium ConfigMap
	// containing Cilium's configuration.
	ConfigMapName = "cilium-config"
	// ServiceName is the default name for the Cilium Service installed in EKS-A clusters.
	ServiceName = "cilium-agent"

	ciliumConfigMapName   = "cilium-config"
	ciliumConfigNamespace = "kube-system"
)

// Installation is an installation of EKSA Cilium components.
type Installation struct {
	DaemonSet *appsv1.DaemonSet
	Operator  *appsv1.Deployment
	ConfigMap *corev1.ConfigMap
}

// Installed determines if all EKS-A Embedded Cilium components are present. It identifies
// EKS-A Embedded Cilium by the image name. If the ConfigMap doesn't exist we still considered
// Cilium is installed. The installation might not be complete but it can be functional.
func (i Installation) Installed() bool {
	var isEKSACilium bool
	if i.DaemonSet != nil {
		for _, c := range i.DaemonSet.Spec.Template.Spec.Containers {
			isEKSACilium = isEKSACilium || strings.Contains(c.Image, "eks")
		}
	}
	return i.DaemonSet != nil && i.Operator != nil && isEKSACilium
}

// GetInstallation creates a new Installation instance. The returned installation's DaemonSet,
// Operator and ConfigMap fields will be nil if they could not be found within the target cluster.
func GetInstallation(ctx context.Context, client client.Client) (*Installation, error) {
	ds, err := getDaemonSet(ctx, client)
	if err != nil {
		return nil, err
	}

	operator, err := getDeployment(ctx, client)
	if err != nil {
		return nil, err
	}

	cm, err := getConfigMap(ctx, client, ciliumConfigMapName, ciliumConfigNamespace)
	if err != nil {
		return nil, err
	}

	return &Installation{
		DaemonSet: ds,
		Operator:  operator,
		ConfigMap: cm,
	}, nil
}

func getDaemonSet(ctx context.Context, client client.Client) (*appsv1.DaemonSet, error) {
	ds := &appsv1.DaemonSet{}
	err := client.Get(ctx, types.NamespacedName{Name: DaemonSetName, Namespace: constants.KubeSystemNamespace}, ds)
	if apierrors.IsNotFound(err) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	return ds, nil
}

func getConfigMap(ctx context.Context, client client.Client, name string, namespace string) (*corev1.ConfigMap, error) {
	c := &corev1.ConfigMap{}
	err := client.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, c)
	if apierrors.IsNotFound(err) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	return c, nil
}

func getDeployment(ctx context.Context, client client.Client) (*appsv1.Deployment, error) {
	deployment := &appsv1.Deployment{}
	key := types.NamespacedName{
		Name:      DeploymentName,
		Namespace: constants.KubeSystemNamespace,
	}
	err := client.Get(ctx, key, deployment)
	if apierrors.IsNotFound(err) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	return deployment, nil
}
