package state

import (
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
)

// TestKubeconfigPassedToChartify verifies that when --kubeconfig is set,
// it is passed to chartify's internal helm template call.
// This is a regression test for issue #2444.
//
// Background: When using jsonPatches or kustomize patches with helmfile,
// chartify runs "helm template" internally to render the chart before applying patches.
// The lookup() helm function requires cluster access (--dry-run=server).
// Without --kubeconfig being passed to the internal helm template call,
// it fails to connect to the cluster when the user's kubeconfig is not in the default location.
//
// These tests exercise the real HelmState.buildChartifyTemplateArgs method (the pure
// helper that processChartification delegates to), not a duplicated copy of the logic.
func TestKubeconfigPassedToChartify(t *testing.T) {
	tests := []struct {
		name            string
		helmfileCommand string
		kubeconfig      string
		kubeContext     string
		expectedFlags   []string
		unexpectedFlags []string
	}{
		{
			name:            "sync with kubeconfig should pass both kubeconfig and dry-run=server",
			helmfileCommand: "sync",
			kubeconfig:      "/path/to/kubeconfig",
			kubeContext:     "",
			expectedFlags:   []string{"--kubeconfig", "/path/to/kubeconfig", "--dry-run=server"},
			unexpectedFlags: []string{},
		},
		{
			name:            "sync with kubeconfig and kube-context should pass both",
			helmfileCommand: "sync",
			kubeconfig:      "/path/to/kubeconfig",
			kubeContext:     "my-context",
			expectedFlags:   []string{"--kubeconfig", "/path/to/kubeconfig", "--kube-context", "my-context", "--dry-run=server"},
			unexpectedFlags: []string{},
		},
		{
			name:            "apply with kubeconfig should pass kubeconfig",
			helmfileCommand: "apply",
			kubeconfig:      "/custom/kubeconfig",
			kubeContext:     "",
			expectedFlags:   []string{"--kubeconfig", "/custom/kubeconfig", "--dry-run=server"},
			unexpectedFlags: []string{},
		},
		{
			name:            "diff with kubeconfig should pass kubeconfig",
			helmfileCommand: "diff",
			kubeconfig:      "/etc/kubeconfig",
			kubeContext:     "prod",
			expectedFlags:   []string{"--kubeconfig", "/etc/kubeconfig", "--kube-context", "prod", "--dry-run=server"},
			unexpectedFlags: []string{},
		},
		{
			name:            "template command should not pass kubeconfig (offline command)",
			helmfileCommand: "template",
			kubeconfig:      "/path/to/kubeconfig",
			kubeContext:     "",
			expectedFlags:   []string{},
			unexpectedFlags: []string{"--kubeconfig", "--dry-run=server"},
		},
		{
			name:            "build command should not pass kubeconfig (offline command)",
			helmfileCommand: "build",
			kubeconfig:      "/path/to/kubeconfig",
			kubeContext:     "",
			expectedFlags:   []string{},
			unexpectedFlags: []string{"--kubeconfig", "--dry-run=server"},
		},
		{
			name:            "no kubeconfig should not add kubeconfig flag",
			helmfileCommand: "sync",
			kubeconfig:      "",
			kubeContext:     "my-context",
			expectedFlags:   []string{"--kube-context", "my-context", "--dry-run=server"},
			unexpectedFlags: []string{"--kubeconfig"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			st := &HelmState{kubeconfig: tt.kubeconfig}
			got := st.buildChartifyTemplateArgs(tt.helmfileCommand, tt.kubeContext, false, "", "")

			for _, flag := range tt.expectedFlags {
				assert.Truef(t, strings.Contains(got, flag),
					"buildChartifyTemplateArgs() = %q; want to contain %q", got, flag)
			}

			for _, flag := range tt.unexpectedFlags {
				assert.Falsef(t, strings.Contains(got, flag),
					"buildChartifyTemplateArgs() = %q; want NOT to contain %q", got, flag)
			}
		})
	}
}

// TestKubeconfigNotDuplicated verifies that kubeconfig is not duplicated
// when it already exists in the template args.
func TestKubeconfigNotDuplicated(t *testing.T) {
	tests := []struct {
		name             string
		helmfileCommand  string
		kubeconfig       string
		existingArgs     string
		expectedCount    int
		expectedContains string
	}{
		{
			name:             "do not duplicate kubeconfig",
			helmfileCommand:  "sync",
			kubeconfig:       "/path/to/kubeconfig",
			existingArgs:     "--kubeconfig /existing/kubeconfig",
			expectedCount:    1,
			expectedContains: "--kubeconfig /existing/kubeconfig",
		},
		{
			name:             "add kubeconfig when not present",
			helmfileCommand:  "sync",
			kubeconfig:       "/path/to/kubeconfig",
			existingArgs:     "--some-flag",
			expectedCount:    1,
			expectedContains: "--kubeconfig /path/to/kubeconfig",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			st := &HelmState{kubeconfig: tt.kubeconfig}
			got := st.buildChartifyTemplateArgs(tt.helmfileCommand, "", false, "", tt.existingArgs)

			assert.Truef(t, strings.Contains(got, tt.expectedContains),
				"buildChartifyTemplateArgs() = %q; want to contain %q", got, tt.expectedContains)

			assert.Equalf(t, tt.expectedCount, strings.Count(got, "--kubeconfig"),
				"buildChartifyTemplateArgs() has --kubeconfig %d times; want %d", strings.Count(got, "--kubeconfig"), tt.expectedCount)
		})
	}
}

// TestTemplateArgsDryRunTriggersKubeInjection verifies that when the user passes
// --template-args="--dry-run=server" to an offline command (e.g. `helmfile template`),
// the kubeconfig and kube-context ARE injected into chartify's internal helm template
// so lookup() can actually reach the cluster. Regression test for issue #1833.
func TestTemplateArgsDryRunTriggersKubeInjection(t *testing.T) {
	st := &HelmState{kubeconfig: "/path/to/kubeconfig"}

	got := st.buildChartifyTemplateArgs("template", "my-context", false, "--dry-run=server", "")

	// User-provided arg is preserved
	assert.Contains(t, got, "--dry-run=server")
	// Cluster connection flags are injected even though "template" is offline
	assert.Contains(t, got, "--kubeconfig /path/to/kubeconfig")
	assert.Contains(t, got, "--kube-context my-context")
	// --dry-run=server is NOT duplicated (only the user's copy is present)
	assert.Equal(t, 1, strings.Count(got, "--dry-run"))
}

// TestTemplateArgsMergedBeforeInjection verifies that user-provided template args
// are merged into chartify's existing template args before the cluster-connectivity
// injection, so that duplicate --dry-run / --kubeconfig flags are deduplicated.
func TestTemplateArgsMergedBeforeInjection(t *testing.T) {
	st := &HelmState{kubeconfig: "/path/to/kubeconfig"}

	got := st.buildChartifyTemplateArgs(
		"sync", "ctx", false,
		"--dry-run=server", // user arg
		"--enable-dns",     // existing chartify arg
	)

	assert.Contains(t, got, "--enable-dns")
	assert.Contains(t, got, "--dry-run=server")
	// kubeconfig injected once, dry-run appears once (not duplicated)
	assert.Equal(t, 1, strings.Count(got, "--dry-run"))
	assert.Equal(t, 1, strings.Count(got, "--kubeconfig"))
}

// TestEffectiveTemplateArgs verifies CLI args take precedence over helmDefaults.templateArgs,
// mirroring the DiffArgs precedence (switch: CLI wins, else helmDefaults, else empty).
func TestEffectiveTemplateArgs(t *testing.T) {
	tests := []struct {
		name         string
		cliArgs      string
		helmDefaults []string
		want         string
	}{
		{
			name:         "CLI args win over helmDefaults",
			cliArgs:      "--dry-run=server",
			helmDefaults: []string{"--enable-dns"},
			want:         "--dry-run=server",
		},
		{
			name:         "helmDefaults used when CLI empty",
			cliArgs:      "",
			helmDefaults: []string{"--dry-run=server", "--enable-dns"},
			want:         "--dry-run=server --enable-dns",
		},
		{
			name:    "empty when neither set",
			cliArgs: "",
			want:    "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			st := &HelmState{
				ReleaseSetSpec: ReleaseSetSpec{
					HelmDefaults: HelmSpec{TemplateArgs: tt.helmDefaults},
				},
			}
			assert.Equal(t, tt.want, st.effectiveTemplateArgs(tt.cliArgs))
		})
	}
}

// TestHelmDefaultsTemplateArgsReachesChartify is a belt-and-suspenders test for the
// composition used by processChartification: it resolves the effective template args
// (CLI vs helmDefaults) and feeds them to buildChartifyTemplateArgs. Verifies that
// setting helmDefaults.templateArgs=[--dry-run=server] makes an offline `template`
// command opt into server-side templating + kube-connection injection end-to-end.
func TestHelmDefaultsTemplateArgsReachesChartify(t *testing.T) {
	st := &HelmState{
		kubeconfig: "/path/to/kubeconfig",
		ReleaseSetSpec: ReleaseSetSpec{
			HelmDefaults: HelmSpec{TemplateArgs: []string{"--dry-run=server"}},
		},
	}

	// Mirror processChartification's exact call shape.
	resolved := st.effectiveTemplateArgs("")
	got := st.buildChartifyTemplateArgs("template", "my-context", false, resolved, "")

	// helmDefaults --dry-run=server is present
	assert.Contains(t, got, "--dry-run=server")
	// ...and triggered kube-connection injection for an otherwise-offline command
	assert.Contains(t, got, "--kubeconfig /path/to/kubeconfig")
	assert.Contains(t, got, "--kube-context my-context")
	// not duplicated
	assert.Equal(t, 1, strings.Count(got, "--dry-run"))
}
