// Copyright (c) 2023 Volvo Car Corporation
// SPDX-License-Identifier: Apache-2.0

// Code generated by lingon. EDIT AS MUCH AS YOU LIKE.

package externalsecrets

import (
	"context"
	"errors"
	"os"
	"os/exec"

	"github.com/golingon/lingon/pkg/kube"
	"github.com/golingon/lingon/pkg/kubeutil"
	arv1 "k8s.io/api/admissionregistration/v1"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
)

const (
	AppName            = "external-secrets"
	webhookName        = AppName + "-webhook"
	controllerName     = AppName + "-controller"
	certControllerName = AppName + "-cert-controller"
	Namespace          = "external-secrets"
	Version            = "0.7.2"
	// FOR DEPLOYMENT
	containerImage         = "ghcr.io/external-secrets/external-secrets:v" + Version
	portMetric             = 8080
	healthzPort            = 8081
	webhookPort            = 10250
	webhookSecretMountPath = "/tmp/certs"
)

var _ kube.Exporter = (*ExternalSecrets)(nil)

type ExternalSecrets struct {
	kube.App

	NS *corev1.Namespace

	SecretsWebhook *corev1.Secret

	CertControllerDeploy *appsv1.Deployment
	CertControllerCR     *rbacv1.ClusterRole
	CertControllerCRB    *rbacv1.ClusterRoleBinding
	CertControllerSA     *corev1.ServiceAccount

	ControllerCR  *rbacv1.ClusterRole
	ControllerCRB *rbacv1.ClusterRoleBinding

	EditCR *rbacv1.ClusterRole
	ViewCR *rbacv1.ClusterRole

	SA     *corev1.ServiceAccount
	Deploy *appsv1.Deployment

	WebhookSA     *corev1.ServiceAccount
	WebhookDeploy *appsv1.Deployment
	WebhookSVC    *corev1.Service

	ExternalSecretValidateWH *arv1.ValidatingWebhookConfiguration
	SecretStoreValidateWH    *arv1.ValidatingWebhookConfiguration

	LeaderElectionRole *rbacv1.Role
	LeaderElectionRB   *rbacv1.RoleBinding
}

func New() *ExternalSecrets {
	sa := kubeutil.ServiceAccount(
		AppName,
		Namespace,
		ESLabels,
		nil,
	)
	certControllerSA := kubeutil.ServiceAccount(
		certControllerName,
		Namespace,
		CertControllerLabels,
		nil,
	)
	webhookSA := kubeutil.ServiceAccount(
		webhookName,
		Namespace,
		WebhookLabels,
		nil,
	)

	return &ExternalSecrets{
		NS: kubeutil.Namespace(Namespace, ESLabels, nil),

		// CONTROLLER
		ControllerCR: ControllerCr,
		ControllerCRB: kubeutil.BindClusterRole(
			controllerName,
			sa,
			ControllerCr,
			ESLabels,
		),
		// CERT-CONTROLLER
		CertControllerDeploy: kubeutil.SetDeploySA(
			CertControllerDeploy,
			certControllerSA.Name,
		),
		CertControllerSA: certControllerSA,
		CertControllerCR: CertControllerCR,
		CertControllerCRB: kubeutil.BindClusterRole(
			certControllerName,
			sa,
			CertControllerCR,
			CertControllerLabels,
		),

		// Assumption: this is for applications to access the external secrets
		EditCR: EditCR,
		ViewCR: ViewCR,

		// Main External Secrets
		SA:     sa,
		Deploy: kubeutil.SetDeploySA(Deploy, sa.Name),

		// WEBHOOK
		ExternalSecretValidateWH: ExternalsecretValidateValidatingWH,
		SecretStoreValidateWH:    SecretstoreValidateValidatingWH,

		WebhookSA:     webhookSA,
		WebhookDeploy: kubeutil.SetDeploySA(WebhookDeploy, webhookSA.Name),
		WebhookSVC:    WebhookSVC,

		// Empty secret for webhook
		SecretsWebhook: &corev1.Secret{
			TypeMeta: kubeutil.TypeSecretV1,
			ObjectMeta: kubeutil.ObjectMeta(
				webhookName, // secret name used in CertControllerDeploy and WebhookDeploy
				Namespace,   // secret namespace used in CertControllerDeploy
				WebhookLabels,
				nil,
			),
			Data: nil,
		},

		// LEADER ELECTION ?? not needed unless more than one instance
		LeaderElectionRB: kubeutil.BindRole(
			AppName+"-leaderelection",
			sa,
			LeaderElectionRole,
			ESLabels,
		),
		LeaderElectionRole: LeaderElectionRole,
	}
}

// Apply applies the kubernetes objects to the cluster
func (a *ExternalSecrets) Apply(ctx context.Context) error {
	return Apply(ctx, a)
}

// Export exports the kubernetes objects to YAML files in the given directory
func (a *ExternalSecrets) Export(dir string) error {
	return kube.Export(a, kube.WithExportOutputDirectory(dir))
}

// Apply applies the kubernetes objects contained in Exporter to the cluster
func Apply(ctx context.Context, km kube.Exporter) error {
	cmd := exec.CommandContext(ctx, "kubectl", "apply", "-f", "-")
	cmd.Env = os.Environ()        // inherit environment in case we need to use kubectl from a container
	stdin, err := cmd.StdinPipe() // pipe to pass data to kubectl
	if err != nil {
		return err
	}

	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	go func() {
		defer func() {
			err = errors.Join(err, stdin.Close())
		}()
		if errEW := kube.Export(
			km,
			kube.WithExportWriter(stdin),
			kube.WithExportAsSingleFile("stdin"),
		); errEW != nil {
			err = errors.Join(err, errEW)
		}
	}()

	if errS := cmd.Start(); errS != nil {
		return errors.Join(err, errS)
	}

	// waits for the command to exit and waits for any copying
	// to stdin or copying from stdout or stderr to complete
	return errors.Join(err, cmd.Wait())
}

func P[T any](t T) *T {
	return &t
}
