// Package template provides controller implementation for ContainerMomTemplate resources
package template

import (
	"context"
	"fmt"
	"strings"
	"time"

	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/util/intstr"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	"sigs.k8s.io/controller-runtime/pkg/log"

	momv1 "github.com/pfeifferj/container-mom-go/pkg/apis/containermom/v1alpha1"
)

// Controller reconciles Template objects
type Controller struct {
	Client client.Client
	Scheme *runtime.Scheme
}

// Reconcile is part of the main kubernetes reconciliation loop
func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := log.FromContext(ctx)
	logger.Info("Reconciling Template", "template", req.NamespacedName)

	// Fetch the Template instance
	template := &momv1.Template{}
	if err := r.Client.Get(ctx, req.NamespacedName, template); err != nil {
		if errors.IsNotFound(err) {
			return ctrl.Result{}, nil
		}
		return ctrl.Result{}, err
	}

	// Update last updated timestamp and update status
	now := metav1.NewTime(time.Now())
	template.Status.LastUpdated = now

	if err := r.Client.Status().Update(ctx, template); err != nil {
		logger.Error(err, "Failed to update Template status")
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager
func (r *Controller) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&momv1.Template{}).
		Complete(r)
}

// InstantiateTemplate creates a ContainerMomDeployment from a Template
func (r *Controller) InstantiateTemplate(ctx context.Context, templateName string, namespace string, params map[string]string) error {
	logger := log.FromContext(ctx)

	// Fetch the template
	template := &momv1.Template{}
	if err := r.Client.Get(ctx, client.ObjectKey{Name: templateName, Namespace: namespace}, template); err != nil {
		return fmt.Errorf("failed to get template: %w", err)
	}

	// Increment usage count
	template.Status.UsageCount++
	if err := r.Client.Status().Update(ctx, template); err != nil {
		logger.Error(err, "Failed to update template usage count")
		// Non-fatal error, continue with instantiation
	}

	// Validate and process parameters
	processedParams, err := r.processParameters(template, params)
	if err != nil {
		return err
	}

	// Generate a deployment name if not provided
	deploymentName := params["name"]
	if deploymentName == "" {
		deploymentName = fmt.Sprintf("%s-%d", template.Name, time.Now().Unix())
	}

	// Create or update primary ContainerMomDeployment
	if err := r.createPrimaryDeployment(ctx, template, deploymentName, namespace, processedParams); err != nil {
		return fmt.Errorf("failed to create primary deployment: %w", err)
	}

	// Create any additional resources defined in the template
	if err := r.createAdditionalResources(ctx, template, deploymentName, namespace, processedParams); err != nil {
		return fmt.Errorf("failed to create additional resources: %w", err)
	}

	return nil
}

// processParameters validates and processes template parameters
func (r *Controller) processParameters(template *momv1.Template, params map[string]string) (map[string]string, error) {
	result := make(map[string]string)

	// First, copy all provided parameters
	for k, v := range params {
		result[k] = v
	}

	// Check for required parameters and apply defaults
	for _, param := range template.Spec.Parameters {
		if _, exists := result[param.Name]; !exists {
			if param.Required && param.DefaultValue == "" && param.Generate == "" {
				return nil, fmt.Errorf("required parameter '%s' not provided", param.Name)
			}

			if param.DefaultValue != "" {
				result[param.Name] = param.DefaultValue
			} else if param.Generate != "" {
				// Handle parameter generation based on the specified method
				switch param.Generate {
				case "name":
					result[param.Name] = fmt.Sprintf("%s-%d", template.Name, time.Now().Unix())
				case "uuid":
					result[param.Name] = fmt.Sprintf("%d", time.Now().UnixNano())
				}
			}
		}
	}

	return result, nil
}

// createPrimaryDeployment creates the main ContainerMomDeployment from the template
func (r *Controller) createPrimaryDeployment(ctx context.Context, template *momv1.Template, deploymentName, namespace string, params map[string]string) error {
	logger := log.FromContext(ctx)

	// Find a component with type "deployment" or use the first component
	primaryComponent := r.findPrimaryComponent(template)
	if primaryComponent == nil && len(template.Spec.Components) > 0 {
		primaryComponent = &template.Spec.Components[0]
	}

	if primaryComponent == nil {
		return fmt.Errorf("no components found in template")
	}

	// Determine component name for labels - this is important for service targeting
	componentName := "primary"
	if primaryComponent.Name != "" {
		componentName = primaryComponent.Name
	}

	// Create a new ContainerMomDeployment
	deployment := &momv1.ContainerMomDeployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      deploymentName,
			Namespace: namespace,
			Labels: map[string]string{
				"app":                          deploymentName,
				"app.kubernetes.io/managed-by": "container-mom",
				"container.mom/template":       template.Name,
				"container.mom/component":      componentName,
			},
		},
		Spec: momv1.ContainerMomDeploymentSpec{
			Source: momv1.SourceSpec{
				Image:  primaryComponent.Properties.Image,
				GitURL: primaryComponent.Properties.GitURL,
				BuildConfig: &momv1.BuildConfig{
					BuilderImage: primaryComponent.Properties.BuilderImage,
				},
			},
			Domain:    template.Spec.NetworkConfig.Domain,
			Resources: primaryComponent.Properties.Resources,
			Env:       substituteParams(primaryComponent.Properties.Env, params),
		},
	}

	// Set controller reference so template owner will be updated if deployment changes
	if err := controllerutil.SetControllerReference(template, deployment, r.Scheme); err != nil {
		return err
	}

	// Create the deployment
	if err := r.Client.Create(ctx, deployment); err != nil {
		if errors.IsAlreadyExists(err) {
			logger.Info("Deployment already exists, updating it", "deployment", deploymentName)

			// Get existing deployment
			existing := &momv1.ContainerMomDeployment{}
			if err := r.Client.Get(ctx, client.ObjectKey{Name: deploymentName, Namespace: namespace}, existing); err != nil {
				return err
			}

			// Update fields
			existing.Spec = deployment.Spec

			// Update labels to ensure component name is set
			if existing.Labels == nil {
				existing.Labels = make(map[string]string)
			}
			existing.Labels["app"] = deploymentName
			existing.Labels["container.mom/component"] = componentName

			if err := r.Client.Update(ctx, existing); err != nil {
				return err
			}
		} else {
			return err
		}
	}

	return nil
}

// findPrimaryComponent looks for a component with type "deployment"
func (r *Controller) findPrimaryComponent(template *momv1.Template) *momv1.TemplateComponent {
	for i, component := range template.Spec.Components {
		if strings.EqualFold(component.Type, "deployment") {
			return &template.Spec.Components[i]
		}
	}
	return nil
}

// createAdditionalResources creates any additional resources defined in the template
func (r *Controller) createAdditionalResources(ctx context.Context, template *momv1.Template, deploymentName, namespace string, params map[string]string) error {
	logger := log.FromContext(ctx)

	// Keep track of which deployment component was processed as primary
	primaryComponentProcessed := false

	// Process each component in the template
	for _, component := range template.Spec.Components {
		// Check if this is a deployment or statefulset component
		if strings.EqualFold(component.Type, "deployment") || strings.EqualFold(component.Type, "statefulset") {
			// If we've already processed the primary component, we need to create this one as an additional resource
			if primaryComponentProcessed {
				// Create a new deployment name by combining base name with component name
				componentDeploymentName := deploymentName
				if component.Name != "" {
					componentDeploymentName = fmt.Sprintf("%s-%s", deploymentName, component.Name)
				}

				logger.Info("Creating additional component from template",
					"template", template.Name,
					"componentName", component.Name,
					"type", component.Type)

				// Handle different component types
				if strings.EqualFold(component.Type, "deployment") {
					// Create as a ContainerMomDeployment resource
					if err := r.createAdditionalDeployment(ctx, template, component, componentDeploymentName, deploymentName, namespace, params); err != nil {
						return err
					}
				} else if strings.EqualFold(component.Type, "statefulset") {
					// Create as a StatefulSet resource
					if err := r.createStatefulSet(ctx, template, component, componentDeploymentName, deploymentName, namespace, params); err != nil {
						return err
					}
				}
			} else {
				// Mark that we've seen the primary component
				primaryComponentProcessed = true
			}

			// Continue to next component
			continue
		}

		// Handle other component types
		switch strings.ToLower(component.Type) {
		case "configmap":
			if err := r.createConfigMap(ctx, template, component, deploymentName, namespace, params); err != nil {
				logger.Error(err, "Failed to create ConfigMap", "component", component.Name)
				return err
			}

		case "secret":
			if err := r.createSecret(ctx, template, component, deploymentName, namespace, params); err != nil {
				logger.Error(err, "Failed to create Secret", "component", component.Name)
				return err
			}

		case "service":
			if err := r.createService(ctx, template, component, deploymentName, namespace, params); err != nil {
				logger.Error(err, "Failed to create Service", "component", component.Name)
				return err
			}

		case "ingress":
			if err := r.createIngress(ctx, template, component, deploymentName, namespace, params); err != nil {
				logger.Error(err, "Failed to create Ingress", "component", component.Name)
				return err
			}

		case "volume":
			if err := r.createVolume(ctx, template, component, deploymentName, namespace, params); err != nil {
				logger.Error(err, "Failed to create Volume", "component", component.Name)
				return err
			}

		default:
			logger.Info("Skipping unknown component type", "type", component.Type, "component", component.Name)
		}
	}

	return nil
}

// createAdditionalDeployment creates a ContainerMomDeployment from a deployment component
func (r *Controller) createAdditionalDeployment(ctx context.Context, template *momv1.Template, component momv1.TemplateComponent, componentDeploymentName, parentDeploymentName, namespace string, params map[string]string) error {
	logger := log.FromContext(ctx)

	// Create the additional deployment
	additionalDeployment := &momv1.ContainerMomDeployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      componentDeploymentName,
			Namespace: namespace,
			Labels: map[string]string{
				"app":                          componentDeploymentName,
				"app.kubernetes.io/managed-by": "container-mom",
				"container.mom/template":       template.Name,
				"container.mom/parent":         parentDeploymentName,
				"container.mom/component":      component.Name,
			},
		},
		Spec: momv1.ContainerMomDeploymentSpec{
			Source: momv1.SourceSpec{
				Image:  component.Properties.Image,
				GitURL: component.Properties.GitURL,
				BuildConfig: &momv1.BuildConfig{
					BuilderImage: component.Properties.BuilderImage,
				},
			},
			Resources: component.Properties.Resources,
			Env:       substituteParams(component.Properties.Env, params),
		},
	}

	// Set controller reference so template owner will be updated if deployment changes
	if err := controllerutil.SetControllerReference(template, additionalDeployment, r.Scheme); err != nil {
		return err
	}

	// Create the deployment
	if err := r.Client.Create(ctx, additionalDeployment); err != nil {
		if errors.IsAlreadyExists(err) {
			logger.Info("Additional deployment already exists, updating it",
				"deploymentName", componentDeploymentName)

			// Get existing deployment
			existing := &momv1.ContainerMomDeployment{}
			if err := r.Client.Get(ctx, client.ObjectKey{
				Name:      componentDeploymentName,
				Namespace: namespace,
			}, existing); err != nil {
				return err
			}

			// Update fields
			existing.Spec = additionalDeployment.Spec

			// Update labels to ensure component name is set
			if existing.Labels == nil {
				existing.Labels = make(map[string]string)
			}
			existing.Labels["app"] = componentDeploymentName
			existing.Labels["container.mom/component"] = component.Name

			if err := r.Client.Update(ctx, existing); err != nil {
				return err
			}
		} else {
			return err
		}
	}

	return nil
}

// createStatefulSet creates a Kubernetes StatefulSet resource from a statefulset component
func (r *Controller) createStatefulSet(ctx context.Context, template *momv1.Template, component momv1.TemplateComponent, statefulSetName, parentDeploymentName, namespace string, params map[string]string) error {
	logger := log.FromContext(ctx)

	logger.Info("Creating StatefulSet from template",
		"template", template.Name,
		"statefulSetName", statefulSetName,
		"componentName", component.Name)

	// Prepare container environment variables
	var envVars []corev1.EnvVar
	for _, env := range substituteParams(component.Properties.Env, params) {
		envVar := corev1.EnvVar{
			Name: env.Name,
		}

		// Handle direct value
		if env.Value != "" {
			envVar.Value = env.Value
		}

		// Handle ValueFrom
		if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil {
			envVar.ValueFrom = &corev1.EnvVarSource{
				SecretKeyRef: &corev1.SecretKeySelector{
					LocalObjectReference: corev1.LocalObjectReference{
						Name: env.ValueFrom.SecretKeyRef.Name,
					},
					Key: env.ValueFrom.SecretKeyRef.Key,
				},
			}
		}

		envVars = append(envVars, envVar)
	}

	// Prepare volumes
	var volumes []corev1.Volume
	var volumeMounts []corev1.VolumeMount

	// Regular volumes
	for _, vol := range component.Properties.Volumes {
		if vol.Type == "emptyDir" {
			volumes = append(volumes, corev1.Volume{
				Name: vol.Name,
				VolumeSource: corev1.VolumeSource{
					EmptyDir: &corev1.EmptyDirVolumeSource{},
				},
			})
		} else if vol.Type == "configMap" {
			volumes = append(volumes, corev1.Volume{
				Name: vol.Name,
				VolumeSource: corev1.VolumeSource{
					ConfigMap: &corev1.ConfigMapVolumeSource{
						LocalObjectReference: corev1.LocalObjectReference{
							Name: vol.SourceName,
						},
					},
				},
			})
		} else if vol.Type == "secret" {
			volumes = append(volumes, corev1.Volume{
				Name: vol.Name,
				VolumeSource: corev1.VolumeSource{
					Secret: &corev1.SecretVolumeSource{
						SecretName: vol.SourceName,
					},
				},
			})
		}

		// Add volume mount
		if vol.MountPath != "" {
			volumeMounts = append(volumeMounts, corev1.VolumeMount{
				Name:      vol.Name,
				MountPath: vol.MountPath,
			})
		}
	}

	// Prepare resource requirements
	resources := corev1.ResourceRequirements{}
	if component.Properties.Resources.CPU != "" {
		if resources.Requests == nil {
			resources.Requests = corev1.ResourceList{}
		}
		resources.Requests[corev1.ResourceCPU] = resource.MustParse(component.Properties.Resources.CPU)
	}
	if component.Properties.Resources.Memory != "" {
		if resources.Requests == nil {
			resources.Requests = corev1.ResourceList{}
		}
		resources.Requests[corev1.ResourceMemory] = resource.MustParse(component.Properties.Resources.Memory)
	}

	// Prepare container
	container := corev1.Container{
		Name:         component.Name,
		Image:        component.Properties.Image,
		Env:          envVars,
		Resources:    resources,
		VolumeMounts: volumeMounts,
	}

	// Prepare PVC templates for StatefulSet
	var pvcTemplates []corev1.PersistentVolumeClaim
	for _, pvcTemplate := range component.Properties.VolumeClaimTemplates {
		// Convert access modes
		var accessModes []corev1.PersistentVolumeAccessMode
		for _, mode := range pvcTemplate.AccessModes {
			accessModes = append(accessModes, corev1.PersistentVolumeAccessMode(mode))
		}

		// Default to ReadWriteOnce if not specified
		if len(accessModes) == 0 {
			accessModes = []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}
		}

		// Create PVC template with storage resource
		storageSize := resource.MustParse(pvcTemplate.Size)

		pvc := corev1.PersistentVolumeClaim{
			ObjectMeta: metav1.ObjectMeta{
				Name: pvcTemplate.Name,
			},
			Spec: corev1.PersistentVolumeClaimSpec{
				AccessModes: accessModes,
				Resources: corev1.ResourceRequirements{
					Requests: corev1.ResourceList{
						corev1.ResourceStorage: storageSize,
					},
				},
			},
		}

		// Set storage class if specified
		if pvcTemplate.StorageClass != "" {
			storageClass := pvcTemplate.StorageClass
			pvc.Spec.StorageClassName = &storageClass
		}

		pvcTemplates = append(pvcTemplates, pvc)

		// Add volume mount for this PVC
		if pvcTemplate.MountPath != "" {
			volumeMounts = append(volumeMounts, corev1.VolumeMount{
				Name:      pvcTemplate.Name,
				MountPath: pvcTemplate.MountPath,
			})
		}
	}

	// Update container with volume mounts
	container.VolumeMounts = volumeMounts

	// Determine service name for StatefulSet
	serviceName := statefulSetName
	if component.Properties.StatefulSetConfig != nil && component.Properties.StatefulSetConfig.ServiceName != "" {
		serviceName = component.Properties.StatefulSetConfig.ServiceName
	}

	// Create StatefulSet
	statefulSet := &appsv1.StatefulSet{
		ObjectMeta: metav1.ObjectMeta{
			Name:      statefulSetName,
			Namespace: namespace,
			Labels: map[string]string{
				"app":                          statefulSetName,
				"app.kubernetes.io/managed-by": "container-mom",
				"container.mom/template":       template.Name,
				"container.mom/parent":         parentDeploymentName,
				"container.mom/component":      component.Name,
			},
		},
		Spec: appsv1.StatefulSetSpec{
			Replicas: ptr(int32(1)), // Default to 1 replica
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"app": statefulSetName,
				},
			},
			ServiceName: serviceName,
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"app": statefulSetName,
					},
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{container},
					Volumes:    volumes,
				},
			},
			VolumeClaimTemplates: pvcTemplates,
		},
	}

	// Configure pod management policy if specified
	if component.Properties.StatefulSetConfig != nil && component.Properties.StatefulSetConfig.PodManagementPolicy != "" {
		statefulSet.Spec.PodManagementPolicy = appsv1.PodManagementPolicyType(component.Properties.StatefulSetConfig.PodManagementPolicy)
	}

	// Configure update strategy if specified
	if component.Properties.StatefulSetConfig != nil && component.Properties.StatefulSetConfig.UpdateStrategy != nil {
		var updateStrategy appsv1.StatefulSetUpdateStrategy

		// Set type
		if component.Properties.StatefulSetConfig.UpdateStrategy.Type != "" {
			updateStrategy.Type = appsv1.StatefulSetUpdateStrategyType(component.Properties.StatefulSetConfig.UpdateStrategy.Type)
		}

		// Set rolling update if specified
		if component.Properties.StatefulSetConfig.UpdateStrategy.RollingUpdate != nil {
			updateStrategy.RollingUpdate = &appsv1.RollingUpdateStatefulSetStrategy{
				Partition: &component.Properties.StatefulSetConfig.UpdateStrategy.RollingUpdate.Partition,
			}
		}

		statefulSet.Spec.UpdateStrategy = updateStrategy
	}

	// Configure PVC retention policy if specified and using Kubernetes 1.23+
	if component.Properties.StatefulSetConfig != nil && component.Properties.StatefulSetConfig.PersistentVolumeClaimRetentionPolicy != nil {
		// PVC retention policy is only available in Kubernetes 1.23+
		// Uncomment this when targeting Kubernetes 1.23+
		/*
			policy := component.Properties.StatefulSetConfig.PersistentVolumeClaimRetentionPolicy
			statefulSet.Spec.PersistentVolumeClaimRetentionPolicy = &appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy{
				WhenDeleted: appsv1.PersistentVolumeClaimRetentionPolicyType(policy.WhenDeleted),
				WhenScaled:  appsv1.PersistentVolumeClaimRetentionPolicyType(policy.WhenScaled),
			}
		*/
	}

	// Set controller reference so template owner will be updated if statefulset changes
	if err := controllerutil.SetControllerReference(template, statefulSet, r.Scheme); err != nil {
		return err
	}

	// Create the StatefulSet
	if err := r.Client.Create(ctx, statefulSet); err != nil {
		if errors.IsAlreadyExists(err) {
			logger.Info("StatefulSet already exists, updating it",
				"statefulSetName", statefulSetName)

			// Get existing StatefulSet
			existing := &appsv1.StatefulSet{}
			if err := r.Client.Get(ctx, client.ObjectKey{
				Name:      statefulSetName,
				Namespace: namespace,
			}, existing); err != nil {
				return err
			}

			// Update fields that can be updated in StatefulSet
			existing.Spec.Template = statefulSet.Spec.Template
			existing.Spec.UpdateStrategy = statefulSet.Spec.UpdateStrategy
			// Cannot update volumeClaimTemplates once created

			// Update labels
			if existing.Labels == nil {
				existing.Labels = make(map[string]string)
			}
			existing.Labels["app"] = statefulSetName
			existing.Labels["container.mom/component"] = component.Name

			if err := r.Client.Update(ctx, existing); err != nil {
				return err
			}
		} else {
			return err
		}
	}

	return nil
}

// createConfigMap creates a ConfigMap resource
func (r *Controller) createConfigMap(ctx context.Context, template *momv1.Template, component momv1.TemplateComponent, deploymentName, namespace string, params map[string]string) error {
	configMapName := component.Name
	if configMapName == "" {
		configMapName = fmt.Sprintf("%s-config", deploymentName)
	}

	// Prepare ConfigMap data with parameter substitution
	configData := make(map[string]string)
	for k, v := range component.Properties.ConfigData {
		configData[k] = substituteParamsInString(v, params)
	}

	// Create ConfigMap
	configMap := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      configMapName,
			Namespace: namespace,
			Labels: map[string]string{
				"app.kubernetes.io/managed-by": "container-mom",
				"container.mom/template":       template.Name,
				"container.mom/deployment":     deploymentName,
			},
		},
		Data: configData,
	}

	// Set owner reference
	if err := controllerutil.SetControllerReference(template, configMap, r.Scheme); err != nil {
		return err
	}

	// Create the ConfigMap
	if err := r.Client.Create(ctx, configMap); err != nil {
		if errors.IsAlreadyExists(err) {
			// Get existing ConfigMap
			existing := &corev1.ConfigMap{}
			if err := r.Client.Get(ctx, client.ObjectKey{Name: configMapName, Namespace: namespace}, existing); err != nil {
				return err
			}

			// Update fields that may have changed
			existing.Data = configMap.Data
			if err := r.Client.Update(ctx, existing); err != nil {
				return err
			}
		} else {
			return err
		}
	}

	return nil
}

// createSecret creates a Secret resource
func (r *Controller) createSecret(ctx context.Context, template *momv1.Template, component momv1.TemplateComponent, deploymentName, namespace string, params map[string]string) error {
	logger := log.FromContext(ctx)

	secretName := component.Name
	if secretName == "" {
		secretName = fmt.Sprintf("%s-secret", deploymentName)
	}

	logger.Info("Creating secret for template",
		"templateName", template.Name,
		"componentName", component.Name,
		"secretName", secretName,
		"deploymentName", deploymentName)

	// Prepare Secret data with parameter substitution
	secretData := make(map[string][]byte)
	for k, v := range component.Properties.SecretData {
		secretData[k] = []byte(substituteParamsInString(v, params))
	}

	// Create Secret
	secret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      secretName,
			Namespace: namespace,
			Labels: map[string]string{
				"app.kubernetes.io/managed-by": "container-mom",
				"container.mom/template":       template.Name,
				"container.mom/deployment":     deploymentName,
			},
		},
		Type: corev1.SecretTypeOpaque,
		Data: secretData,
	}

	// Set owner reference
	if err := controllerutil.SetControllerReference(template, secret, r.Scheme); err != nil {
		return err
	}

	// Create the Secret
	if err := r.Client.Create(ctx, secret); err != nil {
		if errors.IsAlreadyExists(err) {
			// Get existing Secret
			existing := &corev1.Secret{}
			if err := r.Client.Get(ctx, client.ObjectKey{Name: secretName, Namespace: namespace}, existing); err != nil {
				return err
			}

			// Update fields that may have changed
			existing.Data = secret.Data
			if err := r.Client.Update(ctx, existing); err != nil {
				return err
			}
		} else {
			return err
		}
	}

	return nil
}

// createService creates a Service resource
func (r *Controller) createService(ctx context.Context, template *momv1.Template, component momv1.TemplateComponent, deploymentName, namespace string, _ map[string]string) error {
	// Determine service and deployment names
	serviceName := component.Name
	targetDeployment := deploymentName

	// If the component has a "targetComponent" property, use it to link to a specific deployment
	if component.Properties.TargetComponent != "" {
		// Link to specific component
		if component.Properties.TargetComponent != "primary" {
			// For non-primary targets, generate the deployment name with component suffix
			targetDeployment = fmt.Sprintf("%s-%s", deploymentName, component.Properties.TargetComponent)
		}
		// If no service name is specified, use the target component name
		if serviceName == "" {
			serviceName = component.Properties.TargetComponent
		}
	} else if serviceName == "" {
		// Default service name to component name if not specified
		serviceName = deploymentName
	}

	// Convert ports
	var ports []corev1.ServicePort
	for _, p := range component.Properties.Ports {
		port := corev1.ServicePort{
			Name:       p.Name,
			Port:       p.Port,
			TargetPort: intstr.FromInt32(p.TargetPort),
		}
		if p.Protocol != "" {
			port.Protocol = corev1.Protocol(p.Protocol)
		}
		ports = append(ports, port)
	}

	// Create Service
	service := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      serviceName,
			Namespace: namespace,
			Labels: map[string]string{
				"app.kubernetes.io/managed-by": "container-mom",
				"container.mom/template":       template.Name,
				"container.mom/deployment":     deploymentName,
			},
		},
		Spec: corev1.ServiceSpec{
			Selector: map[string]string{
				"app": targetDeployment,
			},
			Ports: ports,
		},
	}

	// Set ClusterIP if specified (None for headless service)
	if component.Properties.ClusterIP != "" {
		service.Spec.ClusterIP = component.Properties.ClusterIP
	}

	// Set owner reference
	if err := controllerutil.SetControllerReference(template, service, r.Scheme); err != nil {
		return err
	}

	// Create the Service
	if err := r.Client.Create(ctx, service); err != nil {
		if errors.IsAlreadyExists(err) {
			// Get existing Service
			existing := &corev1.Service{}
			if err := r.Client.Get(ctx, client.ObjectKey{Name: serviceName, Namespace: namespace}, existing); err != nil {
				return err
			}

			// Update ports and selector
			existing.Spec.Ports = ports
			existing.Spec.Selector = map[string]string{
				"app": targetDeployment,
			}

			// Update ClusterIP if specified and service is not yet created
			// Note: ClusterIP cannot be changed once a service is created

			if err := r.Client.Update(ctx, existing); err != nil {
				return err
			}
		} else {
			return err
		}
	}

	return nil
}

// createIngress creates an Ingress resource
func (r *Controller) createIngress(ctx context.Context, template *momv1.Template, _ momv1.TemplateComponent, deploymentName, namespace string, _ map[string]string) error {
	// Use NetworkConfig from template
	if len(template.Spec.NetworkConfig.IngressRules) == 0 {
		// No ingress rules defined, nothing to do
		return nil
	}

	ingressName := fmt.Sprintf("%s-ingress", deploymentName)

	// Convert ingress rules
	var ingressRules []networkingv1.IngressRule
	var tls []networkingv1.IngressTLS

	for _, rule := range template.Spec.NetworkConfig.IngressRules {
		httpPaths := []networkingv1.HTTPIngressPath{
			{
				Path:     rule.Path,
				PathType: func() *networkingv1.PathType { pt := networkingv1.PathTypePrefix; return &pt }(),
				Backend: networkingv1.IngressBackend{
					Service: &networkingv1.IngressServiceBackend{
						Name: rule.ServiceName,
						Port: networkingv1.ServiceBackendPort{
							Number: rule.ServicePort,
						},
					},
				},
			},
		}

		ingressRules = append(ingressRules, networkingv1.IngressRule{
			Host: rule.Host,
			IngressRuleValue: networkingv1.IngressRuleValue{
				HTTP: &networkingv1.HTTPIngressRuleValue{
					Paths: httpPaths,
				},
			},
		})

		// Add TLS if domain has TLS config
		if template.Spec.NetworkConfig.Domain.TLS != nil && template.Spec.NetworkConfig.Domain.TLS.AutoCert {
			hosts := []string{rule.Host}
			tls = append(tls, networkingv1.IngressTLS{
				Hosts:      hosts,
				SecretName: fmt.Sprintf("%s-tls", deploymentName),
			})
		}
	}

	// Create Ingress
	ingress := &networkingv1.Ingress{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ingressName,
			Namespace: namespace,
			Labels: map[string]string{
				"app.kubernetes.io/managed-by": "container-mom",
				"container.mom/template":       template.Name,
				"container.mom/deployment":     deploymentName,
			},
			Annotations: map[string]string{
				"kubernetes.io/ingress.class": "nginx",
			},
		},
		Spec: networkingv1.IngressSpec{
			Rules: ingressRules,
			TLS:   tls,
		},
	}

	// Set owner reference
	if err := controllerutil.SetControllerReference(template, ingress, r.Scheme); err != nil {
		return err
	}

	// Create the Ingress
	if err := r.Client.Create(ctx, ingress); err != nil {
		if errors.IsAlreadyExists(err) {
			// Get existing Ingress
			existing := &networkingv1.Ingress{}
			if err := r.Client.Get(ctx, client.ObjectKey{Name: ingressName, Namespace: namespace}, existing); err != nil {
				return err
			}

			// Update spec
			existing.Spec.Rules = ingressRules
			existing.Spec.TLS = tls
			if err := r.Client.Update(ctx, existing); err != nil {
				return err
			}
		} else {
			return err
		}
	}

	return nil
}

// createVolume creates a PersistentVolumeClaim resource
func (r *Controller) createVolume(ctx context.Context, template *momv1.Template, component momv1.TemplateComponent, deploymentName, namespace string, _ map[string]string) error {
	// Only handle PVC type volumes
	var pvcFound bool
	for _, vol := range component.Properties.Volumes {
		if strings.EqualFold(vol.Type, "persistentVolumeClaim") {
			pvcFound = true
			break
		}
	}

	if !pvcFound {
		return nil // No PVCs to create
	}

	// Process each volume
	for _, vol := range component.Properties.Volumes {
		if !strings.EqualFold(vol.Type, "persistentVolumeClaim") {
			continue
		}

		pvcName := vol.Name
		if pvcName == "" {
			pvcName = fmt.Sprintf("%s-data", deploymentName)
		}

		// Create PVC
		pvc := &corev1.PersistentVolumeClaim{
			ObjectMeta: metav1.ObjectMeta{
				Name:      pvcName,
				Namespace: namespace,
				Labels: map[string]string{
					"app.kubernetes.io/managed-by": "container-mom",
					"container.mom/template":       template.Name,
					"container.mom/deployment":     deploymentName,
				},
			},
			Spec: corev1.PersistentVolumeClaimSpec{
				AccessModes: []corev1.PersistentVolumeAccessMode{
					corev1.ReadWriteOnce,
				},
				Resources: corev1.VolumeResourceRequirements{
					Requests: corev1.ResourceList{
						corev1.ResourceStorage: func() resource.Quantity {
							q, err := resource.ParseQuantity(vol.Size)
							if err != nil {
								return resource.MustParse("1Gi") // Default
							}
							return q
						}(),
					},
				},
			},
		}

		// Set storage class if specified
		if vol.StorageClass != "" {
			pvc.Spec.StorageClassName = &vol.StorageClass
		}

		// Set owner reference
		if err := controllerutil.SetControllerReference(template, pvc, r.Scheme); err != nil {
			return err
		}

		// Create the PVC
		if err := r.Client.Create(ctx, pvc); err != nil {
			if !errors.IsAlreadyExists(err) {
				return err
			}
			// If PVC already exists, we don't update it since resizing is complex
		}
	}

	return nil
}

// substituteParams replaces parameter placeholders in environment variables
func substituteParams(envVars []momv1.EnvVar, params map[string]string) []momv1.EnvVar {
	result := make([]momv1.EnvVar, len(envVars))
	for i, env := range envVars {
		newEnv := momv1.EnvVar{
			Name: env.Name,
		}

		// Handle direct values by substituting parameters
		if env.Value != "" {
			newEnv.Value = substituteParamsInString(env.Value, params)
		}

		// Preserve ValueFrom references (for secrets, configmaps, etc.)
		if env.ValueFrom != nil {
			newEnv.ValueFrom = env.ValueFrom

			// If it's a SecretKeyRef, check if we need to substitute parameters in the name
			if env.ValueFrom.SecretKeyRef != nil {
				secretName := env.ValueFrom.SecretKeyRef.Name
				if strings.Contains(secretName, "${") {
					secretName = substituteParamsInString(secretName, params)
				}

				newEnv.ValueFrom = &momv1.EnvVarValueFrom{
					SecretKeyRef: &momv1.SecretKeySelector{
						Name: secretName,
						Key:  env.ValueFrom.SecretKeyRef.Key,
					},
				}
			}
		}

		result[i] = newEnv
	}
	return result
}

// substituteParamsInString replaces parameter placeholders in a string with actual values
func substituteParamsInString(input string, params map[string]string) string {
	result := input
	for name, value := range params {
		placeholder := fmt.Sprintf("${%s}", name)
		result = strings.ReplaceAll(result, placeholder, value)
	}
	return result
}

// Helper function to create a pointer to an int32
func ptr[T any](v T) *T {
	return &v
}
