package tier

import (
	"bytes"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"sigs.k8s.io/yaml"

	"github.com/tidbcloud/cluster-service-ng/internal/capacity-planner/api/v1alpha1"
)

func TestHelmMetricsStorageIsWritableByNonRoot(t *testing.T) {
	chartPath := filepath.Join("..", "..", "charts", "capacity-planner")
	rendered, err := exec.Command(helmBinary(t), "template", chartPath, "--show-only", "templates/deployment.yaml").CombinedOutput()
	if err != nil {
		t.Fatalf("helm template failed: %v\n%s", err, rendered)
	}
	var deployment appsv1.Deployment
	if err := yaml.Unmarshal(rendered, &deployment); err != nil {
		t.Fatal(err)
	}
	pod := deployment.Spec.Template.Spec
	const (
		imageGroupID       = 65532
		metricsStoragePath = "/var/lib/capacity-planner/metrics"
	)
	if pod.SecurityContext == nil || pod.SecurityContext.FSGroup == nil || *pod.SecurityContext.FSGroup != imageGroupID {
		t.Fatal("metrics volume must be writable by the image's non-root group")
	}
	container := pod.Containers[0]
	if container.SecurityContext == nil || container.SecurityContext.ReadOnlyRootFilesystem == nil || !*container.SecurityContext.ReadOnlyRootFilesystem {
		t.Fatal("root filesystem must remain read-only")
	}
	for _, mount := range container.VolumeMounts {
		if mount.MountPath != metricsStoragePath || mount.ReadOnly {
			continue
		}
		for _, volume := range pod.Volumes {
			if volume.Name == mount.Name && volume.EmptyDir != nil {
				return
			}
		}
	}
	t.Fatal("default metrics storage path must have a writable emptyDir")
}

// TestHelmTierTemplateMaterializesTiDBCatalog renders the production Helm chart
// and verifies that the rendered tier template config can materialize every
// TiDB autoscaling tier into the expected canonical TiDBGroup capacity patch.
// This is a cross-boundary guard: CSNG must emit these exact variants and the
// Helm config must produce matching label/selector/resources/GOMEMLIMIT.
func TestHelmTierTemplateMaterializesTiDBCatalog(t *testing.T) {
	chartPath := filepath.Join("..", "..", "charts", "capacity-planner")
	var stdout, stderr bytes.Buffer
	cmd := exec.Command(helmBinary(t), "template", chartPath, "--include-crds")
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	if err := cmd.Run(); err != nil {
		t.Fatalf("helm template failed: %v\n%s", err, stderr.String())
	}

	configData := extractTierTemplatesConfig(t, stdout.String())
	templates, err := ParseTemplatesConfig([]byte(configData))
	if err != nil {
		t.Fatalf("ParseTemplatesConfig() error = %v", err)
	}

	// Expected variants are the effective TiDB request values that CSNG emits.
	tiers, err := New(templates, &v1alpha1.Tiers{
		Template: v1alpha1.TierTemplateRef{Name: "tidb-node"},
		Sets: []v1alpha1.TierSet{{
			Dimensions: []v1alpha1.TierDimension{{
				Resources: []v1alpha1.TierDimensionResource{
					{Name: corev1.ResourceCPU, Variants: []resource.Quantity{
						resource.MustParse("3257m"), resource.MustParse("7220m"), resource.MustParse("15200m"),
					}},
					{Name: corev1.ResourceMemory, Variants: []resource.Quantity{
						resource.MustParse("13115Mi"), resource.MustParse("13114Mi"), resource.MustParse("28041Mi"),
					}},
				},
			}},
		}},
	})
	if err != nil {
		t.Fatalf("New() error = %v", err)
	}
	if len(tiers) != 3 {
		t.Fatalf("New() returned %d tiers, want 3", len(tiers))
	}

	expected := []struct {
		name     string
		nodeSpec string
		cpuReq   string
		memReq   string
		cpuLim   string
		memLim   string
		goMemLim string
		cost     int64
	}{
		{"tidb-node-0", "4C16G", "3257m", "13115Mi", "3920m", "14079Mi", "11263MiB", 56},
		{"tidb-node-1", "8C16G", "7220m", "13114Mi", "7910m", "14078Mi", "11262MiB", 96},
		{"tidb-node-2", "16C32G", "15200m", "28041Mi", "15890m", "29326Mi", "23460MiB", 192},
	}
	for i, tier := range tiers {
		if i >= len(expected) {
			break
		}
		exp := expected[i]
		if tier.Name() != exp.name {
			t.Fatalf("tier[%d].Name() = %q, want %q", i, tier.Name(), exp.name)
		}
		if got := tier.Cost(); got != exp.cost {
			t.Fatalf("tier %q Cost() = %d, want %d", tier.Name(), got, exp.cost)
		}
		assertTierPatch(t, tier, exp.nodeSpec, exp.cpuReq, exp.memReq, exp.cpuLim, exp.memLim, exp.goMemLim)
	}
}

func helmBinary(t *testing.T) string {
	t.Helper()
	if local := filepath.Join("..", "..", "bin", "helm"); fileExists(local) {
		abs, err := filepath.Abs(local)
		if err != nil {
			t.Fatalf("resolve helm path: %v", err)
		}
		return abs
	}
	if path, err := exec.LookPath("helm"); err == nil {
		return path
	}
	t.Fatal("helm binary not found; run make verify/chart to install it")
	return ""
}

func fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

func extractTierTemplatesConfig(t *testing.T, rendered string) string {
	t.Helper()
	var configData strings.Builder
	inConfigMap := false
	inData := false
	indent := ""
	lines := strings.Split(rendered, "\n")
	for i, line := range lines {
		if strings.TrimSpace(line) == "kind: ConfigMap" {
			inConfigMap = true
			continue
		}
		if !inConfigMap {
			continue
		}
		if strings.HasPrefix(line, "---") {
			break
		}
		trimmed := strings.TrimSpace(line)
		if trimmed == "data:" {
			inData = true
			continue
		}
		if !inData {
			continue
		}
		if trimmed == "tier-templates.yaml: |-" {
			// Next line determines the base indentation of the YAML payload.
			if i+1 < len(lines) {
				indent = leadingSpaces(lines[i+1])
			}
			continue
		}
		if indent != "" && strings.HasPrefix(line, indent) {
			configData.WriteString(strings.TrimPrefix(line, indent))
			configData.WriteByte('\n')
		} else if trimmed != "" && !strings.HasPrefix(line, indent) && configData.Len() > 0 {
			// End of the indented YAML block.
			break
		}
	}
	if configData.Len() == 0 {
		t.Fatal("tier-templates.yaml config not found in rendered Helm chart")
	}
	return configData.String()
}

func leadingSpaces(s string) string {
	var n int
	for _, r := range s {
		if r != ' ' {
			break
		}
		n++
	}
	return s[:n]
}
