/*
Copyright 2026 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package kustomize

import (
	"bytes"
	"fmt"
	"strings"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"sigs.k8s.io/yaml"

	"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/templater"
)

// ChartGenerator generates Helm chart template files derived from kustomize resources.
// Note: values.yaml is generated by Machinery template (templates/values.go).
//
// The generator performs NO input/output operations. All functions accept data structures and
// return strings, making them easy to test in memory without needing filesystem access.
type ChartGenerator struct {
	templatesGen   *TemplatesGenerator
	templater      *templater.Templater
	detectedPrefix string
}

// NewChartGenerator creates a new chart generator.
func NewChartGenerator(t *templater.Templater, detectedPrefix string) *ChartGenerator {
	return &ChartGenerator{
		templatesGen:   &TemplatesGenerator{},
		templater:      t,
		detectedPrefix: detectedPrefix,
	}
}

// ChartFiles contains generated chart template files derived from kustomize resources.
// Fixed Helm chart files (Chart.yaml, values.yaml, NOTES.txt, _helpers.tpl) are handled by Machinery templates.
type ChartFiles struct {
	TemplateFiles map[string]string
}

// GenerateChart generates chart template files derived from kustomize resources.
// Note: values.yaml is generated separately by Machinery template.
func (g *ChartGenerator) GenerateChart(
	resourceGroups map[string][]*unstructured.Unstructured,
) *ChartFiles {
	managerNamespace := ""
	if g.templater != nil {
		managerNamespace = g.templater.GetManagerNamespace()
	}
	return &ChartFiles{
		TemplateFiles: g.templatesGen.Generate(resourceGroups, g.templater, g.detectedPrefix, managerNamespace),
	}
}

// TemplatesGenerator generates template file contents.
type TemplatesGenerator struct{}

// Generate generates template files from resource groups.
// Returns map of filename -> content (with directory structure: "groupName/filename.yaml").
func (g *TemplatesGenerator) Generate(
	resourceGroups map[string][]*unstructured.Unstructured,
	t *templater.Templater,
	detectedPrefix string,
	managerNamespace string,
) map[string]string {
	templates := make(map[string]string)

	for groupName, resources := range resourceGroups {
		if len(resources) == 0 {
			continue
		}

		if g.shouldSplitFiles(groupName) {
			for i, resource := range resources {
				templatedYAML := g.templateResource(resource, t)
				filename := g.generateFileName(resource, i, groupName, detectedPrefix, managerNamespace)
				path := fmt.Sprintf("%s/%s", groupName, filename)
				templates[path] = templatedYAML
			}
		} else {
			var buf bytes.Buffer
			for i, resource := range resources {
				if i > 0 {
					buf.WriteString("---\n")
				}
				templatedYAML := g.templateResource(resource, t)
				buf.WriteString(templatedYAML)
			}
			path := fmt.Sprintf("%s/%s.yaml", groupName, groupName)
			templates[path] = buf.String()
		}
	}

	return templates
}

func (g *TemplatesGenerator) templateResource(
	resource *unstructured.Unstructured,
	t *templater.Templater,
) string {
	yamlBytes, err := yaml.Marshal(resource.Object)
	if err != nil {
		// Return error as YAML comment to make marshal failures visible
		return fmt.Sprintf(
			"# Failed to marshal resource %s %s/%s: %v\n",
			resource.GetKind(),
			resource.GetNamespace(),
			resource.GetName(),
			err,
		)
	}
	yamlContent := string(yamlBytes)
	if t == nil {
		return yamlContent
	}
	return t.ApplyHelmSubstitutions(yamlContent, resource)
}

func (g *TemplatesGenerator) shouldSplitFiles(groupName string) bool {
	return groupName == "crd" || groupName == "cert-manager" || groupName == "webhook" ||
		groupName == "prometheus" || groupName == "network-policy" || groupName == "rbac" ||
		groupName == "metrics" || groupName == "extras"
}

// generateFileName creates a unique filename for a resource based on its metadata.
// For namespace-scoped resources (Role, RoleBinding) in non-manager namespaces, includes
// namespace in filename to prevent collisions and distinguish cross-namespace resources.
func (g *TemplatesGenerator) generateFileName(
	resource *unstructured.Unstructured, index int, _ string, detectedPrefix string, managerNamespace string,
) string {
	resourceName := resource.GetName()
	kind := resource.GetKind()
	namespace := resource.GetNamespace()

	// Strip the detected prefix from the resource name for the filename
	if after, ok := strings.CutPrefix(resourceName, detectedPrefix+"-"); ok {
		resourceName = after
	}

	// For namespace-scoped RBAC resources, include namespace suffix only if NOT in manager namespace.
	// This distinguishes cross-namespace RBAC resources while keeping manager namespace resources clean.
	if resourceName != "" {
		if (kind == "Role" || kind == "RoleBinding") && namespace != "" && namespace != managerNamespace {
			return fmt.Sprintf("%s-%s.yaml", resourceName, namespace)
		}
		return fmt.Sprintf("%s.yaml", resourceName)
	}

	if kind == "" {
		kind = "resource"
	}
	return fmt.Sprintf("%s-%d.yaml", strings.ToLower(kind), index)
}
