package workload

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

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

// ResourceManager handles resource-related operations
type ResourceManager struct {
	controller *WorkloadController
}

// NewResourceManager creates a new resource manager
func NewResourceManager(controller *WorkloadController) *ResourceManager {
	return &ResourceManager{
		controller: controller,
	}
}

// SetupResourceQuota creates or updates resource quotas for a namespace
func (r *ResourceManager) SetupResourceQuota(ctx context.Context,
	deployment *v1alpha1.ContainerMomDeployment,
	namespace string) error {
	// Calculate the requested resources from the deployment
	cpuRequest := deployment.Spec.Resources.CPU
	memoryRequest := deployment.Spec.Resources.Memory

	// Use default values if empty
	if cpuRequest == "" {
		cpuRequest = "100m"
	}
	if memoryRequest == "" {
		memoryRequest = "128Mi"
	}

	// Parse and add operational overhead
	cpuValue, cpuUnit, err := r.parseResourceValue(cpuRequest)
	if err != nil {
		// Fall back to default CPU
		cpuValue = 100
		cpuUnit = "m"
	}

	memValue, memUnit, err := r.parseResourceValue(memoryRequest)
	if err != nil {
		// Fall back to default Memory
		memValue = 128
		memUnit = "Mi"
	}

	// Add overhead for cert-manager (10m CPU, 64Mi memory) and build processes
	const (
		certManagerCPU      = 20  // Increased from 10m to 20m
		certManagerMemory   = 128 // Increased from 64Mi to 128Mi
		buildOverheadCPU    = 50  // 50m for build processes
		buildOverheadMemory = 256 // 256Mi for build processes
	)

	// Calculate new limits with overhead
	totalCPU := cpuValue + certManagerCPU + buildOverheadCPU
	totalMemory := memValue + certManagerMemory + buildOverheadMemory

	// Format back to Kubernetes resource strings with proper units
	var totalCPUStr, totalMemoryStr string

	// For CPU: ensure consistent units and reasonable values
	switch cpuUnit {
	case "m":
		// Already in millicores, just add and keep the unit
		totalCPUStr = fmt.Sprintf("%dm", int(totalCPU))
	case "":
		// No unit - assume cores
		// Convert to millicores if less than 1, otherwise keep as cores
		if totalCPU < 1 {
			totalCPUStr = fmt.Sprintf("%dm", int(totalCPU*1000))
		} else {
			// Ensure we don't exceed reasonable values
			if totalCPU > 4 {
				totalCPU = 4 // Cap at 4 cores
			}
			totalCPUStr = fmt.Sprintf("%.2f", totalCPU)
		}
	default:
		// This is a safety check - we should only have "" or "m" units for CPU
		// Convert to proper format based on value range
		if totalCPU > 4 && cpuUnit != "m" {
			totalCPUStr = "4" // Cap at 4 cores
		} else if cpuUnit == "m" {
			totalCPUStr = fmt.Sprintf("%dm", int(totalCPU))
		} else {
			// Convert to cores with proper formatting
			totalCPUStr = fmt.Sprintf("%.2f", totalCPU)
		}
	}

	// For memory: ensure consistent units and reasonable values
	if memUnit == "Mi" {
		// If very large, convert to Gi
		if totalMemory > 8192 {
			totalMemoryStr = "8Gi"
		} else {
			totalMemoryStr = fmt.Sprintf("%dMi", int(totalMemory))
		}
	} else if memUnit == "Gi" {
		// Cap at 8Gi
		if totalMemory > 8 {
			totalMemoryStr = "8Gi"
		} else {
			totalMemoryStr = fmt.Sprintf("%.1fGi", totalMemory)
		}
	} else {
		// Default to Mi if no unit (safer assumption)
		totalMemoryStr = fmt.Sprintf("%dMi", int(totalMemory))
	}

	// Always perform a final validation to ensure the values are reasonable
	// This serves as a final safety check
	if !isReasonableValue(totalCPUStr, "cpu") {
		if strings.HasSuffix(totalCPUStr, "m") {
			totalCPUStr = "1000m" // Default to 1 core in millicores
		} else {
			totalCPUStr = "1" // Default to 1 core
		}
	}

	if !isReasonableValue(totalMemoryStr, "memory") {
		totalMemoryStr = "1Gi" // Default to 1Gi if unreasonable
	}

	// First check if quota already exists with sufficient resources
	quotaName := deployment.Name + "-quota"
	checkCmd := exec.Command("oc", "get", "resourcequota", quotaName, "-n", namespace, "-o", "json")
	checkOutput, err := checkCmd.CombinedOutput()

	// Only create/update quota if it doesn't exist or has insufficient resources
	if err != nil || !hasAdequateResources(string(checkOutput), totalCPUStr, totalMemoryStr) {
		// Create or update the resource quota
		quotaYAML := fmt.Sprintf(`
apiVersion: v1
kind: ResourceQuota
metadata:
  name: %s-quota
  namespace: %s
spec:
  hard:
    requests.cpu: %s
    requests.memory: %s
    limits.cpu: %s
    limits.memory: %s
    pods: "30"
`, deployment.Name, namespace, totalCPUStr, totalMemoryStr, totalCPUStr, totalMemoryStr)

		// Apply the quota using oc instead of kubectl for OpenShift compatibility
		cmd := exec.Command("oc", "apply", "-f", "-")
		cmd.Stdin = strings.NewReader(quotaYAML)
		output, err := cmd.CombinedOutput()
		if err != nil {
			return fmt.Errorf("failed to create resource quota: %w, output: %s", err, string(output))
		}
	}

	return nil
}

// hasAdequateResources checks if the existing quota has adequate resources
func hasAdequateResources(quotaJSON string, requiredCPU, requiredMemory string) bool {
	// Skip check if we can't parse the quota
	if quotaJSON == "" || !strings.Contains(quotaJSON, "hard") {
		return false
	}

	// Extract hard limits from JSON
	hardSection := ""
	if idx := strings.Index(quotaJSON, `"hard":`); idx > 0 {
		hardSection = quotaJSON[idx:]
		if endIdx := strings.Index(hardSection, "}"); endIdx > 0 {
			hardSection = hardSection[:endIdx]
		}
	}

	// Check for CPU and Memory resources
	// Parse required values for comparison
	reqCPUVal, reqCPUUnit, err := parseResourceStringForCheck(requiredCPU)
	if err != nil {
		return false
	}

	reqMemVal, reqMemUnit, err := parseResourceStringForCheck(requiredMemory)
	if err != nil {
		return false
	}

	// Check if values in JSON appear adequate (simple check)
	cpuAdequate := hasAdequateResource(hardSection, "cpu", reqCPUVal, reqCPUUnit)
	memAdequate := hasAdequateResource(hardSection, "memory", reqMemVal, reqMemUnit)

	return cpuAdequate && memAdequate
}

// parseResourceStringForCheck parses a resource string into value and unit
func parseResourceStringForCheck(resource string) (float64, string, error) {
	// Common units
	units := []string{"m", "Mi", "Gi", "Ki"}

	// Find which unit is used
	var numStr string
	var unit string

	for _, u := range units {
		if strings.HasSuffix(resource, u) {
			unit = u
			numStr = resource[:len(resource)-len(u)]
			break
		}
	}

	// If no unit found, assume it's a raw number
	if unit == "" {
		numStr = resource
	}

	// Parse the number part
	num, err := strconv.ParseFloat(numStr, 64)
	if err != nil {
		return 0, "", fmt.Errorf("failed to parse resource value %s: %w", resource, err)
	}

	return num, unit, nil
}

// hasAdequateResource checks if a specific resource has adequate quota
func hasAdequateResource(hardSection, resourceType string, requiredVal float64, requiredUnit string) bool {
	// Look for both requests and limits
	requestKey := fmt.Sprintf(`"requests.%s"`, resourceType)
	limitKey := fmt.Sprintf(`"limits.%s"`, resourceType)

	// Find the values in the JSON
	reqIdx := strings.Index(hardSection, requestKey)
	limIdx := strings.Index(hardSection, limitKey)

	// Extract the actual value from hardSection if present
	var valueFound bool
	var actualVal float64

	// Simple value extraction - this is a basic implementation
	// In a production system, use proper JSON parsing
	if reqIdx > 0 || limIdx > 0 {
		// Find the number after the key
		valueStartIdx := -1
		if reqIdx > 0 {
			valueStartIdx = reqIdx + len(requestKey) + 2 // Skip past the key and ": "
		} else if limIdx > 0 {
			valueStartIdx = limIdx + len(limitKey) + 2
		}

		if valueStartIdx > 0 && valueStartIdx < len(hardSection) {
			// Extract the number part
			endIdx := strings.IndexAny(hardSection[valueStartIdx:], ",}")
			if endIdx > 0 {
				numStr := strings.Trim(hardSection[valueStartIdx:valueStartIdx+endIdx], "\" ")
				// Remove unit if present
				for _, u := range []string{requiredUnit, "m", "Mi", "Gi", "Ki"} {
					numStr = strings.TrimSuffix(numStr, u)
				}

				// Try to parse the value
				parsedVal, err := strconv.ParseFloat(numStr, 64)
				if err == nil {
					valueFound = true
					actualVal = parsedVal
				}
			}
		}
	}

	// Check if we found a value and it's sufficient
	if valueFound {
		// Simple comparison - just check if actual >= required
		// For more complex comparison, would need to convert units
		return actualVal >= requiredVal && strings.Contains(hardSection, requiredUnit)
	}

	// Fall back to simple check if we couldn't extract the value properly
	return (reqIdx > 0 || limIdx > 0) && strings.Contains(hardSection, requiredUnit)
}

// parseResourceValue parses Kubernetes resource values like "200m" or "1Gi"
func (r *ResourceManager) parseResourceValue(value string) (float64, string, error) {
	// Handle empty string case
	if value == "" {
		return 0, "", fmt.Errorf("resource value cannot be empty")
	}

	// Common units
	units := []string{"m", "Mi", "Gi", "Ki"}

	// Find which unit is used
	var numStr string
	var unit string

	for _, u := range units {
		if strings.HasSuffix(value, u) {
			unit = u
			numStr = value[:len(value)-len(u)]
			break
		}
	}

	// If no unit found, assume it's a raw number
	if unit == "" {
		numStr = value
	}

	// Parse the number part
	num, err := strconv.ParseFloat(numStr, 64)
	if err != nil {
		return 0, "", fmt.Errorf("failed to parse resource value %s: %w", value, err)
	}

	return num, unit, nil
}

// TemporarilyIncreaseCertManagerQuota increases quota specifically for cert-manager operations
func (r *ResourceManager) TemporarilyIncreaseCertManagerQuota(ctx context.Context, namespace string) error {
	// Create a temporary increase to handle cert-manager needs
	quotaYAML := fmt.Sprintf(`
apiVersion: v1
kind: ResourceQuota
metadata:
  name: cert-manager-quota
  namespace: %s
spec:
  hard:
    requests.cpu: "500m"
    requests.memory: "3Gi"
    limits.cpu: "500m"
    limits.memory: "3Gi"
    pods: "50"
`, namespace)

	// Apply the temporary quota using oc command for OpenShift compatibility
	cmd := exec.Command("oc", "apply", "-f", "-")
	cmd.Stdin = strings.NewReader(quotaYAML)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to create temporary cert-manager quota: %w, output: %s", err, string(output))
	}

	return nil
}

// PatchExistingQuota patches an existing quota to increase resources
func (r *ResourceManager) PatchExistingQuota(ctx context.Context, namespace, quotaName string, cpuRequest, memoryRequest string) error {
	// Validate the CPU and memory values to ensure they're reasonable
	if !isReasonableValue(cpuRequest, "cpu") {
		return fmt.Errorf("CPU request value '%s' seems unreasonable", cpuRequest)
	}
	if !isReasonableValue(memoryRequest, "memory") {
		return fmt.Errorf("memory request value '%s' seems unreasonable", memoryRequest)
	}

	// Create the patch JSON
	patchJSON := fmt.Sprintf(`{"spec":{"hard":{"requests.cpu":"%s","requests.memory":"%s","limits.cpu":"%s","limits.memory":"%s","pods":"30"}}}`,
		cpuRequest, memoryRequest, cpuRequest, memoryRequest)

	// Apply the patch using oc patch
	cmd := exec.Command("oc", "patch", "resourcequota", quotaName,
		"-n", namespace,
		"--type=merge",
		"-p", patchJSON)

	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to patch resource quota: %w, output: %s", err, string(output))
	}

	return nil
}

// isReasonableValue checks if a resource value is within reasonable limits
func isReasonableValue(value, resourceType string) bool {
	// Convert value to a number if possible
	numStr := value
	for _, suffix := range []string{"m", "Mi", "Gi", "Ki"} {
		numStr = strings.TrimSuffix(numStr, suffix)
	}

	num, err := strconv.ParseFloat(numStr, 64)
	if err != nil {
		return false
	}

	if resourceType == "cpu" {
		// If using millicores (e.g., 500m)
		if strings.HasSuffix(value, "m") {
			// Max 4000m (4 cores) seems reasonable
			return num > 0 && num <= 4000
		}
		// If using cores (e.g., 2)
		return num > 0 && num <= 4
	} else if resourceType == "memory" {
		// If using Mi
		if strings.HasSuffix(value, "Mi") {
			// Max 8192Mi (8Gi) seems reasonable
			return num > 0 && num <= 8192
		}
		// If using Gi
		if strings.HasSuffix(value, "Gi") {
			// Max 8Gi seems reasonable
			return num > 0 && num <= 8
		}
	}

	// Default for unknown resource types
	return true
}

// Other resource-related methods...
