package config

import (
	"fmt"
	"os"
	"strings"

	"github.com/helmfile/helmfile/pkg/maputil"
	"github.com/helmfile/helmfile/pkg/state"
	"github.com/urfave/cli"
	"go.uber.org/zap"
	"golang.org/x/crypto/ssh/terminal"
)

type ConfigImpl struct {
	c *cli.Context

	set map[string]interface{}
}

func NewUrfaveCliConfigImpl(c *cli.Context) (ConfigImpl, error) {
	if c.NArg() > 0 {
		err := cli.ShowAppHelp(c)
		if err != nil {
			return ConfigImpl{}, err
		}
		return ConfigImpl{}, fmt.Errorf("err: extraneous arguments: %s", strings.Join(c.Args(), ", "))
	}

	conf := ConfigImpl{
		c: c,
	}

	optsSet := c.GlobalStringSlice("state-values-set")
	if len(optsSet) > 0 {
		set := map[string]interface{}{}
		for i := range optsSet {
			ops := strings.Split(optsSet[i], ",")
			for j := range ops {
				op := strings.SplitN(ops[j], "=", 2)
				k := maputil.ParseKey(op[0])
				v := op[1]

				maputil.Set(set, k, v)
			}
		}
		conf.set = set
	}

	return conf, nil
}

func (c ConfigImpl) Set() []string {
	return c.c.StringSlice("set")
}

func (c ConfigImpl) SkipRepos() bool {
	return c.c.Bool("skip-repos")
}

func (c ConfigImpl) Wait() bool {
	return c.c.Bool("wait")
}

func (c ConfigImpl) WaitForJobs() bool {
	return c.c.Bool("wait-for-jobs")
}

func (c ConfigImpl) Values() []string {
	return c.c.StringSlice("values")
}

func (c ConfigImpl) Args() string {
	args := c.c.String("args")
	enableHelmDebug := c.c.GlobalBool("debug")

	if enableHelmDebug {
		args = fmt.Sprintf("%s %s", args, "--debug")
	}
	return args
}

func (c ConfigImpl) OutputDir() string {
	return strings.TrimRight(c.c.String("output-dir"), fmt.Sprintf("%c", os.PathSeparator))
}

func (c ConfigImpl) OutputDirTemplate() string {
	return c.c.String("output-dir-template")
}

func (c ConfigImpl) OutputFileTemplate() string {
	return c.c.String("output-file-template")
}

func (c ConfigImpl) Validate() bool {
	return c.c.Bool("validate")
}

func (c ConfigImpl) Concurrency() int {
	return c.c.Int("concurrency")
}

func (c ConfigImpl) HasCommandName(name string) bool {
	return c.c.Command.HasName(name)
}

func (c ConfigImpl) SkipNeeds() bool {
	if !c.IncludeNeeds() {
		return c.c.Bool("skip-needs")
	}

	return false
}

func (c ConfigImpl) IncludeNeeds() bool {
	return c.c.Bool("include-needs") || c.IncludeTransitiveNeeds()
}

func (c ConfigImpl) IncludeTransitiveNeeds() bool {
	return c.c.Bool("include-transitive-needs")
}

// DiffConfig

func (c ConfigImpl) SkipDeps() bool {
	return c.c.Bool("skip-deps")
}

func (c ConfigImpl) DetailedExitcode() bool {
	return c.c.Bool("detailed-exitcode")
}

func (c ConfigImpl) RetainValuesFiles() bool {
	return c.c.Bool("retain-values-files")
}

func (c ConfigImpl) IncludeTests() bool {
	return c.c.Bool("include-tests")
}

func (c ConfigImpl) Suppress() []string {
	return c.c.StringSlice("suppress")
}

func (c ConfigImpl) SuppressSecrets() bool {
	return c.c.Bool("suppress-secrets")
}

func (c ConfigImpl) ShowSecrets() bool {
	return c.c.Bool("show-secrets")
}

func (c ConfigImpl) SuppressDiff() bool {
	return c.c.Bool("suppress-diff")
}

// DeleteConfig

func (c ConfigImpl) Purge() bool {
	return c.c.Bool("purge")
}

// TestConfig

func (c ConfigImpl) Cleanup() bool {
	return c.c.Bool("cleanup")
}

func (c ConfigImpl) Logs() bool {
	return c.c.Bool("logs")
}

func (c ConfigImpl) Timeout() int {
	if !c.c.IsSet("timeout") {
		return state.EmptyTimeout
	}
	return c.c.Int("timeout")
}

// ListConfig

func (c ConfigImpl) Output() string {
	return c.c.String("output")
}

func (c ConfigImpl) KeepTempDir() bool {
	return c.c.Bool("keep-temp-dir")
}

// GlobalConfig

func (c ConfigImpl) HelmBinary() string {
	return c.c.GlobalString("helm-binary")
}

func (c ConfigImpl) KubeContext() string {
	return c.c.GlobalString("kube-context")
}

func (c ConfigImpl) Namespace() string {
	return c.c.GlobalString("namespace")
}

func (c ConfigImpl) Chart() string {
	return c.c.GlobalString("chart")
}

func (c ConfigImpl) FileOrDir() string {
	return c.c.GlobalString("file")
}

func (c ConfigImpl) Selectors() []string {
	return c.c.GlobalStringSlice("selector")
}

func (c ConfigImpl) StateValuesSet() map[string]interface{} {
	return c.set
}

func (c ConfigImpl) StateValuesFiles() []string {
	return c.c.GlobalStringSlice("state-values-file")
}

func (c ConfigImpl) Interactive() bool {
	return c.c.GlobalBool("interactive")
}

func (c ConfigImpl) Color() bool {
	if c := c.c.GlobalBool("color"); c {
		return c
	}

	if c.NoColor() {
		return false
	}

	// We replicate the helm-diff behavior in helmfile
	// because when when helmfile calls helm-diff, helm-diff has no access to term and therefore
	// we can't rely on helm-diff's ability to auto-detect term for color output.
	// See https://github.com/roboll/helmfile/issues/2043

	term := terminal.IsTerminal(int(os.Stdout.Fd()))
	// https://github.com/databus23/helm-diff/issues/281
	dumb := os.Getenv("TERM") == "dumb"
	return term && !dumb
}

func (c ConfigImpl) NoColor() bool {
	return c.c.GlobalBool("no-color")
}

func (c ConfigImpl) Context() int {
	return c.c.Int("context")
}

func (c ConfigImpl) DiffOutput() string {
	return c.c.String("output")
}

func (c ConfigImpl) SkipCleanup() bool {
	return c.c.Bool("skip-cleanup")
}

func (c ConfigImpl) SkipCRDs() bool {
	return c.c.Bool("skip-crds")
}

func (c ConfigImpl) SkipDiffOnInstall() bool {
	return c.c.Bool("skip-diff-on-install")
}

func (c ConfigImpl) EmbedValues() bool {
	return c.c.Bool("embed-values")
}

func (c ConfigImpl) IncludeCRDs() bool {
	return c.c.Bool("include-crds")
}

func (c ConfigImpl) SkipTests() bool {
	return c.c.Bool("skip-tests")
}

func (c ConfigImpl) Logger() *zap.SugaredLogger {
	return c.c.App.Metadata["logger"].(*zap.SugaredLogger)
}

func (c ConfigImpl) Env() string {
	env := c.c.GlobalString("environment")
	if env == "" {
		env = os.Getenv("HELMFILE_ENVIRONMENT")
		if env == "" {
			env = state.DefaultEnv
		}
	}
	return env
}
