package convert

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"

	"github.com/pkg/errors"
	"gopkg.in/yaml.v2"
)

// V1Beta2ToV1Beta3Result holds the conversion results
type V1Beta2ToV1Beta3Result struct {
	TemplatedSpec string                 `yaml:"-"`
	ValuesFile    string                 `yaml:"-"`
	Values        map[string]interface{} `yaml:"-"`
}

// ConvertToV1Beta3 converts a v1beta2 preflight spec to v1beta3 format with templating
func ConvertToV1Beta3(doc []byte) (*V1Beta2ToV1Beta3Result, error) {
	var parsed map[string]interface{}
	err := yaml.Unmarshal(doc, &parsed)
	if err != nil {
		return nil, errors.Wrap(err, "failed to unmarshal yaml")
	}

	// Check if it's already v1beta3
	if apiVersion, ok := parsed["apiVersion"]; ok && apiVersion == "troubleshoot.sh/v1beta3" {
		return nil, errors.New("document is already v1beta3")
	}

	// Check if it's v1beta2
	if apiVersion, ok := parsed["apiVersion"]; !ok || apiVersion != "troubleshoot.sh/v1beta2" {
		return nil, errors.Errorf("unsupported apiVersion: %v", apiVersion)
	}

	// Check if it's a preflight spec
	if kind, ok := parsed["kind"]; !ok || kind != "Preflight" {
		return nil, errors.Errorf("unsupported kind: %v", kind)
	}

	// Extract values and create templated spec
	values := make(map[string]interface{})
	converter := &v1beta3Converter{
		values: values,
		spec:   parsed,
	}

	templatedSpec, err := converter.convert()
	if err != nil {
		return nil, errors.Wrap(err, "failed to convert spec")
	}

	// Marshal values
	valuesBytes, err := yaml.Marshal(values)
	if err != nil {
		return nil, errors.Wrap(err, "failed to marshal values")
	}

	return &V1Beta2ToV1Beta3Result{
		TemplatedSpec: templatedSpec,
		ValuesFile:    string(valuesBytes),
		Values:        values,
	}, nil
}

type v1beta3Converter struct {
	values map[string]interface{}
	spec   map[string]interface{}
}

func (c *v1beta3Converter) convert() (string, error) {
	// Initialize values structure
	c.initializeValues()

	// Get metadata name
	metadataName := "converted-from-v1beta2"
	if metadata, ok := c.spec["metadata"].(map[interface{}]interface{}); ok {
		if name, ok := metadata["name"].(string); ok {
			metadataName = name
		}
	}

	// Process spec
	var analyzers []interface{}
	if spec, ok := c.spec["spec"].(map[interface{}]interface{}); ok {
		if analyzersList, ok := spec["analyzers"].([]interface{}); ok {
			convertedAnalyzers, err := c.convertAnalyzers(analyzersList)
			if err != nil {
				return "", errors.Wrap(err, "failed to convert analyzers")
			}
			analyzers = convertedAnalyzers
		}
	}

	// Build the templated spec string
	var buf bytes.Buffer

	// Header
	buf.WriteString("apiVersion: troubleshoot.sh/v1beta3\n")
	buf.WriteString("kind: Preflight\n")
	buf.WriteString("metadata:\n")
	buf.WriteString(fmt.Sprintf("  name: %s\n", metadataName))
	buf.WriteString("spec:\n")
	buf.WriteString("  analyzers:\n")

	// Add each analyzer
	for _, analyzer := range analyzers {
		if analyzerStr, ok := analyzer.(string); ok {
			// This is already a templated string
			buf.WriteString("    ")
			buf.WriteString(strings.ReplaceAll(analyzerStr, "\n", "\n    "))
			buf.WriteString("\n")
		} else {
			// Convert to YAML and add as-is
			analyzerBytes, err := yaml.Marshal(analyzer)
			if err != nil {
				return "", errors.Wrap(err, "failed to marshal analyzer")
			}
			lines := strings.Split(string(analyzerBytes), "\n")
			for _, line := range lines {
				if strings.TrimSpace(line) != "" {
					buf.WriteString("    - ")
					buf.WriteString(line)
					buf.WriteString("\n")
				}
			}
		}
	}

	return buf.String(), nil
}

func (c *v1beta3Converter) initializeValues() {
	c.values["kubernetes"] = map[string]interface{}{
		"enabled":            false,
		"minVersion":         "1.20.0",
		"recommendedVersion": "1.22.0",
	}

	c.values["storage"] = map[string]interface{}{
		"enabled":   false,
		"className": "default",
	}

	c.values["cluster"] = map[string]interface{}{
		"minNodes":         3,
		"recommendedNodes": 5,
		"minCPU":           4,
	}

	c.values["node"] = map[string]interface{}{
		"minMemoryGi":            8,
		"recommendedMemoryGi":    32,
		"minEphemeralGi":         40,
		"recommendedEphemeralGi": 100,
	}

	c.values["ingress"] = map[string]interface{}{
		"enabled": false,
		"type":    "Contour",
	}

	c.values["runtime"] = map[string]interface{}{
		"enabled": false,
	}

	c.values["distribution"] = map[string]interface{}{
		"enabled": false,
	}

	c.values["nodeChecks"] = map[string]interface{}{
		"enabled": false,
		"count": map[string]interface{}{
			"enabled": false,
		},
		"cpu": map[string]interface{}{
			"enabled": false,
		},
		"memory": map[string]interface{}{
			"enabled": false,
		},
		"ephemeral": map[string]interface{}{
			"enabled": false,
		},
	}
}

func (c *v1beta3Converter) convertAnalyzers(analyzers []interface{}) ([]interface{}, error) {
	var result []interface{}

	for _, analyzer := range analyzers {
		if analyzerMap, ok := analyzer.(map[interface{}]interface{}); ok {
			converted, err := c.convertAnalyzer(analyzerMap)
			if err != nil {
				return nil, err
			}
			if converted != nil {
				result = append(result, converted)
			}
		}
	}

	return result, nil
}

func (c *v1beta3Converter) convertAnalyzer(analyzer map[interface{}]interface{}) (interface{}, error) {
	// Convert analyzer based on type
	if _, exists := analyzer["clusterVersion"]; exists {
		return c.convertClusterVersion(analyzer)
	}

	if _, exists := analyzer["customResourceDefinition"]; exists {
		return c.convertCustomResourceDefinition(analyzer)
	}

	if _, exists := analyzer["containerRuntime"]; exists {
		return c.convertContainerRuntime(analyzer)
	}

	if _, exists := analyzer["storageClass"]; exists {
		return c.convertStorageClass(analyzer)
	}

	if _, exists := analyzer["distribution"]; exists {
		return c.convertDistribution(analyzer)
	}

	if _, exists := analyzer["nodeResources"]; exists {
		return c.convertNodeResources(analyzer)
	}

	// For unrecognized analyzers, return as-is with warning comment
	return c.wrapWithWarning(analyzer, "Unknown analyzer type - manual review required")
}

func (c *v1beta3Converter) convertClusterVersion(analyzer map[interface{}]interface{}) (interface{}, error) {
	// Enable kubernetes checks
	c.setNestedValue("kubernetes.enabled", true)

	// Extract version requirements from outcomes
	if cv, ok := analyzer["clusterVersion"].(map[interface{}]interface{}); ok {
		if outcomes, ok := cv["outcomes"].([]interface{}); ok {
			c.extractVersionRequirements(outcomes)
		}
	}

	return c.createTemplatedAnalyzer("kubernetes", analyzer, "")
}

func (c *v1beta3Converter) convertCustomResourceDefinition(analyzer map[interface{}]interface{}) (interface{}, error) {
	c.setNestedValue("ingress.enabled", true)

	if crd, ok := analyzer["customResourceDefinition"].(map[interface{}]interface{}); ok {
		if crdName, ok := crd["customResourceDefinitionName"].(string); ok {
			if strings.Contains(crdName, "contour") {
				c.setNestedValue("ingress.type", "Contour")
			}
		}
	}

	return c.createTemplatedAnalyzer("ingress", analyzer, "")
}

func (c *v1beta3Converter) convertContainerRuntime(analyzer map[interface{}]interface{}) (interface{}, error) {
	c.setNestedValue("runtime.enabled", true)

	return c.createTemplatedAnalyzer("runtime", analyzer, "")
}

func (c *v1beta3Converter) convertStorageClass(analyzer map[interface{}]interface{}) (interface{}, error) {
	c.setNestedValue("storage.enabled", true)

	// Extract storage class name
	if sc, ok := analyzer["storageClass"].(map[interface{}]interface{}); ok {
		if className, ok := sc["storageClassName"].(string); ok {
			c.setNestedValue("storage.className", className)
		}
	}

	// Update the analyzer to use template
	if sc, ok := analyzer["storageClass"].(map[interface{}]interface{}); ok {
		sc["storageClassName"] = "{{ .Values.storage.className }}"
	}

	return c.createTemplatedAnalyzer("storage", analyzer, "")
}

func (c *v1beta3Converter) convertDistribution(analyzer map[interface{}]interface{}) (interface{}, error) {
	c.setNestedValue("distribution.enabled", true)

	return c.createTemplatedAnalyzer("distribution", analyzer, "")
}

func (c *v1beta3Converter) convertNodeResources(analyzer map[interface{}]interface{}) (interface{}, error) {
	if nr, ok := analyzer["nodeResources"].(map[interface{}]interface{}); ok {
		checkName := ""
		if name, ok := nr["checkName"].(string); ok {
			checkName = strings.ToLower(name)
		}

		// Determine node resource type and enable appropriate check
		if strings.Contains(checkName, "node") && strings.Contains(checkName, "count") {
			c.setNestedValue("nodeChecks.enabled", true)
			c.setNestedValue("nodeChecks.count.enabled", true)
			c.extractNodeCountRequirements(nr)
			return c.createTemplatedAnalyzer("nodeChecks.count", analyzer, "")
		}

		if strings.Contains(checkName, "cpu") || strings.Contains(checkName, "core") {
			c.setNestedValue("nodeChecks.enabled", true)
			c.setNestedValue("nodeChecks.cpu.enabled", true)
			c.extractCPURequirements(nr)
			return c.createTemplatedAnalyzer("nodeChecks.cpu", analyzer, "")
		}

		if strings.Contains(checkName, "memory") {
			c.setNestedValue("nodeChecks.enabled", true)
			c.setNestedValue("nodeChecks.memory.enabled", true)
			c.extractMemoryRequirements(nr)
			c.templatizeMemoryOutcomes(analyzer)
			return c.createTemplatedAnalyzer("nodeChecks.memory", analyzer, "")
		}

		if strings.Contains(checkName, "ephemeral") || strings.Contains(checkName, "storage") {
			c.setNestedValue("nodeChecks.enabled", true)
			c.setNestedValue("nodeChecks.ephemeral.enabled", true)
			c.extractEphemeralRequirements(nr)
			c.templatizeEphemeralOutcomes(analyzer)
			return c.createTemplatedAnalyzer("nodeChecks.ephemeral", analyzer, "")
		}
	}

	// Default case - enable general node checks
	c.setNestedValue("nodeChecks.enabled", true)
	return c.createTemplatedAnalyzer("nodeChecks", analyzer, "")
}

func (c *v1beta3Converter) createTemplatedAnalyzer(checkType string, originalAnalyzer map[interface{}]interface{}, docString string) (interface{}, error) {
	// Convert map[interface{}]interface{} to map[string]interface{} for proper YAML output
	convertedAnalyzer := c.convertMapKeys(originalAnalyzer)

	// Add placeholder docString - user should replace with their actual requirements
	convertedAnalyzer["docString"] = "# TODO: Add docString with Title, Requirement, and rationale for this check"

	// Marshal the analyzer to YAML
	analyzerBytes, err := yaml.Marshal(convertedAnalyzer)
	if err != nil {
		return nil, errors.Wrap(err, "failed to marshal analyzer")
	}

	// Create template string with proper indentation
	analyzerYAML := strings.TrimSuffix(string(analyzerBytes), "\n")

	// Add conditional wrapper
	condition := fmt.Sprintf("{{- if .Values.%s.enabled }}", checkType)
	endCondition := "{{- end }}"

	templateStr := fmt.Sprintf("%s\n- %s\n%s", condition,
		strings.ReplaceAll(analyzerYAML, "\n", "\n  "),
		endCondition)

	return templateStr, nil
}

func (c *v1beta3Converter) wrapWithWarning(analyzer map[interface{}]interface{}, warning string) (interface{}, error) {
	convertedAnalyzer := c.convertMapKeys(analyzer)
	convertedAnalyzer["docString"] = fmt.Sprintf("# TODO: Manual Review Required - %s", warning)
	return convertedAnalyzer, nil
}

func (c *v1beta3Converter) convertMapKeys(m map[interface{}]interface{}) map[string]interface{} {
	result := make(map[string]interface{})
	for k, v := range m {
		strKey := fmt.Sprintf("%v", k)
		switch val := v.(type) {
		case map[interface{}]interface{}:
			result[strKey] = c.convertMapKeys(val)
		case []interface{}:
			result[strKey] = c.convertSlice(val)
		default:
			result[strKey] = val
		}
	}
	return result
}

func (c *v1beta3Converter) convertSlice(s []interface{}) []interface{} {
	result := make([]interface{}, len(s))
	for i, v := range s {
		switch val := v.(type) {
		case map[interface{}]interface{}:
			result[i] = c.convertMapKeys(val)
		case []interface{}:
			result[i] = c.convertSlice(val)
		default:
			result[i] = val
		}
	}
	return result
}

// Helper methods for extracting requirements from outcomes
func (c *v1beta3Converter) extractVersionRequirements(outcomes []interface{}) {
	for _, outcome := range outcomes {
		if outcomeMap, ok := outcome.(map[interface{}]interface{}); ok {
			if fail, ok := outcomeMap["fail"].(map[interface{}]interface{}); ok {
				if when, ok := fail["when"].(string); ok {
					if version := c.extractVersionFromWhen(when); version != "" {
						c.setNestedValue("kubernetes.minVersion", version)
					}
				}
			}
			if warn, ok := outcomeMap["warn"].(map[interface{}]interface{}); ok {
				if when, ok := warn["when"].(string); ok {
					if version := c.extractVersionFromWhen(when); version != "" {
						c.setNestedValue("kubernetes.recommendedVersion", version)
					}
				}
			}
		}
	}
}

func (c *v1beta3Converter) extractVersionFromWhen(when string) string {
	// Simple version extraction from conditions like "< 1.22.0"
	when = strings.TrimSpace(when)
	if strings.HasPrefix(when, "<") {
		version := strings.TrimSpace(strings.TrimPrefix(when, "<"))
		version = strings.Trim(version, `"`)
		return version
	}
	return ""
}

func (c *v1beta3Converter) extractNodeCountRequirements(nr map[interface{}]interface{}) {
	if outcomes, ok := nr["outcomes"].([]interface{}); ok {
		for _, outcome := range outcomes {
			if outcomeMap, ok := outcome.(map[interface{}]interface{}); ok {
				if fail, ok := outcomeMap["fail"].(map[interface{}]interface{}); ok {
					if when, ok := fail["when"].(string); ok {
						if count := c.extractNumberFromWhen(when, "count()"); count > 0 {
							c.setNestedValue("cluster.minNodes", count)
						}
					}
				}
				if warn, ok := outcomeMap["warn"].(map[interface{}]interface{}); ok {
					if when, ok := warn["when"].(string); ok {
						if count := c.extractNumberFromWhen(when, "count()"); count > 0 {
							c.setNestedValue("cluster.recommendedNodes", count)
						}
					}
				}
			}
		}
	}
}

func (c *v1beta3Converter) extractCPURequirements(nr map[interface{}]interface{}) {
	if outcomes, ok := nr["outcomes"].([]interface{}); ok {
		for _, outcome := range outcomes {
			if outcomeMap, ok := outcome.(map[interface{}]interface{}); ok {
				if fail, ok := outcomeMap["fail"].(map[interface{}]interface{}); ok {
					if when, ok := fail["when"].(string); ok {
						if cpu := c.extractNumberFromWhen(when, "sum(cpuCapacity)"); cpu > 0 {
							c.setNestedValue("cluster.minCPU", cpu)
						}
					}
				}
			}
		}
	}
}

func (c *v1beta3Converter) extractMemoryRequirements(nr map[interface{}]interface{}) {
	if outcomes, ok := nr["outcomes"].([]interface{}); ok {
		for _, outcome := range outcomes {
			if outcomeMap, ok := outcome.(map[interface{}]interface{}); ok {
				if fail, ok := outcomeMap["fail"].(map[interface{}]interface{}); ok {
					if when, ok := fail["when"].(string); ok {
						if memory := c.extractMemoryFromWhen(when); memory > 0 {
							c.setNestedValue("node.minMemoryGi", memory)
						}
					}
				}
				if warn, ok := outcomeMap["warn"].(map[interface{}]interface{}); ok {
					if when, ok := warn["when"].(string); ok {
						if memory := c.extractMemoryFromWhen(when); memory > 0 {
							c.setNestedValue("node.recommendedMemoryGi", memory)
						}
					}
				}
			}
		}
	}
}

func (c *v1beta3Converter) extractEphemeralRequirements(nr map[interface{}]interface{}) {
	if outcomes, ok := nr["outcomes"].([]interface{}); ok {
		for _, outcome := range outcomes {
			if outcomeMap, ok := outcome.(map[interface{}]interface{}); ok {
				if fail, ok := outcomeMap["fail"].(map[interface{}]interface{}); ok {
					if when, ok := fail["when"].(string); ok {
						if storage := c.extractStorageFromWhen(when); storage > 0 {
							c.setNestedValue("node.minEphemeralGi", storage)
						}
					}
				}
				if warn, ok := outcomeMap["warn"].(map[interface{}]interface{}); ok {
					if when, ok := warn["when"].(string); ok {
						if storage := c.extractStorageFromWhen(when); storage > 0 {
							c.setNestedValue("node.recommendedEphemeralGi", storage)
						}
					}
				}
			}
		}
	}
}

func (c *v1beta3Converter) extractNumberFromWhen(when, prefix string) int {
	when = strings.TrimSpace(when)
	if strings.Contains(when, prefix) {
		// Extract number from conditions like "count() < 3"
		parts := strings.Split(when, "<")
		if len(parts) == 2 {
			numStr := strings.TrimSpace(parts[1])
			if num, err := strconv.Atoi(numStr); err == nil {
				return num
			}
		}
	}
	return 0
}

func (c *v1beta3Converter) extractMemoryFromWhen(when string) int {
	when = strings.TrimSpace(when)
	// Handle conditions like "min(memoryCapacity) < 8Gi"
	if strings.Contains(when, "memoryCapacity") {
		parts := strings.Split(when, "<")
		if len(parts) == 2 {
			sizeStr := strings.TrimSpace(parts[1])
			sizeStr = strings.TrimSuffix(sizeStr, "Gi")
			if num, err := strconv.Atoi(sizeStr); err == nil {
				return num
			}
		}
	}
	return 0
}

func (c *v1beta3Converter) extractStorageFromWhen(when string) int {
	when = strings.TrimSpace(when)
	// Handle conditions like "min(ephemeralStorageCapacity) < 40Gi"
	if strings.Contains(when, "ephemeralStorageCapacity") {
		parts := strings.Split(when, "<")
		if len(parts) == 2 {
			sizeStr := strings.TrimSpace(parts[1])
			sizeStr = strings.TrimSuffix(sizeStr, "Gi")
			if num, err := strconv.Atoi(sizeStr); err == nil {
				return num
			}
		}
	}
	return 0
}

func (c *v1beta3Converter) templatizeMemoryOutcomes(analyzer map[interface{}]interface{}) {
	c.templatizeNodeResourceOutcomes(analyzer, "memoryCapacity", "node.minMemoryGi", "node.recommendedMemoryGi")
}

func (c *v1beta3Converter) templatizeEphemeralOutcomes(analyzer map[interface{}]interface{}) {
	c.templatizeNodeResourceOutcomes(analyzer, "ephemeralStorageCapacity", "node.minEphemeralGi", "node.recommendedEphemeralGi")
}

func (c *v1beta3Converter) templatizeNodeResourceOutcomes(analyzer map[interface{}]interface{}, capacity, minKey, recKey string) {
	if nr, ok := analyzer["nodeResources"].(map[interface{}]interface{}); ok {
		if outcomes, ok := nr["outcomes"].([]interface{}); ok {
			for _, outcome := range outcomes {
				if outcomeMap, ok := outcome.(map[interface{}]interface{}); ok {
					// Update fail condition
					if fail, ok := outcomeMap["fail"].(map[interface{}]interface{}); ok {
						if when, ok := fail["when"].(string); ok && strings.Contains(when, capacity) {
							fail["when"] = fmt.Sprintf("min(%s) < {{ .Values.%s }}Gi", capacity, minKey)
						}
						if _, ok := fail["message"].(string); ok {
							parts := strings.Split(minKey, ".")
							fail["message"] = fmt.Sprintf("All nodes must have at least {{ .Values.%s }} GiB of %s.", minKey, parts[len(parts)-1])
						}
					}
					// Update warn condition
					if warn, ok := outcomeMap["warn"].(map[interface{}]interface{}); ok {
						if when, ok := warn["when"].(string); ok && strings.Contains(when, capacity) {
							warn["when"] = fmt.Sprintf("min(%s) < {{ .Values.%s }}Gi", capacity, recKey)
						}
						if _, ok := warn["message"].(string); ok {
							parts := strings.Split(recKey, ".")
							warn["message"] = fmt.Sprintf("All nodes are recommended to have at least {{ .Values.%s }} GiB of %s.", recKey, parts[len(parts)-1])
						}
					}
					// Update pass message
					if pass, ok := outcomeMap["pass"].(map[interface{}]interface{}); ok {
						if _, ok := pass["message"].(string); ok {
							parts := strings.Split(recKey, ".")
							pass["message"] = fmt.Sprintf("All nodes have at least {{ .Values.%s }} GiB of %s.", recKey, parts[len(parts)-1])
						}
					}
				}
			}
		}
	}
}

func (c *v1beta3Converter) setNestedValue(path string, value interface{}) {
	parts := strings.Split(path, ".")
	current := c.values

	for _, part := range parts[:len(parts)-1] {
		if _, ok := current[part]; !ok {
			current[part] = make(map[string]interface{})
		}
		if nextMap, ok := current[part].(map[string]interface{}); ok {
			current = nextMap
		} else {
			// Path exists but isn't a map, need to handle this case
			return
		}
	}

	current[parts[len(parts)-1]] = value
}
