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

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

package nats

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

	"github.com/golingon/lingon/pkg/kube"
	ku "github.com/golingon/lingon/pkg/kubeutil"
	"github.com/golingon/lingoneks/monitoring"
	promoperatorv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	policyv1 "k8s.io/api/policy/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
)

// validate the struct implements the interface
var _ kube.Exporter = (*Nats)(nil)

var (
	NS = N.NS()
	SA = N.ServiceAccount()
)

// Nats contains kubernetes manifests
type Nats struct {
	kube.App

	NS *corev1.Namespace

	BoxDeploy          *appsv1.Deployment
	ConfigCM           *corev1.ConfigMap
	PDB                *policyv1.PodDisruptionBudget
	SA                 *corev1.ServiceAccount
	STS                *appsv1.StatefulSet
	SVC                *corev1.Service
	ServiceMonitor     *promoperatorv1.ServiceMonitor
	TestRequestReplyPO *corev1.Pod
}

// New creates a new Nats
func New() *Nats {
	return &Nats{
		NS: NS,
		SA: SA,

		STS:      STS,
		ConfigCM: N.Config.ConfigMap(),
		SVC:      SVC,

		BoxDeploy: BoxDeploy,
		ServiceMonitor: &promoperatorv1.ServiceMonitor{
			ObjectMeta: metav1.ObjectMeta{
				Name:      N.Name,
				Namespace: monitoring.Namespace,
				Labels:    N.Labels(),
			},
			Spec: promoperatorv1.ServiceMonitorSpec{
				Endpoints: []promoperatorv1.Endpoint{
					{Path: ku.PathMetrics, Port: N.Metrics.Service.Name},
				},
				NamespaceSelector: promoperatorv1.NamespaceSelector{Any: true},
				Selector:          metav1.LabelSelector{MatchLabels: N.MatchLabels()},
			},
			TypeMeta: metav1.TypeMeta{
				APIVersion: "monitoring.coreos.com/v1",
				Kind:       "ServiceMonitor",
			},
		},

		PDB: &policyv1.PodDisruptionBudget{
			ObjectMeta: N.ObjectMeta(),
			Spec: policyv1.PodDisruptionBudgetSpec{
				MaxUnavailable: P(intstr.FromInt(1)),
				Selector:       &metav1.LabelSelector{MatchLabels: N.MatchLabels()},
			},
			TypeMeta: ku.TypePodDisruptionBudgetV1,
		},
		TestRequestReplyPO: TestRequestReplyPO,
	}
}

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

// Export exports the kubernetes objects to YAML files in the given directory
func (a *Nats) 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())
}

// P converts T to *T, useful for basic types
func P[T any](t T) *T {
	return &t
}
