/*
Copyright 2026.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package common

import (
	"context"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"strings"

	yamlv3 "gopkg.in/yaml.v3"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	networkingv1 "k8s.io/api/networking/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/client-go/util/retry"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"

	infrav1alpha1 "github.com/sandbox0-ai/sandbox0/infra-operator/api/v1alpha1"
	"github.com/sandbox0-ai/sandbox0/pkg/internalauth"
)

type ResourceManager struct {
	Client client.Client
	Scheme *runtime.Scheme
	// ImagePullPolicy overrides container pull policy when set.
	ImagePullPolicy *corev1.PullPolicy
	// LocalDev config for running operator outside the cluster.
	LocalDev LocalDevConfig
}

type LocalDevConfig struct {
	LocalDevMode   bool
	KubeconfigPath string
}

const PodTemplateConfigHashAnnotation = "infra.sandbox0.ai/config-hash"

func NewResourceManager(client client.Client, scheme *runtime.Scheme, imagePullPolicy *corev1.PullPolicy, localDev LocalDevConfig) *ResourceManager {
	return &ResourceManager{
		Client:          client,
		Scheme:          scheme,
		ImagePullPolicy: imagePullPolicy,
		LocalDev:        localDev,
	}
}

// ServiceDefinition defines deployment/daemonset configuration for a service.
type ServiceDefinition struct {
	Name               string
	Port               int32
	TargetPort         int32
	Ports              []corev1.ContainerPort
	Image              string
	ImagePullPolicy    *corev1.PullPolicy
	Command            []string
	Args               []string
	EnvVars            []corev1.EnvVar
	VolumeMounts       []corev1.VolumeMount
	Volumes            []corev1.Volume
	PodAnnotations     map[string]string
	LivenessProbe      *corev1.Probe
	ReadinessProbe     *corev1.Probe
	ServiceAccountName string
	Resources          *corev1.ResourceRequirements
}

// ReconcileDeployment creates or updates a deployment.
func (r *ResourceManager) ReconcileDeployment(ctx context.Context, infra *infrav1alpha1.Sandbox0Infra, name string, labels map[string]string, replicas int32, def ServiceDefinition) error {
	return r.ReconcileDeploymentWithScope(ctx, NewObjectScope(infra), name, labels, replicas, def)
}

func (r *ResourceManager) ReconcileDeploymentWithScope(ctx context.Context, scope ObjectScope, name string, labels map[string]string, replicas int32, def ServiceDefinition) error {
	deploy := &appsv1.Deployment{}
	err := r.Client.Get(ctx, types.NamespacedName{Name: name, Namespace: scope.Namespace}, deploy)
	if err != nil && !errors.IsNotFound(err) {
		return err
	}

	defaultResources := corev1.ResourceRequirements{
		Requests: corev1.ResourceList{
			corev1.ResourceCPU:    resource.MustParse("100m"),
			corev1.ResourceMemory: resource.MustParse("256Mi"),
		},
		Limits: corev1.ResourceList{
			corev1.ResourceCPU:    resource.MustParse("500m"),
			corev1.ResourceMemory: resource.MustParse("512Mi"),
		},
	}
	if def.Resources != nil {
		defaultResources = *def.Resources
	}

	desiredLabels := EnsureManagedLabels(labels, name)
	desiredDeploy := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: scope.Namespace,
			Labels:    desiredLabels,
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: &replicas,
			Selector: &metav1.LabelSelector{
				MatchLabels: labels,
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels:      desiredLabels,
					Annotations: EnsurePodTemplateAnnotations(def.PodAnnotations),
				},
				Spec: corev1.PodSpec{
					ServiceAccountName: def.ServiceAccountName,
					Containers: []corev1.Container{
						{
							Name:            def.Name,
							Image:           def.Image,
							ImagePullPolicy: ResolveImagePullPolicy(def, r.ImagePullPolicy),
							Command:         def.Command,
							Args:            def.Args,
							Env:             def.EnvVars,
							VolumeMounts:    def.VolumeMounts,
							Ports:           ResolveContainerPorts(def),
							Resources:       defaultResources,
							LivenessProbe:   def.LivenessProbe,
							ReadinessProbe:  def.ReadinessProbe,
						},
					},
					Volumes: def.Volumes,
				},
			},
		},
	}

	if err := scope.SetControllerReference(desiredDeploy, r.Scheme); err != nil {
		return err
	}

	if errors.IsNotFound(err) {
		return r.Client.Create(ctx, desiredDeploy)
	}

	deploy.Spec = desiredDeploy.Spec
	deploy.Labels = desiredLabels
	return r.Client.Update(ctx, deploy)
}

// EnsureDeploymentReady validates deployment readiness before reporting success.
func (r *ResourceManager) EnsureDeploymentReady(ctx context.Context, infra *infrav1alpha1.Sandbox0Infra, name string, replicas int32) error {
	return r.EnsureDeploymentReadyWithScope(ctx, NewObjectScope(infra), name, replicas)
}

func (r *ResourceManager) EnsureDeploymentReadyWithScope(ctx context.Context, scope ObjectScope, name string, replicas int32) error {
	if replicas == 0 {
		return nil
	}

	deploy := &appsv1.Deployment{}
	if err := r.Client.Get(ctx, types.NamespacedName{Name: name, Namespace: scope.Namespace}, deploy); err != nil {
		return err
	}

	desired := replicas
	if deploy.Spec.Replicas != nil {
		desired = *deploy.Spec.Replicas
	}
	if desired == 0 {
		return nil
	}
	if deploy.Status.ReadyReplicas < desired {
		return fmt.Errorf("deployment %q not ready: %d/%d ready", name, deploy.Status.ReadyReplicas, desired)
	}
	return nil
}

// ReconcileDaemonSet creates or updates a daemonset.
func (r *ResourceManager) ReconcileDaemonSet(ctx context.Context, infra *infrav1alpha1.Sandbox0Infra, name string, labels map[string]string, def ServiceDefinition) error {
	return r.ReconcileDaemonSetWithScope(ctx, NewObjectScope(infra), name, labels, def)
}

func (r *ResourceManager) ReconcileDaemonSetWithScope(ctx context.Context, scope ObjectScope, name string, labels map[string]string, def ServiceDefinition) error {
	defaultResources := corev1.ResourceRequirements{
		Requests: corev1.ResourceList{
			corev1.ResourceCPU:    resource.MustParse("100m"),
			corev1.ResourceMemory: resource.MustParse("128Mi"),
		},
		Limits: corev1.ResourceList{
			corev1.ResourceCPU:    resource.MustParse("500m"),
			corev1.ResourceMemory: resource.MustParse("256Mi"),
		},
	}
	if def.Resources != nil {
		defaultResources = *def.Resources
	}

	desiredLabels := EnsureManagedLabels(labels, name)
	desiredDs := &appsv1.DaemonSet{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: scope.Namespace,
			Labels:    desiredLabels,
		},
		Spec: appsv1.DaemonSetSpec{
			Selector: &metav1.LabelSelector{
				MatchLabels: labels,
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels:      desiredLabels,
					Annotations: EnsurePodTemplateAnnotations(def.PodAnnotations),
				},
				Spec: corev1.PodSpec{
					ServiceAccountName: def.ServiceAccountName,
					HostNetwork:        true,
					HostPID:            true,
					DNSPolicy:          corev1.DNSClusterFirstWithHostNet,
					Containers: []corev1.Container{
						{
							Name:            def.Name,
							Image:           def.Image,
							ImagePullPolicy: ResolveImagePullPolicy(def, r.ImagePullPolicy),
							Env:             def.EnvVars,
							VolumeMounts:    def.VolumeMounts,
							Ports:           ResolveContainerPorts(def),
							SecurityContext: &corev1.SecurityContext{
								Privileged: BoolPtr(true),
								Capabilities: &corev1.Capabilities{
									Add: []corev1.Capability{"NET_ADMIN", "SYS_ADMIN"},
								},
							},
							Resources:      defaultResources,
							LivenessProbe:  def.LivenessProbe,
							ReadinessProbe: def.ReadinessProbe,
						},
					},
					Volumes: def.Volumes,
				},
			},
		},
	}

	if err := scope.SetControllerReference(desiredDs, r.Scheme); err != nil {
		return err
	}

	return r.ApplyDaemonSetWithScope(ctx, scope, desiredDs)
}

// ApplyDaemonSet creates or updates a daemonset using fresh reads on each retry
// so controller-driven status/resourceVersion updates do not cause reconcile
// loops to fail on optimistic concurrency conflicts.
func (r *ResourceManager) ApplyDaemonSet(ctx context.Context, infra *infrav1alpha1.Sandbox0Infra, desired *appsv1.DaemonSet) error {
	return r.ApplyDaemonSetWithScope(ctx, NewObjectScope(infra), desired)
}

func (r *ResourceManager) ApplyDaemonSetWithScope(ctx context.Context, scope ObjectScope, desired *appsv1.DaemonSet) error {
	if err := scope.SetControllerReference(desired, r.Scheme); err != nil {
		return err
	}

	key := types.NamespacedName{Name: desired.Name, Namespace: desired.Namespace}
	return retry.RetryOnConflict(retry.DefaultRetry, func() error {
		current := &appsv1.DaemonSet{}
		err := r.Client.Get(ctx, key, current)
		if errors.IsNotFound(err) {
			return r.Client.Create(ctx, desired.DeepCopy())
		}
		if err != nil {
			return err
		}

		current.Labels = desired.Labels
		current.Annotations = desired.Annotations
		current.Spec = desired.Spec
		current.OwnerReferences = desired.OwnerReferences
		return r.Client.Update(ctx, current)
	})
}

func ResolveContainerPorts(def ServiceDefinition) []corev1.ContainerPort {
	if len(def.Ports) > 0 {
		return def.Ports
	}
	if def.TargetPort == 0 {
		return nil
	}
	return []corev1.ContainerPort{
		{
			Name:          "http",
			ContainerPort: def.TargetPort,
		},
	}
}

func ResolveImagePullPolicy(def ServiceDefinition, fallback *corev1.PullPolicy) corev1.PullPolicy {
	if def.ImagePullPolicy != nil {
		return *def.ImagePullPolicy
	}
	if fallback != nil {
		return *fallback
	}
	return ""
}

func ResolveServiceType(config *infrav1alpha1.ServiceNetworkConfig) corev1.ServiceType {
	if config != nil && config.Type != "" {
		return config.Type
	}
	return corev1.ServiceTypeClusterIP
}

func ResolveServicePort(config *infrav1alpha1.ServiceNetworkConfig, fallback int32) int32 {
	if config != nil && config.Port != 0 {
		return config.Port
	}
	return fallback
}

// ResolveSSHEndpoint returns the advertised SSH endpoint for a region when the
// SSH gateway is enabled and public routing is configured.
func ResolveSSHEndpoint(infra *infrav1alpha1.Sandbox0Infra, fallbackPort int32) (string, int32, bool) {
	if infra == nil || infra.Spec.Services == nil || infra.Spec.Services.SSHGateway == nil || !infra.Spec.Services.SSHGateway.Enabled {
		return "", 0, false
	}
	if infra.Spec.PublicExposure == nil {
		return "", 0, false
	}

	rootDomain := strings.TrimSpace(infra.Spec.PublicExposure.RootDomain)
	regionLabel := strings.TrimSpace(infra.Spec.PublicExposure.RegionID)
	if rootDomain == "" || regionLabel == "" {
		return "", 0, false
	}

	port := ResolveServicePort(infra.Spec.Services.SSHGateway.Service, fallbackPort)
	if port <= 0 {
		return "", 0, false
	}

	return fmt.Sprintf("%s.ssh.%s", regionLabel, rootDomain), port, true
}

func ResolveServiceAnnotations(config *infrav1alpha1.ServiceNetworkConfig) map[string]string {
	if config == nil || len(config.Annotations) == 0 {
		return nil
	}
	return CloneStringMap(config.Annotations)
}

// ReconcileService creates or updates a service.
// For NodePort type, the port is also used as NodePort.
func (r *ResourceManager) ReconcileService(ctx context.Context, infra *infrav1alpha1.Sandbox0Infra, name string, labels map[string]string, serviceType corev1.ServiceType, annotations map[string]string, port, targetPort int32) error {
	return r.ReconcileServiceWithScope(ctx, NewObjectScope(infra), name, labels, serviceType, annotations, port, targetPort)
}

func (r *ResourceManager) ReconcileServiceWithScope(ctx context.Context, scope ObjectScope, name string, labels map[string]string, serviceType corev1.ServiceType, annotations map[string]string, port, targetPort int32) error {
	return r.ReconcileServicePortsWithScope(ctx, scope, name, labels, serviceType, annotations, []corev1.ServicePort{
		BuildServicePort("http", port, targetPort, serviceType),
	})
}

// ReconcileServicePorts creates or updates a service with multiple ports.
func (r *ResourceManager) ReconcileServicePorts(ctx context.Context, infra *infrav1alpha1.Sandbox0Infra, name string, labels map[string]string, serviceType corev1.ServiceType, annotations map[string]string, ports []corev1.ServicePort) error {
	return r.ReconcileServicePortsWithScope(ctx, NewObjectScope(infra), name, labels, serviceType, annotations, ports)
}

func (r *ResourceManager) ReconcileServicePortsWithScope(ctx context.Context, scope ObjectScope, name string, labels map[string]string, serviceType corev1.ServiceType, annotations map[string]string, ports []corev1.ServicePort) error {
	return r.reconcileServicePorts(ctx, scope, name, labels, serviceType, annotations, ports, nil)
}

func (r *ResourceManager) ReconcileServicePortsWithSpecMutator(ctx context.Context, infra *infrav1alpha1.Sandbox0Infra, name string, labels map[string]string, serviceType corev1.ServiceType, annotations map[string]string, ports []corev1.ServicePort, mutate func(*corev1.ServiceSpec)) error {
	return r.ReconcileServicePortsWithScopeAndSpecMutator(ctx, NewObjectScope(infra), name, labels, serviceType, annotations, ports, mutate)
}

func (r *ResourceManager) ReconcileServicePortsWithScopeAndSpecMutator(ctx context.Context, scope ObjectScope, name string, labels map[string]string, serviceType corev1.ServiceType, annotations map[string]string, ports []corev1.ServicePort, mutate func(*corev1.ServiceSpec)) error {
	return r.reconcileServicePorts(ctx, scope, name, labels, serviceType, annotations, ports, mutate)
}

func (r *ResourceManager) reconcileServicePorts(ctx context.Context, scope ObjectScope, name string, labels map[string]string, serviceType corev1.ServiceType, annotations map[string]string, ports []corev1.ServicePort, mutate func(*corev1.ServiceSpec)) error {
	if len(ports) == 0 {
		return fmt.Errorf("service %q requires at least one port", name)
	}

	desiredLabels := EnsureManagedLabels(labels, name)
	desiredSpec := corev1.ServiceSpec{
		Type:     serviceType,
		Selector: labels,
		Ports:    ports,
	}
	if mutate != nil {
		mutate(&desiredSpec)
	}
	desiredSvc := corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:        name,
			Namespace:   scope.Namespace,
			Labels:      desiredLabels,
			Annotations: CloneStringMap(annotations),
		},
		Spec: desiredSpec,
	}

	if err := scope.SetControllerReference(&desiredSvc, r.Scheme); err != nil {
		return err
	}

	return retry.RetryOnConflict(retry.DefaultRetry, func() error {
		svc := &corev1.Service{}
		err := r.Client.Get(ctx, types.NamespacedName{Name: name, Namespace: scope.Namespace}, svc)
		if errors.IsNotFound(err) {
			return r.Client.Create(ctx, desiredSvc.DeepCopy())
		}
		if err != nil {
			return err
		}

		svc.Spec = desiredSvc.Spec
		svc.Labels = desiredLabels
		svc.Annotations = CloneStringMap(annotations)
		return r.Client.Update(ctx, svc)
	})
}

// BuildServicePort returns a ServicePort with a target port.
// For NodePort type, the port is also used as NodePort.
func BuildServicePort(name string, port, targetPort int32, serviceType corev1.ServiceType) corev1.ServicePort {
	sp := corev1.ServicePort{
		Name:       name,
		Port:       port,
		TargetPort: intstr.FromInt(int(targetPort)),
	}
	if serviceType == corev1.ServiceTypeNodePort {
		sp.NodePort = port
	}
	return sp
}

// EnsureManagedLabels merges standard managed labels without overriding existing values.
func EnsureManagedLabels(labels map[string]string, fallbackName string) map[string]string {
	out := MergeLabels(labels, map[string]string{
		"app.kubernetes.io/managed-by": "sandbox0infra-operator",
	})
	if out["app.kubernetes.io/name"] == "" {
		out["app.kubernetes.io/name"] = fallbackName
	}
	return out
}

// MergeLabels returns a copy of base with overrides applied.
func MergeLabels(base map[string]string, overrides map[string]string) map[string]string {
	out := make(map[string]string, len(base)+len(overrides))
	for key, value := range base {
		out[key] = value
	}
	for key, value := range overrides {
		out[key] = value
	}
	return out
}

func CloneStringMap(src map[string]string) map[string]string {
	if len(src) == 0 {
		return nil
	}
	out := make(map[string]string, len(src))
	for key, value := range src {
		out[key] = value
	}
	return out
}

// ReconcileIngress creates or updates an ingress.
func (r *ResourceManager) ReconcileIngress(ctx context.Context, infra *infrav1alpha1.Sandbox0Infra, serviceName string, servicePort int32, config *infrav1alpha1.IngressConfig) error {
	return r.ReconcileIngressWithScope(ctx, NewObjectScope(infra), serviceName, servicePort, config)
}

func (r *ResourceManager) ReconcileIngressWithScope(ctx context.Context, scope ObjectScope, serviceName string, servicePort int32, config *infrav1alpha1.IngressConfig) error {
	ingressName := serviceName

	ingress := &networkingv1.Ingress{}
	err := r.Client.Get(ctx, types.NamespacedName{Name: ingressName, Namespace: scope.Namespace}, ingress)
	if err != nil && !errors.IsNotFound(err) {
		return err
	}

	pathType := networkingv1.PathTypePrefix
	desiredLabels := EnsureManagedLabels(ingress.Labels, ingressName)
	desiredAnnotations := CloneStringMap(config.Annotations)
	hosts := ingressHosts(config)
	rules := make([]networkingv1.IngressRule, 0, len(hosts))
	for _, host := range hosts {
		rules = append(rules, networkingv1.IngressRule{
			Host: host,
			IngressRuleValue: networkingv1.IngressRuleValue{
				HTTP: &networkingv1.HTTPIngressRuleValue{
					Paths: []networkingv1.HTTPIngressPath{
						{
							Path:     "/",
							PathType: &pathType,
							Backend: networkingv1.IngressBackend{
								Service: &networkingv1.IngressServiceBackend{
									Name: serviceName,
									Port: networkingv1.ServiceBackendPort{
										Number: servicePort,
									},
								},
							},
						},
					},
				},
			},
		})
	}
	desiredIngress := &networkingv1.Ingress{
		ObjectMeta: metav1.ObjectMeta{
			Name:        ingressName,
			Namespace:   scope.Namespace,
			Labels:      desiredLabels,
			Annotations: desiredAnnotations,
		},
		Spec: networkingv1.IngressSpec{
			Rules: rules,
		},
	}
	if config.ClassName != "" {
		desiredIngress.Spec.IngressClassName = &config.ClassName
	}
	desiredIngress.Spec.TLS = ingressTLS(config, hosts)

	if err := scope.SetControllerReference(desiredIngress, r.Scheme); err != nil {
		return err
	}

	if errors.IsNotFound(err) {
		return r.Client.Create(ctx, desiredIngress)
	}

	ingress.Spec = desiredIngress.Spec
	ingress.Labels = desiredLabels
	ingress.Annotations = desiredAnnotations
	return r.Client.Update(ctx, ingress)
}

func ingressHosts(config *infrav1alpha1.IngressConfig) []string {
	seen := map[string]struct{}{}
	hosts := []string{}
	add := func(host string) {
		host = strings.ToLower(strings.TrimSpace(host))
		if host == "" {
			return
		}
		if _, ok := seen[host]; ok {
			return
		}
		seen[host] = struct{}{}
		hosts = append(hosts, host)
	}
	add(config.Host)
	for _, host := range config.ExtraHosts {
		add(host)
	}
	if len(hosts) == 0 {
		return []string{""}
	}
	return hosts
}

func ingressTLS(config *infrav1alpha1.IngressConfig, defaultHosts []string) []networkingv1.IngressTLS {
	if len(config.TLS) == 0 {
		if config.TLSSecret == "" {
			return nil
		}
		return []networkingv1.IngressTLS{
			{
				Hosts:      defaultHosts,
				SecretName: config.TLSSecret,
			},
		}
	}

	tls := make([]networkingv1.IngressTLS, 0, len(config.TLS))
	for _, entry := range config.TLS {
		secretName := strings.TrimSpace(entry.SecretName)
		if secretName == "" {
			continue
		}
		hosts := normalizeHostList(entry.Hosts)
		if len(hosts) == 0 {
			hosts = defaultHosts
		}
		tls = append(tls, networkingv1.IngressTLS{
			Hosts:      hosts,
			SecretName: secretName,
		})
	}
	return tls
}

func normalizeHostList(values []string) []string {
	seen := map[string]struct{}{}
	hosts := []string{}
	for _, value := range values {
		host := strings.ToLower(strings.TrimSpace(value))
		if host == "" {
			continue
		}
		if _, ok := seen[host]; ok {
			continue
		}
		seen[host] = struct{}{}
		hosts = append(hosts, host)
	}
	return hosts
}

// ReconcileServiceConfigMap creates or updates a configmap for a service.
func (r *ResourceManager) ReconcileServiceConfigMap(ctx context.Context, infra *infrav1alpha1.Sandbox0Infra, name string, labels map[string]string, config any) error {
	return r.ReconcileServiceConfigMapWithScope(ctx, NewObjectScope(infra), name, labels, config)
}

func (r *ResourceManager) ReconcileServiceConfigMapWithScope(ctx context.Context, scope ObjectScope, name string, labels map[string]string, config any) error {
	if config == nil {
		config = map[string]any{}
	}

	payload, err := yamlv3.Marshal(config)
	if err != nil {
		return fmt.Errorf("marshal config for %s: %w", name, err)
	}

	desiredLabels := EnsureManagedLabels(labels, name)
	desired := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: scope.Namespace,
			Labels:    desiredLabels,
		},
		Data: map[string]string{
			"config.yaml": string(payload),
		},
	}

	if err := scope.SetControllerReference(desired, r.Scheme); err != nil {
		return err
	}

	existing := &corev1.ConfigMap{}
	err = r.Client.Get(ctx, types.NamespacedName{Name: name, Namespace: scope.Namespace}, existing)
	if err != nil && !errors.IsNotFound(err) {
		return err
	}

	if errors.IsNotFound(err) {
		return r.Client.Create(ctx, desired)
	}

	existing.Data = desired.Data
	existing.Labels = desired.Labels
	return r.Client.Update(ctx, existing)
}

// ReconcileNamespace creates a namespace if it does not exist.
// It ignores errors if the operator does not have permission to create the namespace.
func (r *ResourceManager) ReconcileNamespace(ctx context.Context, name string) error {
	ns := &corev1.Namespace{}
	err := r.Client.Get(ctx, types.NamespacedName{Name: name}, ns)
	if err == nil {
		return nil
	}

	if !errors.IsNotFound(err) {
		return err
	}

	desired := &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}

	err = r.Client.Create(ctx, desired)
	if err != nil {
		if errors.IsForbidden(err) {
			log.FromContext(ctx).Info("Forbidden to create namespace, skipping", "namespace", name)
			return nil
		}
		return err
	}

	return nil
}

func GetOrInitMap(config map[string]any, key string) map[string]any {
	if val, ok := config[key]; ok {
		if typed, ok := val.(map[string]any); ok {
			return typed
		}
	}

	child := map[string]any{}
	config[key] = child
	return child
}

// GenerateRandomString generates a random string of specified length.
func GenerateRandomString(length int) string {
	bytes := make([]byte, length)
	if _, err := rand.Read(bytes); err != nil {
		return "defaultsecret123456789012"
	}

	encoded := base64.URLEncoding.EncodeToString(bytes)
	if len(encoded) > length {
		return encoded[:length]
	}
	return encoded
}

// EnsureSecretValue ensures a named secret contains a key, generating if needed.
func EnsureSecretValue(ctx context.Context, client client.Client, scheme *runtime.Scheme, infra *infrav1alpha1.Sandbox0Infra, name, key string, length int) (string, error) {
	return EnsureSecretValueWithScope(ctx, client, scheme, NewObjectScope(infra), name, key, length)
}

func EnsureSecretValueWithScope(ctx context.Context, client client.Client, scheme *runtime.Scheme, scope ObjectScope, name, key string, length int) (string, error) {
	secret := &corev1.Secret{}
	err := client.Get(ctx, types.NamespacedName{Name: name, Namespace: scope.Namespace}, secret)
	if err != nil && !errors.IsNotFound(err) {
		return "", err
	}

	if errors.IsNotFound(err) {
		value := GenerateRandomString(length)
		secret = &corev1.Secret{
			ObjectMeta: metav1.ObjectMeta{
				Name:      name,
				Namespace: scope.Namespace,
			},
			Type: corev1.SecretTypeOpaque,
			StringData: map[string]string{
				key: value,
			},
		}
		if err := scope.SetControllerReference(secret, scheme); err != nil {
			return "", err
		}
		if err := client.Create(ctx, secret); err != nil {
			return "", err
		}
		return value, nil
	}

	if value, ok := secret.Data[key]; ok && len(value) > 0 {
		return string(value), nil
	}

	value := GenerateRandomString(length)
	if secret.Data == nil {
		secret.Data = map[string][]byte{}
	}
	secret.Data[key] = []byte(value)
	if err := client.Update(ctx, secret); err != nil {
		return "", err
	}
	return value, nil
}

// EnsureEd25519KeyPair ensures a named secret contains an Ed25519 keypair.
func EnsureEd25519KeyPair(ctx context.Context, client client.Client, scheme *runtime.Scheme, infra *infrav1alpha1.Sandbox0Infra, name, privateKeyKey, publicKeyKey string) (string, string, error) {
	return EnsureEd25519KeyPairWithScope(ctx, client, scheme, NewObjectScope(infra), name, privateKeyKey, publicKeyKey)
}

func EnsureEd25519KeyPairWithScope(ctx context.Context, client client.Client, scheme *runtime.Scheme, scope ObjectScope, name, privateKeyKey, publicKeyKey string) (string, string, error) {
	secret := &corev1.Secret{}
	err := client.Get(ctx, types.NamespacedName{Name: name, Namespace: scope.Namespace}, secret)
	if err != nil && !errors.IsNotFound(err) {
		return "", "", err
	}

	generatePair := func() (string, string, error) {
		privateKeyPEM, publicKeyPEM, err := internalauth.GenerateEd25519KeyPair()
		if err != nil {
			return "", "", err
		}
		return string(privateKeyPEM), string(publicKeyPEM), nil
	}

	if errors.IsNotFound(err) {
		privateKeyPEM, publicKeyPEM, err := generatePair()
		if err != nil {
			return "", "", err
		}
		secret = &corev1.Secret{
			ObjectMeta: metav1.ObjectMeta{
				Name:      name,
				Namespace: scope.Namespace,
			},
			Type: corev1.SecretTypeOpaque,
			StringData: map[string]string{
				privateKeyKey: privateKeyPEM,
				publicKeyKey:  publicKeyPEM,
			},
		}
		if err := scope.SetControllerReference(secret, scheme); err != nil {
			return "", "", err
		}
		if err := client.Create(ctx, secret); err != nil {
			return "", "", err
		}
		return privateKeyPEM, publicKeyPEM, nil
	}

	privateKeyPEM, privateKeyOK := secret.Data[privateKeyKey]
	publicKeyPEM, publicKeyOK := secret.Data[publicKeyKey]
	if privateKeyOK && len(privateKeyPEM) > 0 && publicKeyOK && len(publicKeyPEM) > 0 {
		return string(privateKeyPEM), string(publicKeyPEM), nil
	}

	regeneratedPrivate, regeneratedPublic, err := generatePair()
	if err != nil {
		return "", "", err
	}
	if secret.Data == nil {
		secret.Data = map[string][]byte{}
	}
	secret.Data[privateKeyKey] = []byte(regeneratedPrivate)
	secret.Data[publicKeyKey] = []byte(regeneratedPublic)
	if err := client.Update(ctx, secret); err != nil {
		return "", "", err
	}
	return regeneratedPrivate, regeneratedPublic, nil
}

// GetSecretValue returns the value from a secret key reference.
func GetSecretValue(ctx context.Context, client client.Client, namespace string, ref infrav1alpha1.SecretKeyRef) (string, error) {
	if ref.Name == "" {
		return "", fmt.Errorf("secret name is required")
	}

	secret := &corev1.Secret{}
	if err := client.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: namespace}, secret); err != nil {
		return "", err
	}

	key := ref.Key
	if key == "" {
		key = "password"
	}

	value, ok := secret.Data[key]
	if !ok {
		return "", fmt.Errorf("key %s not found in secret %s", key, ref.Name)
	}

	return string(value), nil
}

// ResolveSecretKeyRef returns a ref with defaults applied when fields are empty.
func ResolveSecretKeyRef(ref infrav1alpha1.SecretKeyRef, defaultName, defaultKey string) infrav1alpha1.SecretKeyRef {
	if ref.Name == "" {
		ref.Name = defaultName
	}
	if ref.Key == "" {
		ref.Key = defaultKey
	}
	return ref
}

// GetServiceLabels returns standard labels for a service.
func GetServiceLabels(instanceName, componentName string) map[string]string {
	return map[string]string{
		"app.kubernetes.io/name":       componentName,
		"app.kubernetes.io/instance":   instanceName,
		"app.kubernetes.io/component":  componentName,
		"app.kubernetes.io/managed-by": "sandbox0infra-operator",
	}
}

// BoolPtr returns a pointer to a bool.
func BoolPtr(b bool) *bool {
	return &b
}

// ResolveSandboxNodePlacement derives the shared scheduling constraints for
// sandbox workloads and node-local sandbox services. The shared spec-level
// placement takes precedence, with netd placement kept as a compatibility
// fallback for older manifests.
func ResolveSandboxNodePlacement(infra *infrav1alpha1.Sandbox0Infra) (map[string]string, []corev1.Toleration) {
	if infra == nil {
		return nil, nil
	}

	var nodeSelector map[string]string
	if infra.Spec.SandboxNodePlacement != nil && len(infra.Spec.SandboxNodePlacement.NodeSelector) > 0 {
		nodeSelector = cloneNodeSelector(infra.Spec.SandboxNodePlacement.NodeSelector)
	} else if infra.Spec.Services != nil && infra.Spec.Services.Netd != nil {
		nodeSelector = cloneNodeSelector(infra.Spec.Services.Netd.NodeSelector)
	}

	var tolerations []corev1.Toleration
	if infra.Spec.SandboxNodePlacement != nil && len(infra.Spec.SandboxNodePlacement.Tolerations) > 0 {
		tolerations = cloneTolerations(infra.Spec.SandboxNodePlacement.Tolerations)
	} else if infra.Spec.Services != nil && infra.Spec.Services.Netd != nil {
		tolerations = cloneTolerations(infra.Spec.Services.Netd.Tolerations)
	}

	return nodeSelector, tolerations
}

func cloneNodeSelector(src map[string]string) map[string]string {
	if len(src) == 0 {
		return nil
	}

	cloned := make(map[string]string, len(src))
	for key, value := range src {
		cloned[key] = value
	}
	return cloned
}

func cloneTolerations(src []corev1.Toleration) []corev1.Toleration {
	if len(src) == 0 {
		return nil
	}

	cloned := make([]corev1.Toleration, len(src))
	copy(cloned, src)
	return cloned
}

func ConfigHash(config any) (string, error) {
	if config == nil {
		config = map[string]any{}
	}

	payload, err := yamlv3.Marshal(config)
	if err != nil {
		return "", err
	}
	sum := sha256.Sum256(payload)
	return hex.EncodeToString(sum[:]), nil
}

func ConfigHashAnnotation(config any) (map[string]string, error) {
	hash, err := ConfigHash(config)
	if err != nil {
		return nil, err
	}
	return map[string]string{
		PodTemplateConfigHashAnnotation: hash,
	}, nil
}

// EnsurePodTemplateAnnotations returns a cloned annotations map for pod templates.
func EnsurePodTemplateAnnotations(annotations map[string]string) map[string]string {
	out := map[string]string{}
	for key, value := range annotations {
		out[key] = value
	}
	return out
}
