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

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

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/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/types"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"

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

// reconcileInCluster handles deployment of resources to a specific cluster
// Moved from workload_controller.go to improve code organization
func (c *WorkloadController) 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)
		}
	}

	// Check if namespace exists and create it if not
	namespace := &corev1.Namespace{}
	err := client.Get(ctx, types.NamespacedName{Name: appNamespace}, namespace)
	if err != nil {
		if errors.IsNotFound(err) {
			// Create the namespace
			namespace = &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: appNamespace,
					Labels: map[string]string{
						"app.kubernetes.io/managed-by": "container-mom",
						"containermom.mom/owner":       deployment.Name,
					},
				},
			}
			if err := client.Create(ctx, namespace); err != nil {
				return fmt.Errorf("failed to create namespace: %w", err)
			}
			log.Info("Created namespace", "namespace", appNamespace)

			// Set up resource quotas for the namespace
			resourceManager := NewResourceManager(c)
			if err := resourceManager.SetupResourceQuota(ctx, deployment, appNamespace); err != nil {
				log.Error(err, "Failed to set up resource quota", "namespace", appNamespace)
				// Continue despite error - quotas are not critical for operation
			}

			// Update the deployment with the namespace annotation
			freshDeployment := &v1alpha1.ContainerMomDeployment{}
			if err := c.client.Get(ctx, types.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)
				}
			}
		} else {
			return fmt.Errorf("failed to check namespace: %w", err)
		}
	}

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

	// Determine if resources for this deployment exist already
	deploymentFound := false
	k8sDeploy := &appsv1.Deployment{}

	// Use the non-cached client to avoid "unknown namespace for the cache" errors
	// The client.Reader interface includes both the cached client and the direct client
	// client.Reader is implemented by both the cache client and the direct client
	err = client.Get(ctx, types.NamespacedName{Name: deployment.Name, Namespace: appNamespace}, k8sDeploy)
	if err != nil {
		if !errors.IsNotFound(err) {
			if strings.Contains(err.Error(), "unknown namespace") {
				// Use direct API client (no cache) for newly created namespaces
				log.Info("Namespace not in cache, using direct API client",
					"namespace", appNamespace,
					"name", deployment.Name)

				// Here we use APIReader which is a direct client that doesn't use the cache
				if apiClient, ok := client.(interface{ GetAPIReader() client.Reader }); ok {
					directClient := apiClient.GetAPIReader()
					err = directClient.Get(ctx, types.NamespacedName{Name: deployment.Name, Namespace: appNamespace}, k8sDeploy)
					if err == nil {
						deploymentFound = true
					} else if !errors.IsNotFound(err) {
						log.Error(err, "Failed to check for deployment using direct API client",
							"namespace", appNamespace,
							"name", deployment.Name)
					}
				} else {
					log.Error(err, "Failed to get direct client for namespace",
						"namespace", appNamespace,
						"name", deployment.Name)
				}
			} else {
				log.Error(err, "Failed to check for deployment",
					"namespace", appNamespace,
					"name", deployment.Name)
			}
		}
	} else {
		deploymentFound = true
	}

	// Decide whether to deploy or skip
	if !deploymentFound {
		log.Info("Deployment not found, creating", "namespace", appNamespace, "name", deployment.Name)

		// Convert env vars to k8s format
		envVars := convertToK8sEnvVars(deployment.Spec.Env)

		// Set up resources
		resources := corev1.ResourceRequirements{
			Requests: corev1.ResourceList{
				corev1.ResourceCPU:    resource.MustParse("100m"),
				corev1.ResourceMemory: resource.MustParse("128Mi"),
			},
			Limits: corev1.ResourceList{
				corev1.ResourceCPU:    resource.MustParse("200m"),
				corev1.ResourceMemory: resource.MustParse("256Mi"),
			},
		}

		// Convert user-specified resources if provided
		if deployment.Spec.Resources.CPU != "" && deployment.Spec.Resources.Memory != "" {
			// Try to parse CPU request
			cpuRequest, err := resource.ParseQuantity(deployment.Spec.Resources.CPU)
			if err == nil {
				resources.Requests[corev1.ResourceCPU] = cpuRequest
				resources.Limits[corev1.ResourceCPU] = cpuRequest
			}

			// Try to parse Memory request
			memRequest, err := resource.ParseQuantity(deployment.Spec.Resources.Memory)
			if err == nil {
				resources.Requests[corev1.ResourceMemory] = memRequest
				resources.Limits[corev1.ResourceMemory] = memRequest
			}
		}

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

		// Create the k8s deployment
		newDeployment := &appsv1.Deployment{
			ObjectMeta: metav1.ObjectMeta{
				Name:      deployment.Name,
				Namespace: appNamespace,
				Labels: map[string]string{
					"app":                          deployment.Name,
					"app.kubernetes.io/managed-by": "container-mom",
				},
			},
			Spec: appsv1.DeploymentSpec{
				Replicas: &replicas,
				Selector: &metav1.LabelSelector{
					MatchLabels: map[string]string{
						"app": deployment.Name,
					},
				},
				Template: corev1.PodTemplateSpec{
					ObjectMeta: metav1.ObjectMeta{
						Labels: map[string]string{
							"app": deployment.Name,
						},
					},
					Spec: corev1.PodSpec{
						Containers: []corev1.Container{
							{
								Name:            deployment.Name,
								Image:           imageName,
								Env:             envVars,
								Resources:       resources,
								ImagePullPolicy: corev1.PullIfNotPresent,
								Ports: []corev1.ContainerPort{
									{
										ContainerPort: 8080,
										Name:          "http",
									},
								},
							},
						},
					},
				},
			},
		}

		// Create the deployment
		if err := client.Create(ctx, newDeployment); err != nil {
			if errors.IsAlreadyExists(err) {
				log.Info("Deployment already exists despite not being found earlier, attempting update instead",
					"namespace", appNamespace,
					"name", deployment.Name)

				// Get the latest version of the deployment
				existingDeployment := &appsv1.Deployment{}
				if getErr := client.Get(ctx, types.NamespacedName{Name: deployment.Name, Namespace: appNamespace}, existingDeployment); getErr != nil {
					return fmt.Errorf("failed to get existing deployment after already-exists error: %w", getErr)
				}

				// Update with our desired specs
				existingDeployment.Spec.Template.Spec.Containers[0].Image = imageName
				existingDeployment.Spec.Template.Spec.Containers[0].Env = envVars
				existingDeployment.Spec.Template.Spec.Containers[0].Resources = resources

				// Set replicas based on pause state
				if isPaused {
					zero := int32(0)
					existingDeployment.Spec.Replicas = &zero
				} else {
					one := int32(1)
					existingDeployment.Spec.Replicas = &one
				}

				// Force rolling update by updating the annotations
				if existingDeployment.Spec.Template.Annotations == nil {
					existingDeployment.Spec.Template.Annotations = make(map[string]string)
				}
				existingDeployment.Spec.Template.Annotations["containermom.mom/reconciled"] = time.Now().Format(time.RFC3339)

				// Update the deployment
				if updateErr := client.Update(ctx, existingDeployment); updateErr != nil {
					return fmt.Errorf("failed to update existing deployment: %w", updateErr)
				}

				log.Info("Updated existing deployment after already-exists error",
					"namespace", appNamespace,
					"name", deployment.Name)
				return nil
			}
			return fmt.Errorf("failed to create deployment: %w", err)
		}
		log.Info("Created deployment", "namespace", appNamespace, "name", deployment.Name)
	} else if isPaused {
		// Check if we need to scale an existing deployment to 0
		if k8sDeploy.Spec.Replicas == nil || *k8sDeploy.Spec.Replicas != 0 {
			// Scale down to 0
			k8sDeployCopy := k8sDeploy.DeepCopy()
			zero := int32(0)
			k8sDeployCopy.Spec.Replicas = &zero

			if err := client.Update(ctx, k8sDeployCopy); err != nil {
				log.Error(err, "Failed to scale deployment to 0 due to pause",
					"namespace", appNamespace,
					"name", deployment.Name)
			} else {
				log.Info("Scaled deployment to 0 replicas due to pause",
					"namespace", appNamespace,
					"name", deployment.Name)
			}
		}
	} else if deploymentFound {
		// Deployment exists - check if we need to update the image
		updateNeeded := false

		// Check if the current deployment image matches the expected image
		if len(k8sDeploy.Spec.Template.Spec.Containers) > 0 {
			currentImage := k8sDeploy.Spec.Template.Spec.Containers[0].Image
			if currentImage != imageName {
				updateNeeded = true
				log.Info("Image update needed",
					"namespace", appNamespace,
					"name", deployment.Name,
					"currentImage", currentImage,
					"newImage", imageName)
			}
		}

		if updateNeeded {
			// Create a copy of the deployment to modify
			k8sDeployCopy := k8sDeploy.DeepCopy()

			// Update the container image
			if len(k8sDeployCopy.Spec.Template.Spec.Containers) > 0 {
				k8sDeployCopy.Spec.Template.Spec.Containers[0].Image = imageName

				// Add annotation to force rolling update
				if k8sDeployCopy.Spec.Template.Annotations == nil {
					k8sDeployCopy.Spec.Template.Annotations = make(map[string]string)
				}
				k8sDeployCopy.Spec.Template.Annotations["containermom.mom/image-updated"] = time.Now().Format(time.RFC3339)

				// Update the deployment
				if err := client.Update(ctx, k8sDeployCopy); err != nil {
					log.Error(err, "Failed to update deployment with new image",
						"namespace", appNamespace,
						"name", deployment.Name,
						"image", imageName)
				} else {
					log.Info("Updated deployment with new image",
						"namespace", appNamespace,
						"name", deployment.Name,
						"image", imageName)
				}
			}
		}

		// Check if the deployment is scaled to 0 and needs to be resumed
		if k8sDeploy.Spec.Replicas != nil && *k8sDeploy.Spec.Replicas == 0 && !isPaused {
			// Resume the deployment
			k8sDeployCopy := k8sDeploy.DeepCopy()
			one := int32(1)
			k8sDeployCopy.Spec.Replicas = &one

			if err := client.Update(ctx, k8sDeployCopy); err != nil {
				log.Error(err, "Failed to resume deployment",
					"namespace", appNamespace,
					"name", deployment.Name)
			} else {
				log.Info("Resumed deployment by scaling to 1 replica",
					"namespace", appNamespace,
					"name", deployment.Name)
			}
		}
	}

	// Continue with existing service and route creation logic...
	// Implement service creation and other related functionality here

	return nil
}

// buildHostname creates a hostname from a DomainSpec
// Moved from workload_controller.go to improve code organization
func buildHostname(domain v1alpha1.DomainSpec) string {
	if domain.UseWildcard {
		return "*." + domain.DomainName
	}
	if domain.Prefix != "" {
		return domain.Prefix + "." + domain.DomainName
	}
	return domain.DomainName
}

// 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
}
