// Package operator implements the core container-mom operator functionality
package operator

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/google/uuid"
	v1alpha1 "github.com/pfeifferj/container-mom-go/pkg/apis/containermom/v1alpha1"
	"github.com/pfeifferj/container-mom-go/pkg/billing"
	appconfig "github.com/pfeifferj/container-mom-go/pkg/config"
	"github.com/pfeifferj/container-mom-go/pkg/controllers/template"
	"github.com/pfeifferj/container-mom-go/pkg/interfaces"
	"github.com/pfeifferj/container-mom-go/pkg/operator/cluster"
	"github.com/pfeifferj/container-mom-go/pkg/operator/health"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/cache"
	"sigs.k8s.io/controller-runtime/pkg/client"
	ctrlconfig "sigs.k8s.io/controller-runtime/pkg/client/config"
	"sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/manager"
)

var scheme = runtime.NewScheme()

func init() {
	// Add the required schemes in order
	if err := clientgoscheme.AddToScheme(scheme); err != nil {
		panic(fmt.Sprintf("failed to add schemes: %v", err))
	}

	// Add our custom types
	gv := v1alpha1.SchemeGroupVersion
	scheme.AddKnownTypes(gv,
		&v1alpha1.ContainerMomDeployment{},
		&v1alpha1.ContainerMomDeploymentList{},
		&v1alpha1.Template{},
		&v1alpha1.TemplateList{},
	)
	metav1.AddToGroupVersion(scheme, gv)

	// Register scheme
	_ = v1alpha1.AddToScheme(scheme)
}

// TemplateController defines the interface for template controllers
type TemplateController interface {
	InstantiateTemplate(ctx context.Context, templateName string, namespace string, params map[string]string) error
}

// DeploymentControllerInterface defines the interface for deployment controllers
type DeploymentControllerInterface interface {
	Start(ctx context.Context) error
	reconcileDeployment(ctx context.Context, deploymentID string) error
	EnqueueDeployment(deploymentID string)
}

// Operator manages container-mom resources and operations
type Operator struct {
	config             *appconfig.Config
	billing            *billing.Service
	controller         DeploymentControllerInterface
	client             client.Client
	templateController TemplateController
	// Add other fields as needed
}

// ContainerStatus represents the current state of a container
type ContainerStatus struct {
	Name   string
	Status string
	// Add other status fields as needed
}

var _ interfaces.OperatorInterface = (*Operator)(nil) // Verify interface implementation

// New creates a new operator without MongoDB connection
func New(cfg *appconfig.Config, billingService *billing.Service) (*Operator, error) {
	// No longer need to check for MongoDB URI

	// Initialize Kubernetes client
	k8sConfig := ctrlconfig.GetConfigOrDie()
	k8sClient, err := client.New(k8sConfig, client.Options{
		Scheme: scheme,
	})
	if err != nil {
		ctrl.Log.Error(err, "failed to create kubernetes client")
		return nil, err
	}

	// Create an operator without initializing the controller yet
	return &Operator{
		config:  cfg,
		billing: billingService,
		client:  k8sClient,
	}, nil
}

// Run starts the operator and blocks until the context is cancelled
func (o *Operator) Run(ctx context.Context) error {
	ctrl := log.FromContext(ctx)
	ctrl.Info("initializing operator")

	if o == nil {
		return fmt.Errorf("operator not initialized")
	}

	// Create a context that can be cancelled
	ctx, cancel := context.WithCancel(ctx)
	defer func() {
		cancel()
		// Remove MongoDB disconnect code
	}()

	// Create a channel to signal HTTP server startup
	serverReady := make(chan struct{})

	// Initialize deployment controller - update to use only Kubernetes CRs
	reconciler := NewDeploymentReconciler(o.client)
	o.controller = NewDeploymentController(nil, reconciler, o.client) // Pass nil for MongoDB

	// Register health check handlers
	healthHandler := &health.Handler{Operator: o}
	mux := http.NewServeMux()
	mux.HandleFunc("/healthz", healthHandler.Healthz)
	mux.HandleFunc("/readyz", healthHandler.Readyz)

	server := &http.Server{
		Addr:    o.config.ListenAddr,
		Handler: mux,
	}

	// Start HTTP server
	go func() {
		ctrl.Info("starting HTTP server", "addr", o.config.ListenAddr)
		// Signal that we're about to start listening
		close(serverReady)
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			ctrl.Error(err, "HTTP server failed")
			// Cancel context on server failure
			cancel()
		}
	}()

	// Wait for server to start listening
	select {
	case <-serverReady:
		ctrl.Info("HTTP server is ready")
	case <-ctx.Done():
		return fmt.Errorf("context cancelled before server could start")
	}

	// ==================== PHASE 1: Start Deployment Controller ====================
	// Start deployment controller in a separate goroutine to avoid blocking
	deployCtrReady := make(chan struct{})
	deployCtrErr := make(chan error, 1)

	ctrl.Info("============ STARTING DEPLOYMENT CONTROLLER ============")
	go func() {
		// Initialize the deployment controller
		if err := o.controller.Start(ctx); err != nil {
			ctrl.Error(err, "failed to start deployment controller")
			deployCtrErr <- fmt.Errorf("failed to start deployment controller: %w", err)
			return
		}
		// Signal that deployment controller has started
		close(deployCtrReady)
	}()

	// Wait briefly to ensure deployment controller is starting up
	select {
	case err := <-deployCtrErr:
		return err
	case <-deployCtrReady:
		ctrl.Info("Deployment controller start function returned successfully")
	case <-time.After(2 * time.Second): // Increased from 500ms to 2s for better initialization
		// Continue anyway after a short timeout
		ctrl.Info("Continuing with workload controller setup (deployment controller initialization in progress)")
	}

	// ==================== PHASE 2: Start Workload Controller ====================
	// THIS IS CRITICAL - We must start the workload controller to create namespaces
	ctrl.Info("============ STARTING WORKLOAD CONTROLLER ============")

	// Create channels to track workload controller initialization
	workloadReady := make(chan struct{})
	workloadError := make(chan error, 1)

	// Launch the workload controller in a separate goroutine
	go func() {
		ctrl.Info("Workload controller goroutine has started")

		// Create manager with explicit namespace
		ctrl.Info("Creating controller manager")
		syncDuration := 10 * time.Second
		// We need to get the current namespace where the operator is running
		operatorNamespace := os.Getenv("OPERATOR_NAMESPACE")
		if operatorNamespace == "" {
			operatorNamespace = "container-mom-system" // Default namespace
		}

		// Create manager with cache options - don't start the cache yet
		mgr, err := manager.New(ctrlconfig.GetConfigOrDie(), manager.Options{
			Scheme:                 scheme,
			LeaderElection:         false,
			HealthProbeBindAddress: ":8088",
			Cache: cache.Options{
				SyncPeriod: &syncDuration,
				DefaultNamespaces: map[string]cache.Config{
					operatorNamespace: {},
				},
			},
			// Set graceful shutdown timeout
			GracefulShutdownTimeout: &syncDuration,
		})
		if err != nil {
			ctrl.Error(err, "failed to create manager")
			workloadError <- fmt.Errorf("failed to create manager: %w", err)
			close(workloadReady)
			return
		}
		ctrl.Info("Controller manager created successfully")

		// Load cluster configurations from environment (not using secrets to avoid cache access)
		clusterConfigs, err := cluster.LoadClustersFromEnv()
		if err != nil {
			ctrl.Error(err, "failed to load cluster configurations from environment")
			workloadError <- fmt.Errorf("failed to load cluster configurations: %w", err)
			close(workloadReady)
			return
		}

		// Print configured clusters for debugging
		clusterNames := make([]string, 0, len(clusterConfigs))
		for clusterName := range clusterConfigs {
			clusterNames = append(clusterNames, clusterName)
			ctrl.Info("Loaded cluster configuration", "cluster", clusterName)
		}

		ctrl.Info("Loaded all cluster configurations", "clusters", strings.Join(clusterNames, ", "))

		// Create the workload controller with cluster configs BEFORE starting the manager
		ctrl.Info("Creating and initializing workload controller")
		workloadController, err := NewWorkloadController(mgr, clusterConfigs)
		if err != nil {
			ctrl.Error(err, "failed to create workload controller")
			workloadError <- fmt.Errorf("failed to create workload controller: %w", err)
			close(workloadReady)
			return
		}
		ctrl.Info("Workload controller created successfully")

		// Set up controller for Template resources BEFORE starting the manager
		templateReconciler := &template.TemplateReconciler{
			Client: mgr.GetClient(),
			Scheme: mgr.GetScheme(),
		}

		// Store the template controller reference for future use
		o.templateController = templateReconciler

		// Set up the Template controller BEFORE starting the manager
		ctrl.Info("Setting up Template controller with manager")
		if err := SetupTemplateController(mgr); err != nil {
			ctrl.Error(err, "Failed to set up template controller with manager")
			workloadError <- fmt.Errorf("failed to set up template controller with manager: %w", err)
			close(workloadReady)
			return
		}
		ctrl.Info("Template controller initialized successfully")

		// Register a startup hook to run dangling namespace cleanup after cache is fully initialized
		if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error {
			// Wait a bit to ensure cache is fully synced
			time.Sleep(5 * time.Second)

			// Now it's safe to run the cleanup as the cache is started
			ctrl.Info("Running cleanup of dangling wkl- namespaces with initialized cache")
			if err := workloadController.CleanupDanglingNamespaces(ctx); err != nil {
				ctrl.Error(err, "error during dangling namespace cleanup", "error", err)
				// Continue even if cleanup fails - this is a best-effort operation
			}
			ctrl.Info("Completed dangling namespaces cleanup")
			return nil
		})); err != nil {
			ctrl.Error(err, "Failed to register dangling namespace cleanup hook")
			workloadError <- fmt.Errorf("failed to register cleanup hook: %w", err)
			close(workloadReady)
			return
		}

		// Signal that workload controller is ready BEFORE starting the manager
		close(workloadReady)

		// IMPORTANT: Start the manager which will also start the cache
		// This call blocks until ctx is canceled or the manager returns an error
		ctrl.Info("Starting manager (this blocks until shutdown)")
		if err := mgr.Start(ctx); err != nil {
			ctrl.Error(err, "failed to start manager")
			// No need to send to workloadError since we've already signaled readiness
		}
		ctrl.Info("Manager stopped gracefully")
	}()

	// Wait for workload controller to be ready or error out
	select {
	case <-workloadReady:
		ctrl.Info("Workload controller initialization completed successfully")
	case err := <-workloadError:
		ctrl.Error(err, "failed to initialize workload controller")
		// We'll continue with basic functionality even if workload controller fails
	case <-ctx.Done():
		return fmt.Errorf("context cancelled before workload controller could initialize")
	}

	// Now wait for context cancellation
	<-ctx.Done()

	// Graceful shutdown
	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer shutdownCancel()

	if err := server.Shutdown(shutdownCtx); err != nil {
		ctrl.Error(err, "failed to shutdown HTTP server")
	}
	return nil
}

// GetCurrentCustomer returns the current customer information
func (o *Operator) GetCurrentCustomer(_ context.Context) (*v1alpha1.Customer, error) {
	// TODO: Implement customer retrieval with context
	return &v1alpha1.Customer{
		ObjectMeta: metav1.ObjectMeta{
			Name: "default",
		},
	}, nil
}

// GetResourceUsage returns resource usage for a customer
func (o *Operator) GetResourceUsage(_ context.Context, _ string) (*v1alpha1.ResourceUsage, error) {
	// TODO: Implement resource usage tracking with context
	return &v1alpha1.ResourceUsage{}, nil
}

// GetBillingHistory returns billing history for a customer
func (o *Operator) GetBillingHistory(_ context.Context, _ string) ([]*v1alpha1.BillingRecord, error) {
	// TODO: Implement billing history retrieval with context
	return []*v1alpha1.BillingRecord{}, nil
}

// UpdateBillingPlan updates a customer's billing plan
func (o *Operator) UpdateBillingPlan(_ context.Context, _, _ string) error {
	// TODO: Implement billing plan updates with context
	return nil
}

// UpdateBillingStatus updates a customer's billing status
func (o *Operator) UpdateBillingStatus(_ context.Context, _ string, _ v1alpha1.BillingStatus) error {
	// TODO: Implement billing status updates with context
	return nil
}

// GetContainers returns all container statuses
func (o *Operator) GetContainers(_ context.Context) ([]ContainerStatus, error) {
	// TODO: Implement container status retrieval with context
	return []ContainerStatus{}, nil
}

// TriggerDeploymentReconcile triggers a reconciliation for a specific deployment
func (o *Operator) TriggerDeploymentReconcile(ctx context.Context, deploymentID string) error {
	// Check if controller is initialized
	if o.controller == nil {
		return fmt.Errorf("deployment controller not initialized")
	}

	// Check if client is initialized
	if o.client == nil {
		return fmt.Errorf("kubernetes client not initialized")
	}

	// First check if the CR exists
	cr := &v1alpha1.ContainerMomDeployment{}
	err := o.client.Get(ctx, types.NamespacedName{
		Name:      deploymentID,
		Namespace: "container-mom-system",
	}, cr)
	if err != nil {
		return fmt.Errorf("failed to find CR %s: %w", deploymentID, err)
	}

	ctrl.Log.Info("Manually triggering reconciliation for deployment", "deployment", deploymentID)

	// Force reconciliation through the controller
	// First manually reconcile the deployment
	if err := o.controller.reconcileDeployment(ctx, deploymentID); err != nil {
		return fmt.Errorf("failed to reconcile deployment: %w", err)
	}

	// Then ensure it's in the queue for any follow-up processing
	o.controller.EnqueueDeployment(deploymentID)
	return nil
}

// GetMongoDB is no longer needed but kept for backwards compatibility with interface
func (o *Operator) GetMongoDB() interface{} {
	// Return nil as MongoDB is no longer used
	return nil
}

// triggerReconciliationForAllCRs finds all ContainerMomDeployment CRs and ensures
// they will be reconciled, particularly to create namespaces for those without annotations
// nolint:unused // This function is kept for future use when bulk reconciliation is needed
func triggerReconciliationForAllCRs(ctx context.Context, c client.Client) error {
	log := ctrl.LoggerFrom(ctx)

	// List all ContainerMomDeployment CRs
	var crList v1alpha1.ContainerMomDeploymentList
	if err := c.List(ctx, &crList); err != nil {
		log.Error(err, "Failed to list ContainerMomDeployment CRs")
		return fmt.Errorf("failed to list ContainerMomDeployment CRs: %w", err)
	}

	// Log the number of CRs found
	log.Info("Found existing ContainerMomDeployment CRs for namespace processing", "count", len(crList.Items))

	if len(crList.Items) == 0 {
		log.Info("No CRs found to process for namespaces")
		return nil
	}

	// Count stats for logging
	var withNamespace, withoutNamespace, updateSuccess, updateFailed int

	// Check each CR for the namespace annotation
	for _, cr := range crList.Items {
		log.Info("Checking CR for namespace annotation",
			"name", cr.Name,
			"namespace", cr.Namespace,
			"annotations", fmt.Sprintf("%v", cr.Annotations))

		hasNamespace := false
		if cr.Annotations != nil {
			if ns, ok := cr.Annotations["containermom.mom/namespace"]; ok && ns != "" {
				hasNamespace = true
				withNamespace++
				log.Info("CR already has namespace annotation - skipping namespace creation",
					"name", cr.Name,
					"namespace", cr.Namespace,
					"appNamespace", ns)
				continue // Skip CRs that already have a namespace
			}
		}

		if !hasNamespace {
			withoutNamespace++
			log.Info("CR does not have namespace annotation - will trigger reconciliation",
				"name", cr.Name,
				"namespace", cr.Namespace)

			// Create copy and add annotation to trigger reconciliation
			crCopy := cr.DeepCopy()

			// Initialize annotations if needed
			if crCopy.Annotations == nil {
				crCopy.Annotations = make(map[string]string)
			}

			// Initialize labels if needed
			if crCopy.Labels == nil {
				crCopy.Labels = make(map[string]string)
			}

			// Add an annotation with timestamp to force reconciliation
			triggerTime := time.Now().Format(time.RFC3339)
			crCopy.Annotations["containermom.mom/reconcile-trigger"] = triggerTime

			// Also set a unique label to guarantee the controller sees the update
			// This is important because controllers may not be watching annotation changes
			forceUpdateID := fmt.Sprintf("force-update-%d", time.Now().UnixNano())
			crCopy.Labels["containermom.mom/force-update"] = forceUpdateID

			// CRITICAL: Define clusters if missing - default to local
			if len(crCopy.Spec.Clusters) == 0 {
				crCopy.Spec.Clusters = []string{"local"}
				log.Info("Added default 'local' cluster to CR with missing clusters",
					"name", cr.Name)
			}

			// Update the CR to trigger reconciliation
			log.Info("Updating CR with reconcile trigger and force-update label",
				"name", cr.Name,
				"namespace", cr.Namespace,
				"triggerTime", triggerTime,
				"forceUpdateID", forceUpdateID)

			if err := c.Update(ctx, crCopy); err != nil {
				updateFailed++
				log.Error(err, "Failed to update CR with reconcile trigger",
					"name", cr.Name,
					"namespace", cr.Namespace)
				continue
			}

			updateSuccess++
			log.Info("Successfully triggered reconciliation for CR",
				"name", cr.Name,
				"namespace", cr.Namespace)

			// Sleep briefly to let the controller process this update
			// before moving to the next one
			time.Sleep(500 * time.Millisecond)
		}
	}

	// Report final stats
	log.Info("Namespace reconciliation trigger complete",
		"totalCRs", len(crList.Items),
		"withNamespace", withNamespace,
		"withoutNamespace", withoutNamespace,
		"updatesSucceeded", updateSuccess,
		"updatesFailed", updateFailed)

	return nil
}

// InstantiateTemplate creates a deployment from a template
func (o *Operator) InstantiateTemplate(ctx context.Context, templateName string, namespace string, params map[string]string) error {
	log := ctrl.LoggerFrom(ctx)

	if o.client == nil {
		return fmt.Errorf("kubernetes client not initialized")
	}

	if o.templateController == nil {
		return fmt.Errorf("template controller not initialized")
	}

	log.Info("Starting template instantiation", "template", templateName, "namespace", namespace)

	// Fetch the template
	template := &v1alpha1.Template{}
	if err := o.client.Get(ctx, types.NamespacedName{
		Name:      templateName,
		Namespace: namespace,
	}, template); err != nil {
		return fmt.Errorf("failed to get template %s in namespace %s: %w", templateName, namespace, err)
	}

	// Create unique deployment name based on template name
	uniqueID := uuid.New()
	deploymentID := fmt.Sprintf("%s-%s", templateName, uniqueID.String()[0:8])

	// Process parameters for substitution
	processedParams := make(map[string]string)
	for k, v := range params {
		processedParams[k] = v
	}

	// Fill in default values for missing parameters
	for _, param := range template.Spec.Parameters {
		if _, exists := processedParams[param.Name]; !exists && param.DefaultValue != "" {
			processedParams[param.Name] = param.DefaultValue
		}
	}

	// Create a deployment from the template
	deployment := &v1alpha1.ContainerMomDeployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      deploymentID,
			Namespace: namespace,
			Annotations: map[string]string{
				"container.mom/template":        template.Name,
				"container.mom/template-source": fmt.Sprintf("%s/%s", template.Namespace, template.Name),
			},
		},
		Spec: v1alpha1.ContainerMomDeploymentSpec{
			// Default to local cluster
			Clusters: []string{"local"},
			Resources: v1alpha1.ResourceRequirements{
				CPU:    "100m",
				Memory: "128Mi",
			},
			// Default image, will be overridden if template specifies one
			Source: v1alpha1.SourceSpec{
				Image: "nginx:latest", // Use a real default image instead of placeholder
			},
		},
	}

	// Apply template components
	if len(template.Spec.Components) > 0 {
		// Find the primary component (usually deployment type)
		primaryComponent := findPrimaryComponent(template)

		if primaryComponent != nil {
			log.Info("Found primary component in template",
				"name", primaryComponent.Name,
				"type", primaryComponent.Type,
				"image", primaryComponent.Properties.Image)

			// Apply image from template
			if primaryComponent.Properties.Image != "" {
				// Substitute parameters in the image string if necessary
				processedImage := substituteParamsInString(primaryComponent.Properties.Image, processedParams)
				deployment.Spec.Source.Image = processedImage
				log.Info("Using image from template",
					"raw_image", primaryComponent.Properties.Image,
					"processed_image", processedImage)

				// Add the primary component type as an annotation
				deployment.Annotations["container.mom/primary-component"] = primaryComponent.Name
			} else {
				log.Info("Primary component has no image defined, using default",
					"default_image", deployment.Spec.Source.Image)
			}

			// Apply resources from template
			if primaryComponent.Properties.Resources.CPU != "" {
				deployment.Spec.Resources.CPU = primaryComponent.Properties.Resources.CPU
				log.Info("Using CPU from template", "cpu", deployment.Spec.Resources.CPU)
			}
			if primaryComponent.Properties.Resources.Memory != "" {
				deployment.Spec.Resources.Memory = primaryComponent.Properties.Resources.Memory
				log.Info("Using memory from template", "memory", deployment.Spec.Resources.Memory)
			}

			// Apply environment variables from template
			if len(primaryComponent.Properties.Env) > 0 {
				deployment.Spec.Env = substituteParams(primaryComponent.Properties.Env, processedParams)
				log.Info("Applied environment variables from template",
					"count", len(deployment.Spec.Env),
					"first_few", fmt.Sprintf("%+v", deployment.Spec.Env[:min(3, len(deployment.Spec.Env))]))
			}
		} else {
			log.Info("No primary component found in template, using default configuration")
		}

		// Add the template parameters as annotations for reference and debugging
		for key, value := range processedParams {
			deployment.Annotations[fmt.Sprintf("container.mom/template-param-%s", key)] = value
		}

		// Apply domain configuration if present in the template
		if template.Spec.NetworkConfig.Domain.DomainName != "" {
			domainName := substituteParamsInString(template.Spec.NetworkConfig.Domain.DomainName, processedParams)
			deployment.Spec.Domain = v1alpha1.DomainSpec{
				DomainName:  domainName,
				UseWildcard: template.Spec.NetworkConfig.Domain.UseWildcard,
			}
			log.Info("Applied domain configuration from template", "domain", domainName)
		}
	}

	// Create the deployment
	log.Info("Creating deployment from template",
		"deployment", deploymentID,
		"image", deployment.Spec.Source.Image)

	if err := o.client.Create(ctx, deployment); err != nil {
		return fmt.Errorf("failed to create deployment: %w", err)
	}

	// Update template usage count
	template.Status.UsageCount++
	template.Status.LastUpdated = metav1.Time{Time: time.Now()}
	if err := o.client.Status().Update(ctx, template); err != nil {
		log.Error(err, "Failed to update template usage count", "template", templateName)
		// Non-fatal error, continue
	}

	log.Info("Template instantiated successfully", "template", templateName, "deployment", deploymentID)
	return nil
}

// findPrimaryComponent looks for a component with type "deployment"
// For WordPress templates, prioritize finding the 'wordpress' component
func findPrimaryComponent(template *v1alpha1.Template) *v1alpha1.TemplateComponent {
	log := ctrl.Log.WithName("template")

	// First, log all components for debugging
	for i, component := range template.Spec.Components {
		log.Info("Template component",
			"index", i,
			"name", component.Name,
			"type", component.Type,
			"image", component.Properties.Image)
	}

	// For WordPress templates, prioritize the 'wordpress' component
	if template.Name == "wordpress" {
		for i, component := range template.Spec.Components {
			if strings.EqualFold(component.Type, "deployment") &&
				strings.EqualFold(component.Name, "wordpress") {
				log.Info("Found WordPress primary component",
					"name", component.Name,
					"image", component.Properties.Image)
				return &template.Spec.Components[i]
			}
		}
	}

	// For other templates or if WordPress component not found, return the first deployment
	for i, component := range template.Spec.Components {
		if strings.EqualFold(component.Type, "deployment") {
			log.Info("Found primary deployment component",
				"name", component.Name,
				"image", component.Properties.Image)
			return &template.Spec.Components[i]
		}
	}

	log.Info("No deployment component found in template")
	return nil
}

// substituteParams replaces parameter placeholders in environment variables
func substituteParams(envVars []v1alpha1.EnvVar, params map[string]string) []v1alpha1.EnvVar {
	result := make([]v1alpha1.EnvVar, len(envVars))
	for i, env := range envVars {
		result[i] = v1alpha1.EnvVar{
			Name:  env.Name,
			Value: substituteParamsInString(env.Value, params),
		}
	}
	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.Replace(result, placeholder, value, -1)
	}
	return result
}

// Helper function to compute minimum of two integers
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
