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

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

package vector

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

	"github.com/golingon/lingon/pkg/kube"
	ku "github.com/golingon/lingon/pkg/kubeutil"
	"github.com/golingon/lingoneks/meta"
	appsv1 "k8s.io/api/apps/v1"
	autoscalingv2 "k8s.io/api/autoscaling/v2"
	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"
)

var V = Core()

func Core() Meta {
	name := "vector"
	ver := "0.31.0-distroless-libc"
	m := meta.Metadata{
		Name:      name,
		Namespace: "monitoring",
		Instance:  name,
		Component: "aggregator",
		PartOf:    name,
		Version:   ver,
		ManagedBy: "lingon",
		Img: meta.ContainerImg{
			Registry: "docker.io",
			Image:    "timberio/vector",
			Tag:      ver,
		},
	}

	p := func(name string, port int32) meta.NetPort {
		return meta.NetPort{
			Container: corev1.ContainerPort{
				Name: name, ContainerPort: port, Protocol: corev1.ProtocolTCP,
			},
			Service: corev1.ServicePort{
				Name: name, Port: port, Protocol: corev1.ProtocolTCP,
			},
		}
	}

	V := Meta{
		Metadata: m,
		Storage: ku.VolumeAndMount{
			VolumeMount: corev1.VolumeMount{
				MountPath: "/vector-data-dir",
				Name:      "data",
			},
			VolumeSource: corev1.VolumeSource{},
		},
		SA:      ku.ServiceAccount(m.Name, m.Namespace, m.Labels(), nil),
		Fluent:  p("fluent", 24224),
		Vector:  p("vector", 6000),
		PromExp: p("prom-exporter", 9090),
	}

	V.Config = ku.ConfigAndMount{
		ObjectMeta: V.ObjectMeta(),
		VolumeMount: corev1.VolumeMount{
			Name:      "config",
			ReadOnly:  true,
			MountPath: "/etc/vector/",
		},
		Data: map[string]string{
			"aggregator.yaml": `
data_dir: /vector-data-dir
api:
  enabled: true
  address: 127.0.0.1:8686
  playground: false
sources:
  fluent:
    address: 0.0.0.0:24224
    type: fluent
  internal_metrics:
    type: internal_metrics
  vector:
    address: 0.0.0.0:6000
    type: vector
    version: "2"
sinks:
  prom_exporter:
    type: prometheus_exporter
    inputs: [internal_metrics]
    address: 0.0.0.0:9090
  stdout:
    type: console
    inputs: [fluent, vector]
    encoding:
      codec: json

`,
		},
	}

	return V
}

type Meta struct {
	meta.Metadata
	Config  ku.ConfigAndMount
	Storage ku.VolumeAndMount
	SA      *corev1.ServiceAccount
	Fluent  meta.NetPort
	Vector  meta.NetPort
	PromExp meta.NetPort
}

func (m Meta) LabelsVectorExclude() map[string]string {
	l := ku.MergeLabels(
		m.Labels(),
		map[string]string{"vector.dev/exclude": "true"},
	)
	return l
}

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

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

	CM          *corev1.ConfigMap
	HPA         *autoscalingv2.HorizontalPodAutoscaler
	HeadlessSVC *corev1.Service
	PDB         *policyv1.PodDisruptionBudget
	SA          *corev1.ServiceAccount
	STS         *appsv1.StatefulSet
	SVC         *corev1.Service
}

// New creates a new Vector
func New() *Vector {
	return &Vector{
		CM: V.Config.ConfigMap(),

		SA:          V.SA,
		STS:         STS,
		SVC:         SVC,
		HeadlessSVC: HeadlessSVC,

		PDB: &policyv1.PodDisruptionBudget{
			TypeMeta:   ku.TypePodDisruptionBudgetV1,
			ObjectMeta: V.ObjectMeta(),
			Spec: policyv1.PodDisruptionBudgetSpec{
				MinAvailable: &intstr.IntOrString{IntVal: int32(1)},
				Selector:     &metav1.LabelSelector{MatchLabels: V.MatchLabels()},
			},
		},
		HPA: &autoscalingv2.HorizontalPodAutoscaler{
			TypeMeta:   ku.TypeHorizontalPodAutoscalerV2,
			ObjectMeta: V.ObjectMeta(),
			Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
				MaxReplicas: int32(10),
				Metrics: []autoscalingv2.MetricSpec{
					{
						Type: autoscalingv2.ResourceMetricSourceType,
						Resource: &autoscalingv2.ResourceMetricSource{
							Name: corev1.ResourceCPU,
							Target: autoscalingv2.MetricTarget{
								AverageUtilization: P(int32(80)),
								Type:               autoscalingv2.UtilizationMetricType,
							},
						},
					},
				},
				MinReplicas: P(int32(1)),
				ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
					APIVersion: STS.APIVersion,
					Kind:       STS.Kind,
					Name:       STS.Name,
				},
			},
		},
	}
}

var HeadlessSVC = &corev1.Service{
	TypeMeta:   ku.TypeServiceV1,
	ObjectMeta: V.ObjectMetaNameSuffix("headless"),
	Spec: corev1.ServiceSpec{
		ClusterIP: corev1.ClusterIPNone,
		Selector:  V.MatchLabels(),
		Type:      corev1.ServiceTypeClusterIP,
		Ports: []corev1.ServicePort{
			V.Fluent.Service,
			V.Vector.Service,
			V.PromExp.Service,
		},
	},
}

var SVC = &corev1.Service{
	TypeMeta:   ku.TypeServiceV1,
	ObjectMeta: V.ObjectMeta(),
	Spec: corev1.ServiceSpec{
		Selector: V.MatchLabels(),
		Type:     corev1.ServiceTypeClusterIP,
		Ports: []corev1.ServicePort{
			V.Fluent.Service,
			V.Vector.Service,
			V.PromExp.Service,
		},
	},
}

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

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