// Package workload implements Kubernetes workload management for the container-mom system.
// It handles deployment of resources across multiple clusters and regions.
package workload

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"strings"
	"time"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"hash/fnv"

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

// reconcileInCluster handles deployment of resources to a specific cluster
// Moved from workload_controller.go to improve code organization
func (c *Controller) reconcileInCluster(ctx context.Context, client client.Client, deployment *v1alpha1.ContainerMomDeployment) error {
	log := ctrl.LoggerFrom(ctx)

	// First, check if we need to create a namespace
	var appNamespace string
	if ns, ok := deployment.Annotations["containermom.mom/namespace"]; ok && ns != "" {
		appNamespace = ns
	} else {
		// No namespace annotation, need to create one
		customerName := deployment.Name
		if owner, ok := deployment.Labels["containermom.mom/owner"]; ok && owner != "" {
			customerName = owner
		}
		appNamespace = generateNamespaceName(customerName, deployment.Name)
	}

	// Check for pause annotation
	isPaused := false
	if deployment.Annotations != nil {
		if _, ok := deployment.Annotations["containermom.mom/paused"]; ok {
			isPaused = true
			log.Info("Deployment is paused, workloads will be scaled to 0",
				"deployment", deployment.Name,
				"namespace", appNamespace)
		}
	}

	// First verify the namespace exists using direct API call rather than client cache
	// This helps avoid cache synchronization issues
	nsCheckCmd := exec.Command("oc", "get", "namespace", appNamespace)
	nsOutput, nsErr := nsCheckCmd.CombinedOutput()

	namespaceExists := nsErr == nil

	if !namespaceExists {
		log.Info("Namespace doesn't exist or can't be accessed",
			"namespace", appNamespace,
			"error", string(nsOutput))

		// Create the namespace using oc to avoid cache issues
		nsCreateCmd := exec.Command("oc", "create", "namespace", appNamespace)
		createOutput, createErr := nsCreateCmd.CombinedOutput()

		if createErr != nil {
			log.Error(createErr, "Failed to create namespace",
				"namespace", appNamespace,
				"output", string(createOutput))
			return fmt.Errorf("failed to create namespace %s: %w", appNamespace, createErr)
		}

		log.Info("Created namespace using direct oc command",
			"namespace", appNamespace)

		// Add labels to the namespace
		labelCmd := exec.Command("oc", "label", "namespace", appNamespace,
			"app.kubernetes.io/managed-by=container-mom",
			fmt.Sprintf("containermom.mom/owner=%s", deployment.Name),
			"--overwrite")
		labelOutput, labelErr := labelCmd.CombinedOutput()

		if labelErr != nil {
			log.Error(labelErr, "Failed to label namespace",
				"namespace", appNamespace,
				"output", string(labelOutput))
			// Continue despite label error
		}

		// Update the deployment with the namespace annotation if needed
		if _, ok := deployment.Annotations["containermom.mom/namespace"]; !ok {
			freshDeployment := &v1alpha1.ContainerMomDeployment{}
			if err := c.client.Get(ctx, utils.NamespacedName{Name: deployment.Name, Namespace: deployment.Namespace}, freshDeployment); err != nil {
				log.Error(err, "Failed to get fresh deployment for annotation update", "name", deployment.Name)
			} else {
				// Initialize annotations if needed
				if freshDeployment.Annotations == nil {
					freshDeployment.Annotations = make(map[string]string)
				}
				// Set the namespace annotation
				freshDeployment.Annotations["containermom.mom/namespace"] = appNamespace
				if err := c.client.Update(ctx, freshDeployment); err != nil {
					log.Error(err, "Failed to update deployment with namespace annotation")
					// Continue despite error - we can recover on next reconciliation
				} else {
					log.Info("Updated deployment with namespace annotation", "namespace", appNamespace)
				}
			}
		}

		// Wait a moment for the namespace to be fully established
		time.Sleep(2 * time.Second)
	}

	// Get the image to use
	imageName := c.getImageForDeployment(ctx, deployment)

	// Check if deployment exists using oc
	deployCmd := exec.Command("oc", "get", "deployment", deployment.Name, "-n", appNamespace)
	deployOutput, deployErr := deployCmd.CombinedOutput()

	deploymentExists := deployErr == nil

	// First, create a secret for the environment variables
	if len(deployment.Spec.Env) > 0 {
		log.Info("Creating secret for environment variables",
			"namespace", appNamespace,
			"name", fmt.Sprintf("%s-env", deployment.Name))

		// Build a map of environment variables
		envData := make(map[string]string)
		for _, env := range deployment.Spec.Env {
			if env.Name != "" {
				envData[env.Name] = env.Value
			}
		}

		// Instead of building YAML directly, create a JSON structure for the secret
		// This avoids issues with YAML formatting and special characters
		secretJSON := fmt.Sprintf(`{
  "apiVersion": "v1",
  "kind": "Secret",
  "metadata": {
    "name": "%s-env",
    "namespace": "%s",
    "labels": {
      "app": "%s",
      "app.kubernetes.io/managed-by": "container-mom"
    }
  },
  "type": "Opaque",
  "stringData": {`, deployment.Name, appNamespace, deployment.Name)

		// Add each environment variable to the secret with proper JSON escaping
		first := true
		for key, value := range envData {
			if !first {
				secretJSON += ","
			}
			// Escape JSON special characters in the value
			escapedValue := escapeJSONString(value)
			secretJSON += fmt.Sprintf(`
    "%s": "%s"`, key, escapedValue)
			first = false
		}

		// Close the JSON structure
		secretJSON += `
  }
}`

		// Apply the secret
		secretCmd := exec.Command("oc", "apply", "-f", "-")
		secretCmd.Stdin = strings.NewReader(secretJSON)
		secretOutput, secretErr := secretCmd.CombinedOutput()

		if secretErr != nil {
			log.Error(secretErr, "Failed to create secret for environment variables",
				"namespace", appNamespace,
				"name", fmt.Sprintf("%s-env", deployment.Name),
				"output", string(secretOutput))
			// We'll continue and just log the error, as we can still create the deployment without env vars
		} else {
			log.Info("Created secret for environment variables",
				"namespace", appNamespace,
				"name", fmt.Sprintf("%s-env", deployment.Name))
		}
	}

	if !deploymentExists {
		log.Info("Deployment doesn't exist, will create",
			"namespace", appNamespace,
			"name", deployment.Name,
			"output", string(deployOutput))

		// Create deployment with oc
		// Set replicas based on pause state
		replicas := 1
		if isPaused {
			replicas = 0
		}

		// Convert CPU and memory requests to appropriate format
		cpuRequest := "100m"
		memoryRequest := "128Mi"
		if deployment.Spec.Resources.CPU != "" {
			cpuRequest = deployment.Spec.Resources.CPU
		}
		if deployment.Spec.Resources.Memory != "" {
			memoryRequest = deployment.Spec.Resources.Memory
		}

		// Base deployment YAML
		deployYAML := fmt.Sprintf(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: %s
  namespace: %s
  labels:
    app: %s
    app.kubernetes.io/managed-by: container-mom
spec:
  replicas: %d
  selector:
    matchLabels:
      app: %s
  template:
    metadata:
      labels:
        app: %s
    spec:
      containers:
      - name: %s
        image: %s
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: %s
            memory: %s
          limits:
            cpu: %s
            memory: %s
`, deployment.Name, appNamespace, deployment.Name, replicas,
			deployment.Name, deployment.Name, deployment.Name,
			imageName, cpuRequest, memoryRequest, cpuRequest, memoryRequest)

		// Add environment variables from secret if we have any
		if len(deployment.Spec.Env) > 0 {
			deployYAML += fmt.Sprintf(`        envFrom:
        - secretRef:
            name: %s-env
`, deployment.Name)
		}

		// Apply the deployment
		applyCmd := exec.Command("oc", "apply", "-f", "-")
		applyCmd.Stdin = strings.NewReader(deployYAML)
		applyOutput, applyErr := applyCmd.CombinedOutput()

		if applyErr != nil {
			log.Error(applyErr, "Failed to create deployment with direct oc command",
				"namespace", appNamespace,
				"name", deployment.Name,
				"output", string(applyOutput))
			return fmt.Errorf("failed to create deployment: %w", applyErr)
		}

		log.Info("Created deployment", "namespace", appNamespace, "name", deployment.Name)

		// Create PVs for any components that use our custom storage class
		if len(deployment.Spec.Components) > 0 {
			log.Info("Checking components for volume claims that need PVs")

			for _, component := range deployment.Spec.Components {
				// Check if this component has volume claim templates
				if component.VolumeClaimTemplates != nil && len(component.VolumeClaimTemplates) > 0 {
					for _, vct := range component.VolumeClaimTemplates {
						if strings.EqualFold(vct.StorageClass, "customer-workload-storage") {
							// Construct full PVC name as it will appear in the cluster
							fullPVCName := fmt.Sprintf("%s-%s-0", vct.Name, fmt.Sprintf("%s-%s", deployment.Name, component.Name))

							if err := c.createPersistentVolumeForClaim(ctx, fullPVCName, appNamespace,
								"customer-workload-storage", vct.Size, component.Name); err != nil {
								log.Error(err, "Failed to create PersistentVolume for component volume claim",
									"pvc", fullPVCName,
									"component", component.Name)
								// Continue despite error
							}
						}
					}
				}
			}
		}
	} else {
		// If the deployment exists, we still need to update the secret
		// and then trigger a deployment rollout to pick up the changes

		if len(deployment.Spec.Env) > 0 {
			log.Info("Deployment exists, updating to ensure any env var changes are applied",
				"namespace", appNamespace,
				"name", deployment.Name)

			// Trigger a rollout restart to pick up the updated secret
			restartCmd := exec.Command("oc", "rollout", "restart", "deployment", deployment.Name, "-n", appNamespace)
			_, restartErr := restartCmd.CombinedOutput()

			if restartErr != nil {
				log.Error(restartErr, "Failed to restart deployment to apply env changes",
					"namespace", appNamespace,
					"name", deployment.Name)
				// Continue despite the error as it's not critical
			} else {
				log.Info("Triggered rollout to apply environment changes",
					"namespace", appNamespace,
					"name", deployment.Name)
			}
		}

		log.Info("Deployment already exists", "namespace", appNamespace, "name", deployment.Name)
	}

	// Create or update service using direct oc
	err := c.createOrUpdateService(ctx, client, deployment, appNamespace)
	if err != nil {
		log.Error(err, "Failed to create or update service",
			"namespace", appNamespace,
			"name", deployment.Name)
		return err
	}

	// Create route if domain is specified
	if deployment.Spec.Domain.DomainName != "" {
		if err := c.createOrUpdateRoute(ctx, deployment, appNamespace); err != nil {
			log.Error(err, "Failed to create route",
				"namespace", appNamespace,
				"name", deployment.Name)
			// Continue despite route error - it's not fatal
		}
	}

	// Process additional components if any
	if len(deployment.Spec.Components) > 0 {
		log.Info("Processing additional components", "count", len(deployment.Spec.Components))

		for _, component := range deployment.Spec.Components {
			// Use a consistent, cleaner naming convention for components
			// This prevents issues with double names when deployment name already contains the component name
			componentName := generateComponentName(deployment.Name, component.Name)

			log.Info("Creating additional component",
				"componentName", componentName,
				"componentType", component.Type)

			if strings.EqualFold(component.Type, "statefulset") {
				// Create StatefulSet for this component
				if err := c.createOrUpdateStatefulSet(ctx, deployment, component, componentName, appNamespace, isPaused); err != nil {
					log.Error(err, "Failed to create StatefulSet for component",
						"component", component.Name,
						"namespace", appNamespace)
					// Continue with other components despite error
				}
			} else {
				// Create Deployment for this component
				if err := c.createOrUpdateComponentDeployment(ctx, deployment, component, componentName, appNamespace, isPaused); err != nil {
					log.Error(err, "Failed to create Deployment for component",
						"component", component.Name,
						"namespace", appNamespace)
					// Continue with other components despite error
				}
			}
		}
	}

	return nil
}

// buildHostname creates a hostname from a DomainSpec
// Moved from workload_controller.go to improve code organization
func buildHostname(domain v1alpha1.DomainSpec) string {
	// Process the domain name to replace any environment variables
	processedDomainName := processEnvVars(domain.DomainName)

	if domain.UseWildcard {
		return "*." + processedDomainName
	}

	if domain.Prefix != "" {
		// Also process any environment variables in the prefix
		processedPrefix := processEnvVars(domain.Prefix)

		// Check if domain already contains the prefix to prevent duplication
		// This handles cases like 'prefix.prefix.domain.com'
		prefixWithDot := processedPrefix + "."
		if strings.HasPrefix(processedDomainName, prefixWithDot) {
			return processedDomainName
		}

		return processedPrefix + "." + processedDomainName
	}

	return processedDomainName
}

// processEnvVars replaces environment variables in the format ${VAR_NAME} with their values
// If the environment variable is not set, it's replaced with a default value or empty string
func processEnvVars(input string) string {
	// Quick check to see if we need to do any processing
	if !strings.Contains(input, "${") {
		return input
	}

	// Common environment variables that might need default values
	defaultValues := map[string]string{
		"DOMAIN_NAME": "container.mom",
	}

	// Get all system environment variables
	osEnv := make(map[string]string)
	for _, e := range os.Environ() {
		parts := strings.SplitN(e, "=", 2)
		if len(parts) == 2 {
			osEnv[parts[0]] = parts[1]
		}
	}

	result := input

	// Replace all environment variables with the pattern ${VAR_NAME}
	for {
		startIndex := strings.Index(result, "${")
		if startIndex == -1 {
			break
		}

		endIndex := strings.Index(result[startIndex:], "}")
		if endIndex == -1 {
			break
		}
		endIndex += startIndex

		varName := result[startIndex+2 : endIndex]
		var value string

		// Check if we have a value for this variable
		if val, ok := osEnv[varName]; ok {
			value = val
		} else if val, ok := defaultValues[varName]; ok {
			value = val
		} else {
			// If no value is found, just use empty string
			value = ""
		}

		// Replace the variable with its value
		result = result[:startIndex] + value + result[endIndex+1:]
	}

	return result
}

// convertToK8sEnvVars converts ContainerMom env vars to Kubernetes env vars
// Moved from workload_controller.go to improve code organization
func convertToK8sEnvVars(envVars []v1alpha1.EnvVar) []corev1.EnvVar {
	// First, count valid env vars (those with non-empty names)
	validCount := 0
	for _, env := range envVars {
		if env.Name != "" {
			validCount++
		}
	}

	// Create slice with exact size needed
	k8sEnvVars := make([]corev1.EnvVar, 0, validCount)

	// Only include env vars with non-empty names
	for _, env := range envVars {
		if env.Name != "" {
			k8sEnvVars = append(k8sEnvVars, corev1.EnvVar{
				Name:  env.Name,
				Value: env.Value,
			})
		}
	}

	return k8sEnvVars
}

// createOrUpdateService creates or updates a Kubernetes Service for the given deployment
func (c *Controller) createOrUpdateService(ctx context.Context, client client.Client, deployment *v1alpha1.ContainerMomDeployment, namespace string) error {
	log := ctrl.LoggerFrom(ctx)

	// Check if service exists using oc to avoid cache issues
	serviceCmd := exec.Command("oc", "get", "service", deployment.Name, "-n", namespace)
	serviceOutput, serviceErr := serviceCmd.CombinedOutput()

	serviceExists := serviceErr == nil

	if !serviceExists {
		log.Info("Service doesn't exist, will create it",
			"namespace", namespace,
			"name", deployment.Name,
			"output", string(serviceOutput))

		// Create service YAML
		serviceYAML := fmt.Sprintf(`
apiVersion: v1
kind: Service
metadata:
  name: %s
  namespace: %s
  labels:
    app: %s
    app.kubernetes.io/managed-by: container-mom
spec:
  selector:
    app: %s
  ports:
  - name: http
    port: 80
    targetPort: 8080
    protocol: TCP
  type: ClusterIP
`, deployment.Name, namespace, deployment.Name, deployment.Name)

		// Apply service using oc
		applyCmd := exec.Command("oc", "apply", "-f", "-")
		applyCmd.Stdin = strings.NewReader(serviceYAML)
		applyOutput, applyErr := applyCmd.CombinedOutput()

		if applyErr != nil {
			log.Error(applyErr, "Failed to create service with direct command",
				"namespace", namespace,
				"name", deployment.Name,
				"output", string(applyOutput))
			return fmt.Errorf("failed to create service: %w", applyErr)
		}

		log.Info("Created service with direct command",
			"namespace", namespace,
			"name", deployment.Name)
	} else {
		// Service exists, no update needed as service selector and port don't change
		log.Info("Service already exists",
			"namespace", namespace,
			"name", deployment.Name)
	}

	return nil
}

// createOrUpdateRoute creates or updates an OpenShift route for the deployment
func (c *Controller) createOrUpdateRoute(ctx context.Context, deployment *v1alpha1.ContainerMomDeployment, namespace string) error {
	log := ctrl.LoggerFrom(ctx)

	// Get hostname from domain spec
	hostname := buildHostname(deployment.Spec.Domain)
	log.Info("Creating or updating route with hostname",
		"namespace", namespace,
		"name", deployment.Name,
		"hostname", hostname,
		"domainName", deployment.Spec.Domain.DomainName,
		"prefix", deployment.Spec.Domain.Prefix)

	// Call the centralized route creation function
	return CreateOrUpdateRoute(ctx, deployment, deployment.Name, deployment.Name, hostname, namespace)
}

// updateDeploymentWithCommandLine uses the oc/kubectl command to update a deployment when the cache method fails
func (c *Controller) updateDeploymentWithCommandLine(ctx context.Context, deployment *appsv1.Deployment, imageName string) error {
	log := ctrl.LoggerFrom(ctx)

	// Create patch data to update image
	patchData := fmt.Sprintf(`{"spec":{"template":{"spec":{"containers":[{"name":"%s","image":"%s"}]}}}}`,
		deployment.Name,
		imageName)

	// Use oc patch command
	patchCmd := exec.Command("oc", "patch", "deployment", deployment.Name,
		"-n", deployment.Namespace,
		"--type=strategic",
		"-p", patchData)

	output, err := patchCmd.CombinedOutput()
	if err != nil {
		// If deployment doesn't exist, create it
		if strings.Contains(string(output), "not found") {
			log.Info("Deployment not found, creating with command line",
				"namespace", deployment.Namespace,
				"name", deployment.Name)

			// Convert deployment to YAML
			// For simplicity, we'll use a simplified version that just has the core elements
			deployYAML := fmt.Sprintf(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: %s
  namespace: %s
  labels:
    app: %s
    app.kubernetes.io/managed-by: container-mom
spec:
  replicas: 1
  selector:
    matchLabels:
      app: %s
  template:
    metadata:
      labels:
        app: %s
    spec:
      containers:
      - name: %s
        image: %s
        ports:
        - containerPort: 8080
          name: http
`, deployment.Name, deployment.Namespace, deployment.Name,
				deployment.Name, deployment.Name, deployment.Name, imageName)

			createCmd := exec.Command("oc", "apply", "-f", "-")
			createCmd.Stdin = strings.NewReader(deployYAML)
			createOutput, createErr := createCmd.CombinedOutput()

			if createErr != nil {
				return fmt.Errorf("failed to create deployment with command line: %w, output: %s", createErr, string(createOutput))
			}

			log.Info("Created deployment with command line",
				"namespace", deployment.Namespace,
				"name", deployment.Name)

			return nil
		}

		return fmt.Errorf("failed to update deployment with command line: %w, output: %s", err, string(output))
	}

	log.Info("Updated deployment with command line",
		"namespace", deployment.Namespace,
		"name", deployment.Name,
		"image", imageName)

	return nil
}

// createServiceWithCommandLine creates a service using the oc/kubectl command line
// nolint:unused // Reserved for future implementation
func createServiceWithCommandLine(_, _ map[string]string, _ string, _ string, _ metav1.Object, _ int32, _ map[string]string) (*corev1.Service, error) {
	return nil, nil
}

// escapeJSONString escapes special characters in a string for JSON
func escapeJSONString(s string) string {
	// Replace common special characters
	result := strings.ReplaceAll(s, "\\", "\\\\")
	result = strings.ReplaceAll(result, "\"", "\\\"")
	result = strings.ReplaceAll(result, "\n", "\\n")
	result = strings.ReplaceAll(result, "\r", "\\r")
	result = strings.ReplaceAll(result, "\t", "\\t")
	result = strings.ReplaceAll(result, "\f", "\\f")
	result = strings.ReplaceAll(result, "\b", "\\b")
	return result
}

// createOrUpdateStatefulSet creates or updates a StatefulSet for a component
func (c *Controller) createOrUpdateStatefulSet(ctx context.Context, parent *v1alpha1.ContainerMomDeployment,
	component v1alpha1.DeploymentComponent, componentName, namespace string, isPaused bool) error {

	log := ctrl.LoggerFrom(ctx)

	// Determine service name for StatefulSet
	serviceName := fmt.Sprintf("%s-headless", componentName)

	// Set replicas based on pause state
	replicas := 1
	if isPaused {
		replicas = 0
	}

	// Convert CPU and memory requests to appropriate format
	cpuRequest := "100m"
	memoryRequest := "128Mi"
	if component.Resources.CPU != "" {
		cpuRequest = component.Resources.CPU
	}
	if component.Resources.Memory != "" {
		memoryRequest = component.Resources.Memory
	}

	// Check if StatefulSet exists
	ssCmd := exec.Command("oc", "get", "statefulset", componentName, "-n", namespace)
	ssOutput, ssErr := ssCmd.CombinedOutput()
	statefulSetExists := ssErr == nil

	// Create secret for environment variables if any
	if len(component.Env) > 0 {
		log.Info("Creating secret for component environment variables",
			"namespace", namespace,
			"name", fmt.Sprintf("%s-env", componentName))

		// Build a map of environment variables
		envData := make(map[string]string)
		for _, env := range component.Env {
			if env.Name != "" {
				envData[env.Name] = env.Value
			}
		}

		// Create JSON structure for the secret
		secretJSON := fmt.Sprintf(`{
  "apiVersion": "v1",
  "kind": "Secret",
  "metadata": {
    "name": "%s-env",
    "namespace": "%s",
    "labels": {
      "app": "%s",
      "app.kubernetes.io/managed-by": "container-mom",
      "container.mom/component": "%s"
    }
  },
  "type": "Opaque",
  "stringData": {`, componentName, namespace, componentName, component.Name)

		// Add each environment variable to the secret with proper JSON escaping
		first := true
		for key, value := range envData {
			if !first {
				secretJSON += ","
			}
			// Escape JSON special characters in the value
			escapedValue := escapeJSONString(value)
			secretJSON += fmt.Sprintf(`
    "%s": "%s"`, key, escapedValue)
			first = false
		}

		// Close the JSON structure
		secretJSON += `
  }
}`

		// Apply the secret
		secretCmd := exec.Command("oc", "apply", "-f", "-")
		secretCmd.Stdin = strings.NewReader(secretJSON)
		secretOutput, secretErr := secretCmd.CombinedOutput()

		if secretErr != nil {
			log.Error(secretErr, "Failed to create secret for component environment variables",
				"namespace", namespace,
				"name", fmt.Sprintf("%s-env", componentName),
				"output", string(secretOutput))
			// We'll continue and just log the error
		} else {
			log.Info("Created secret for component environment variables",
				"namespace", namespace,
				"name", fmt.Sprintf("%s-env", componentName))
		}
	}

	if !statefulSetExists {
		log.Info("StatefulSet doesn't exist, will create",
			"namespace", namespace,
			"name", componentName,
			"output", string(ssOutput))

		// Base StatefulSet YAML
		ssYAML := fmt.Sprintf(`
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: %s
  namespace: %s
  labels:
    app: %s
    app.kubernetes.io/managed-by: container-mom
    container.mom/deployment: %s
    container.mom/component: %s
spec:
  replicas: %d
  selector:
    matchLabels:
      app: %s
  serviceName: %s
  template:
    metadata:
      labels:
        app: %s
    spec:
      containers:
      - name: %s
        image: %s
        resources:
          requests:
            cpu: %s
            memory: %s
          limits:
            cpu: %s
            memory: %s
`, componentName, namespace, componentName, parent.Name, component.Name, replicas,
			componentName, serviceName, componentName, component.Name,
			component.Source.Image, cpuRequest, memoryRequest, cpuRequest, memoryRequest)

		// Add environment variables from secret if we have any
		if len(component.Env) > 0 {
			ssYAML += fmt.Sprintf(`        envFrom:
        - secretRef:
            name: %s-env
`, componentName)
		}

		// Check if we need to add volume claim templates
		if len(component.VolumeClaimTemplates) > 0 {
			ssYAML += `  volumeClaimTemplates:
`

			for _, vct := range component.VolumeClaimTemplates {
				// Create access modes string
				accessModes := "ReadWriteOnce"
				if len(vct.AccessModes) > 0 {
					accessModes = strings.Join(vct.AccessModes, ", ")
				}

				// Add volume claim template
				ssYAML += fmt.Sprintf(`  - metadata:
      name: %s
    spec:
      accessModes: ["%s"]
      resources:
        requests:
          storage: %s
`, vct.Name, accessModes, vct.Size)

				// Handle storage class selection
				storageClass := vct.StorageClass
				if storageClass != "" {
					// Check if the storage class exists
					scCheckCmd := exec.Command("oc", "get", "storageclass", storageClass)
					_, scErr := scCheckCmd.CombinedOutput()
					if scErr != nil {
						// Storage class doesn't exist, try to find an available one
						log.Info("Requested storage class doesn't exist, falling back to customer-workload-storage",
							"requestedClass", storageClass)

						// Use our custom storage class as fallback
						storageClass = "customer-workload-storage"
					}

					// Add storage class if we have a valid one
					if storageClass != "" {
						ssYAML += fmt.Sprintf(`      storageClassName: %s
`, storageClass)
					} else {
						log.Info("No valid storage class found, omitting storageClassName field",
							"component", component.Name,
							"volumeClaim", vct.Name)
					}
				} else {
					// No storage class specified, default to our customer-workload-storage
					log.Info("No storage class specified, using customer-workload-storage by default")
					storageClass = "customer-workload-storage"
					ssYAML += fmt.Sprintf(`      storageClassName: %s
`, storageClass)
				}

				// Add volume mounts to container
				ssYAML += fmt.Sprintf(`      containers:
      - name: %s
        volumeMounts:
        - name: %s
          mountPath: %s
`, component.Name, vct.Name, vct.MountPath)

				// Create PV for this PVC if using our custom storage class
				if strings.EqualFold(storageClass, "customer-workload-storage") {
					// Construct full PVC name as it will appear in the cluster
					fullPVCName := fmt.Sprintf("%s-%s-0", vct.Name, componentName)

					if err := c.createPersistentVolumeForClaim(ctx, fullPVCName, namespace, storageClass, vct.Size, component.Name); err != nil {
						log.Error(err, "Failed to create PersistentVolume for claim",
							"pvc", fullPVCName,
							"component", component.Name)
						// Continue despite error - the PV might be created manually
					}
				}
			}
		}

		// Apply the StatefulSet
		applyCmd := exec.Command("oc", "apply", "-f", "-")
		applyCmd.Stdin = strings.NewReader(ssYAML)
		applyOutput, applyErr := applyCmd.CombinedOutput()

		if applyErr != nil {
			log.Error(applyErr, "Failed to create StatefulSet with direct oc command",
				"namespace", namespace,
				"name", componentName,
				"output", string(applyOutput))
			return fmt.Errorf("failed to create StatefulSet: %w", applyErr)
		}

		log.Info("Created StatefulSet", "namespace", namespace, "name", componentName)
	} else {
		log.Info("StatefulSet already exists, updating if needed",
			"namespace", namespace,
			"name", componentName)

		// Trigger a rollout restart to pick up any changes (like updated env vars)
		if len(component.Env) > 0 {
			restartCmd := exec.Command("oc", "rollout", "restart", "statefulset", componentName, "-n", namespace)
			_, restartErr := restartCmd.CombinedOutput()

			if restartErr != nil {
				log.Error(restartErr, "Failed to restart StatefulSet to apply changes",
					"namespace", namespace,
					"name", componentName)
				// Continue despite the error as it's not critical
			} else {
				log.Info("Triggered StatefulSet rollout to apply changes",
					"namespace", namespace,
					"name", componentName)
			}
		}
	}

	// Create headless service for the StatefulSet if it doesn't exist
	hsCmd := exec.Command("oc", "get", "service", serviceName, "-n", namespace)
	hsOutput, hsErr := hsCmd.CombinedOutput()
	headlessServiceExists := hsErr == nil

	if !headlessServiceExists {
		log.Info("Headless service doesn't exist, will create",
			"namespace", namespace,
			"name", serviceName,
			"output", string(hsOutput))

		// Create headless service YAML
		hsYAML := fmt.Sprintf(`
apiVersion: v1
kind: Service
metadata:
  name: %s
  namespace: %s
  labels:
    app: %s
    app.kubernetes.io/managed-by: container-mom
    container.mom/deployment: %s
    container.mom/component: %s
    container.mom/service-type: headless
spec:
  selector:
    app: %s
  clusterIP: None
  ports:
  - port: 3306
    name: db
`, serviceName, namespace, componentName, parent.Name, component.Name, componentName)

		// Apply the headless service
		hsApplyCmd := exec.Command("oc", "apply", "-f", "-")
		hsApplyCmd.Stdin = strings.NewReader(hsYAML)
		hsApplyOutput, hsApplyErr := hsApplyCmd.CombinedOutput()

		if hsApplyErr != nil {
			log.Error(hsApplyErr, "Failed to create headless service with direct oc command",
				"namespace", namespace,
				"name", serviceName,
				"output", string(hsApplyOutput))
			// Continue despite error as it's not critical
		} else {
			log.Info("Created headless service", "namespace", namespace, "name", serviceName)
		}
	}

	// Create regular service for the component
	svcCmd := exec.Command("oc", "get", "service", component.Name, "-n", namespace)
	svcOutput, svcErr := svcCmd.CombinedOutput()
	serviceExists := svcErr == nil

	if !serviceExists {
		log.Info("Regular service doesn't exist, will create",
			"namespace", namespace,
			"name", component.Name,
			"output", string(svcOutput))

		// Create service YAML
		svcYAML := fmt.Sprintf(`
apiVersion: v1
kind: Service
metadata:
  name: %s
  namespace: %s
  labels:
    app: %s
    app.kubernetes.io/managed-by: container-mom
    container.mom/deployment: %s
    container.mom/component: %s
spec:
  selector:
    app: %s
  ports:
  - port: 3306
    targetPort: 3306
  type: ClusterIP
`, component.Name, namespace, componentName, parent.Name, component.Name, componentName)

		// Apply the service
		svcApplyCmd := exec.Command("oc", "apply", "-f", "-")
		svcApplyCmd.Stdin = strings.NewReader(svcYAML)
		svcApplyOutput, svcApplyErr := svcApplyCmd.CombinedOutput()

		if svcApplyErr != nil {
			log.Error(svcApplyErr, "Failed to create component service with direct oc command",
				"namespace", namespace,
				"name", component.Name,
				"output", string(svcApplyOutput))
			// Continue despite error as it's not critical
		} else {
			log.Info("Created component service", "namespace", namespace, "name", component.Name)
		}
	}

	return nil
}

// createOrUpdateComponentDeployment creates or updates a Deployment for a component
func (c *Controller) createOrUpdateComponentDeployment(ctx context.Context, parent *v1alpha1.ContainerMomDeployment,
	component v1alpha1.DeploymentComponent, componentName, namespace string, isPaused bool) error {

	log := ctrl.LoggerFrom(ctx)

	// Set replicas based on pause state
	replicas := 1
	if isPaused {
		replicas = 0
	}

	// Convert CPU and memory requests to appropriate format
	cpuRequest := "100m"
	memoryRequest := "128Mi"
	if component.Resources.CPU != "" {
		cpuRequest = component.Resources.CPU
	}
	if component.Resources.Memory != "" {
		memoryRequest = component.Resources.Memory
	}

	// Check if Deployment exists
	deployCmd := exec.Command("oc", "get", "deployment", componentName, "-n", namespace)
	deployOutput, deployErr := deployCmd.CombinedOutput()
	deploymentExists := deployErr == nil

	// Create secret for environment variables if any
	if len(component.Env) > 0 {
		log.Info("Creating secret for component environment variables",
			"namespace", namespace,
			"name", fmt.Sprintf("%s-env", componentName))

		// Build a map of environment variables
		envData := make(map[string]string)
		for _, env := range component.Env {
			if env.Name != "" {
				envData[env.Name] = env.Value
			}
		}

		// Create JSON structure for the secret
		secretJSON := fmt.Sprintf(`{
  "apiVersion": "v1",
  "kind": "Secret",
  "metadata": {
    "name": "%s-env",
    "namespace": "%s",
    "labels": {
      "app": "%s",
      "app.kubernetes.io/managed-by": "container-mom",
      "container.mom/component": "%s"
    }
  },
  "type": "Opaque",
  "stringData": {`, componentName, namespace, componentName, component.Name)

		// Add each environment variable to the secret with proper JSON escaping
		first := true
		for key, value := range envData {
			if !first {
				secretJSON += ","
			}
			// Escape JSON special characters in the value
			escapedValue := escapeJSONString(value)
			secretJSON += fmt.Sprintf(`
    "%s": "%s"`, key, escapedValue)
			first = false
		}

		// Close the JSON structure
		secretJSON += `
  }
}`

		// Apply the secret
		secretCmd := exec.Command("oc", "apply", "-f", "-")
		secretCmd.Stdin = strings.NewReader(secretJSON)
		secretOutput, secretErr := secretCmd.CombinedOutput()

		if secretErr != nil {
			log.Error(secretErr, "Failed to create secret for component environment variables",
				"namespace", namespace,
				"name", fmt.Sprintf("%s-env", componentName),
				"output", string(secretOutput))
			// We'll continue and just log the error
		} else {
			log.Info("Created secret for component environment variables",
				"namespace", namespace,
				"name", fmt.Sprintf("%s-env", componentName))
		}
	}

	if !deploymentExists {
		log.Info("Component Deployment doesn't exist, will create",
			"namespace", namespace,
			"name", componentName,
			"output", string(deployOutput))

		// Base Deployment YAML
		deployYAML := fmt.Sprintf(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: %s
  namespace: %s
  labels:
    app: %s
    app.kubernetes.io/managed-by: container-mom
    container.mom/deployment: %s
    container.mom/component: %s
spec:
  replicas: %d
  selector:
    matchLabels:
      app: %s
  template:
    metadata:
      labels:
        app: %s
    spec:
      containers:
      - name: %s
        image: %s
        resources:
          requests:
            cpu: %s
            memory: %s
          limits:
            cpu: %s
            memory: %s
`, componentName, namespace, componentName, parent.Name, component.Name, replicas,
			componentName, componentName, component.Name,
			component.Source.Image, cpuRequest, memoryRequest, cpuRequest, memoryRequest)

		// Add environment variables from secret if we have any
		if len(component.Env) > 0 {
			deployYAML += fmt.Sprintf(`        envFrom:
        - secretRef:
            name: %s-env
`, componentName)
		}

		// Apply the Deployment
		applyCmd := exec.Command("oc", "apply", "-f", "-")
		applyCmd.Stdin = strings.NewReader(deployYAML)
		applyOutput, applyErr := applyCmd.CombinedOutput()

		if applyErr != nil {
			log.Error(applyErr, "Failed to create component Deployment with direct oc command",
				"namespace", namespace,
				"name", componentName,
				"output", string(applyOutput))
			return fmt.Errorf("failed to create component Deployment: %w", applyErr)
		}

		log.Info("Created component Deployment", "namespace", namespace, "name", componentName)
	} else {
		log.Info("Component Deployment already exists, updating if needed",
			"namespace", namespace,
			"name", componentName)

		// Trigger a rollout restart to pick up any changes (like updated env vars)
		if len(component.Env) > 0 {
			restartCmd := exec.Command("oc", "rollout", "restart", "deployment", componentName, "-n", namespace)
			_, restartErr := restartCmd.CombinedOutput()

			if restartErr != nil {
				log.Error(restartErr, "Failed to restart component Deployment to apply changes",
					"namespace", namespace,
					"name", componentName)
				// Continue despite the error as it's not critical
			} else {
				log.Info("Triggered component Deployment rollout to apply changes",
					"namespace", namespace,
					"name", componentName)
			}
		}
	}

	// Create service for the component
	svcCmd := exec.Command("oc", "get", "service", component.Name, "-n", namespace)
	svcOutput, svcErr := svcCmd.CombinedOutput()
	serviceExists := svcErr == nil

	if !serviceExists {
		log.Info("Component service doesn't exist, will create",
			"namespace", namespace,
			"name", component.Name,
			"output", string(svcOutput))

		// Create service YAML
		svcYAML := fmt.Sprintf(`
apiVersion: v1
kind: Service
metadata:
  name: %s
  namespace: %s
  labels:
    app: %s
    app.kubernetes.io/managed-by: container-mom
    container.mom/deployment: %s
    container.mom/component: %s
spec:
  selector:
    app: %s
  ports:
  - port: 80
    targetPort: 8080
  type: ClusterIP
`, component.Name, namespace, componentName, parent.Name, component.Name, componentName)

		// Apply the service
		svcApplyCmd := exec.Command("oc", "apply", "-f", "-")
		svcApplyCmd.Stdin = strings.NewReader(svcYAML)
		svcApplyOutput, svcApplyErr := svcApplyCmd.CombinedOutput()

		if svcApplyErr != nil {
			log.Error(svcApplyErr, "Failed to create component service with direct oc command",
				"namespace", namespace,
				"name", component.Name,
				"output", string(svcApplyOutput))
			// Continue despite error as it's not critical
		} else {
			log.Info("Created component service", "namespace", namespace, "name", component.Name)
		}
	}

	return nil
}

// createPersistentVolumeForClaim creates a matching PV for a PVC that uses our custom storage class
func (c *Controller) createPersistentVolumeForClaim(ctx context.Context, pvcName, namespace, storageClass, size string, component string) error {
	log := ctrl.LoggerFrom(ctx)

	// Generate a shorter, more unique name without duplicating namespace
	// Create a hash of the combined PVC name and namespace to avoid duplications
	// but still maintain uniqueness and stability
	hasher := fnv.New32a()
	hasher.Write([]byte(pvcName + namespace))
	hash := fmt.Sprintf("%08x", hasher.Sum32())

	// Construct a unique but shorter PV name
	pvName := fmt.Sprintf("pv-%s-%s", component, hash)

	// Make sure name doesn't exceed 63 characters
	if len(pvName) > 63 {
		pvName = pvName[:63]
	}

	// Check if PV already exists
	pvCheckCmd := exec.Command("oc", "get", "pv", pvName)
	_, pvErr := pvCheckCmd.CombinedOutput()
	if pvErr == nil {
		log.Info("PersistentVolume already exists", "name", pvName)
		return nil
	}

	// Create a unique path on the host for this volume
	// Use structured naming to avoid duplicate paths
	hostPath := fmt.Sprintf("/var/data/container-mom/%s/%s/%s", namespace, component, strings.ReplaceAll(pvcName, "-", "_"))

	// Create directory on host if it doesn't exist
	mkdirCmd := exec.Command("oc", "exec", "-n", "container-mom-system", "container-mom-operator-0", "--", "mkdir", "-p", hostPath)
	mkdirOutput, mkdirErr := mkdirCmd.CombinedOutput()
	if mkdirErr != nil {
		log.Info("Failed to create host directory, but continuing anyway",
			"output", string(mkdirOutput),
			"path", hostPath)
		// We'll continue anyway as this might be a permission issue or the pod name might be different
	}

	// Create the PV
	pvYAML := fmt.Sprintf(`
apiVersion: v1
kind: PersistentVolume
metadata:
  name: %s
  labels:
    container.mom/managed: "true"
    container.mom/namespace: %s
    container.mom/pvc: %s
    container.mom/component: %s
spec:
  storageClassName: %s
  capacity:
    storage: %s
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: %s
  persistentVolumeReclaimPolicy: Retain
`, pvName, namespace, pvcName, component, storageClass, size, hostPath)

	// Log what we're about to do
	log.Info("Creating PersistentVolume for claim",
		"pvc", pvcName,
		"namespace", namespace,
		"pvName", pvName,
		"component", component,
		"hostPath", hostPath)

	pvCmd := exec.Command("oc", "apply", "-f", "-")
	pvCmd.Stdin = strings.NewReader(pvYAML)
	pvOutput, pvErr := pvCmd.CombinedOutput()

	if pvErr != nil {
		log.Error(pvErr, "Failed to create PersistentVolume",
			"name", pvName,
			"output", string(pvOutput))
		return fmt.Errorf("failed to create PersistentVolume: %w", pvErr)
	}

	log.Info("Created PersistentVolume", "name", pvName, "size", size, "path", hostPath)
	return nil
}

// generateComponentName generates a consistent naming convention for component deployments
func generateComponentName(deploymentName, componentName string) string {
	return fmt.Sprintf("%s-%s", deploymentName, componentName)
}
