package main

import (
	"fmt"
	"os"
	"reflect"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/databus23/helm-diff/v3/cmd"
)

func TestMain(m *testing.M) {
	if os.Getenv(env) == envValue {
		os.Exit(runFakeHelm())
	}

	os.Exit(m.Run())
}

type helmDiffTestHelper struct {
	oldArgs    []string
	helmBin    string
	helmBinSet bool
}

func setupHelmDiffTest(t *testing.T) *helmDiffTestHelper {
	t.Helper()
	os.Setenv(env, envValue)

	h := &helmDiffTestHelper{
		oldArgs: os.Args,
	}
	h.helmBin, h.helmBinSet = os.LookupEnv("HELM_BIN")
	os.Setenv("HELM_BIN", os.Args[0])

	return h
}

func (h *helmDiffTestHelper) cleanup() {
	os.Unsetenv(env)
	os.Args = h.oldArgs
	if h.helmBinSet {
		os.Setenv("HELM_BIN", h.helmBin)
	} else {
		os.Unsetenv("HELM_BIN")
	}
}

func TestHelmDiff(t *testing.T) {
	h := setupHelmDiffTest(t)
	defer h.cleanup()

	os.Args = []string{"helm-diff", "upgrade", "-f", "test/testdata/test-values.yaml", "test-release", "test/testdata/test-chart"}
	require.NoError(t, cmd.New().Execute())
}

func TestHelmDiffWithKubeContext(t *testing.T) {
	h := setupHelmDiffTest(t)
	defer h.cleanup()

	os.Args = []string{"helm-diff", "upgrade", "-f", "test/testdata/test-values.yaml", "--kube-context", "test-context", "test-release", "test/testdata/test-chart"}
	require.NoError(t, cmd.New().Execute())
}

func TestHelmDiffWithKubeContextReuseValues(t *testing.T) {
	h := setupHelmDiffTest(t)
	defer h.cleanup()

	os.Args = []string{"helm-diff", "upgrade", "--reuse-values", "--kube-context", "test-context", "-f", "test/testdata/test-values.yaml", "test-release", "test/testdata/test-chart"}
	require.NoError(t, cmd.New().Execute())
}

func TestHelmDiffRevisionWithKubeContext(t *testing.T) {
	h := setupHelmDiffTest(t)
	defer h.cleanup()

	os.Args = []string{"helm-diff", "revision", "--kube-context", "test-context", "test-release", "2"}
	require.NoError(t, cmd.New().Execute())
}

func TestHelmDiffRollbackWithKubeContext(t *testing.T) {
	h := setupHelmDiffTest(t)
	defer h.cleanup()

	os.Args = []string{"helm-diff", "rollback", "--kube-context", "test-context", "test-release", "2"}
	require.NoError(t, cmd.New().Execute())
}

func TestHelmDiffReleaseWithKubeContext(t *testing.T) {
	h := setupHelmDiffTest(t)
	defer h.cleanup()

	os.Args = []string{"helm-diff", "release", "--kube-context", "test-context", "test-release1", "test-release2"}
	require.NoError(t, cmd.New().Execute())
}

const (
	env      = "BECOME_FAKE_HELM"
	envValue = "1"
)

type fakeHelmSubcmd struct {
	cmd      []string
	args     []string
	stdout   string
	stderr   string
	exitCode int
}

var helmSubcmdStubs = []fakeHelmSubcmd{
	{
		cmd:    []string{"version"},
		stdout: `version.BuildInfo{Version:"v3.1.0-rc.1", GitCommit:"12345", GitTreeState:"clean", GoVersion:"go1.20.12"}`,
	},
	{
		cmd:  []string{"get", "manifest"},
		args: []string{"test-release"},
		stdout: `---
# Source: test-chart/templates/cm.yaml
`,
	},
	{
		cmd:  []string{"template"},
		args: []string{"test-release", "test/testdata/test-chart", "--values", "test/testdata/test-values.yaml", "--validate", "--is-upgrade"},
	},
	{
		cmd:  []string{"get", "hooks"},
		args: []string{"test-release"},
	},
	{
		cmd:  []string{"get", "manifest"},
		args: []string{"test-release", "--kube-context", "test-context"},
		stdout: `---
# Source: test-chart/templates/cm.yaml
`,
	},
	{
		cmd:  []string{"template"},
		args: []string{"test-release", "test/testdata/test-chart", "--kube-context", "test-context", "--values", "test/testdata/test-values.yaml", "--validate", "--is-upgrade"},
	},
	{
		cmd:  []string{"get", "hooks"},
		args: []string{"test-release", "--kube-context", "test-context"},
	},
	{
		cmd:  []string{"get", "values"},
		args: []string{"test-release", "--output", "yaml", "--all", "--kube-context", "test-context"},
	},
	{
		cmd:  []string{"get", "values"},
		args: []string{"test-release", "--output", "yaml", "--all", "--namespace", "*", "--kube-context", "test-context"},
	},
	{
		cmd:  []string{"template"},
		args: []string{"test-release", "test/testdata/test-chart", "--kube-context", "test-context", "--values", "*", "--values", "test/testdata/test-values.yaml", "--validate", "--is-upgrade"},
	},
	{
		cmd:  []string{"get", "manifest"},
		args: []string{"test-release", "--revision", "2", "--kube-context", "test-context"},
		stdout: `---
# Source: test-chart/templates/cm.yaml
`,
	},
	{
		cmd:  []string{"get", "manifest"},
		args: []string{"test-release1", "--kube-context", "test-context"},
		stdout: `---
# Source: test-chart/templates/cm.yaml
`,
	},
	{
		cmd:    []string{"get", "all"},
		args:   []string{"test-release1", "--template", "*", "--kube-context", "test-context"},
		stdout: `test-chart`,
	},
	{
		cmd:  []string{"get", "manifest"},
		args: []string{"test-release2", "--kube-context", "test-context"},
		stdout: `---
# Source: test-chart/templates/cm.yaml
`,
	},
	{
		cmd:    []string{"get", "all"},
		args:   []string{"test-release2", "--template", "*", "--kube-context", "test-context"},
		stdout: `test-chart`,
	},
}

func runFakeHelm() int {
	var stub *fakeHelmSubcmd

	if len(os.Args) < 2 {
		_, _ = fmt.Fprintln(os.Stderr, "fake helm does not support invocations without subcommands")
		return 1
	}

	cmdAndArgs := os.Args[1:]
	for i := range helmSubcmdStubs {
		s := helmSubcmdStubs[i]
		if reflect.DeepEqual(s.cmd, cmdAndArgs[:len(s.cmd)]) {
			want := s.args
			if want == nil {
				want = []string{}
			}
			got := cmdAndArgs[len(s.cmd):]
			if argsMatch(want, got) {
				stub = &s
				break
			}
		}
	}

	if stub == nil {
		_, _ = fmt.Fprintf(os.Stderr, "no stub for %s\n", cmdAndArgs)
		return 1
	}

	_, _ = fmt.Fprintf(os.Stdout, "%s", stub.stdout)
	_, _ = fmt.Fprintf(os.Stderr, "%s", stub.stderr)
	return stub.exitCode
}

func argsMatch(want, got []string) bool {
	if len(want) != len(got) {
		return false
	}
	for i := range want {
		if want[i] == "*" {
			continue
		}
		if want[i] != got[i] {
			return false
		}
	}
	return true
}