package chartify

import (
	"fmt"
	"os"
	"path"
	"path/filepath"

	"github.com/Masterminds/semver/v3"
	"gopkg.in/yaml.v3"
)

var validSortOrders = map[string]bool{
	"legacy": true,
	"fifo":   true,
}

type SortOptions struct {
	Order string `yaml:"order"`
}

func (o *SortOptions) validate() error {
	if o.Order == "" {
		return fmt.Errorf("sortOptions.order must not be empty")
	}
	if !validSortOrders[o.Order] {
		return fmt.Errorf("sortOptions.order %q is not valid; accepted values are: legacy, fifo", o.Order)
	}
	return nil
}

func marshalSortOptions(opts *SortOptions) ([]byte, error) {
	if opts == nil {
		return nil, nil
	}
	if err := opts.validate(); err != nil {
		return nil, err
	}
	sortOptsBytes, err := yaml.Marshal(map[string]*SortOptions{"sortOptions": opts})
	if err != nil {
		return nil, fmt.Errorf("marshaling sortOptions: %w", err)
	}
	return sortOptsBytes, nil
}

type KustomizeOpts struct {
	Images      []KustomizeImage `yaml:"images"`
	NamePrefix  string           `yaml:"namePrefix"`
	NameSuffix  string           `yaml:"nameSuffix"`
	Namespace   string           `yaml:"namespace"`
	SortOptions *SortOptions     `yaml:"sortOptions,omitempty"`
}

type KustomizeImage struct {
	Name    string `yaml:"name"`
	NewName string `yaml:"newName"`
	NewTag  string `yaml:"newTag"`
	Digest  string `yaml:"digest"`
}

func (img KustomizeImage) String() string {
	res := img.Name
	if img.NewName != "" {
		res = res + "=" + img.NewName
	}
	if img.NewTag != "" {
		res = res + ":" + img.NewTag
	}
	if img.Digest != "" {
		res = res + "@" + img.Digest
	}
	return res
}

type KustomizeBuildOpts struct {
	ValuesFiles        []string
	SetValues          []string
	SetFlags           []string
	EnableAlphaPlugins bool
	Namespace          string
	HelmBinary         string
	SortOptions        *SortOptions
}

func (o *KustomizeBuildOpts) SetKustomizeBuildOption(opts *KustomizeBuildOpts) error {
	*opts = *o
	return nil
}

type KustomizeBuildOption interface {
	SetKustomizeBuildOption(opts *KustomizeBuildOpts) error
}

func (r *Runner) KustomizeBuild(srcDir string, tempDir string, opts ...KustomizeBuildOption) (string, error) {
	kustomizeOpts := KustomizeOpts{}
	u := &KustomizeBuildOpts{}

	for i := range opts {
		if err := opts[i].SetKustomizeBuildOption(u); err != nil {
			return "", err
		}
	}

	for _, f := range u.ValuesFiles {
		valsFileContent, err := r.ReadFile(f)
		if err != nil {
			return "", err
		}
		if err := yaml.Unmarshal(valsFileContent, &kustomizeOpts); err != nil {
			return "", err
		}
	}

	if u.Namespace != "" {
		kustomizeOpts.Namespace = u.Namespace
	}

	if u.SortOptions != nil {
		kustomizeOpts.SortOptions = u.SortOptions
	}

	if len(u.SetValues) > 0 || len(u.SetFlags) > 0 {
		panic("--set is not yet supported for kustomize-based apps! Use -f/--values flag instead.")
	}

	prevDir, err := os.Getwd()
	if err != nil {
		return "", err
	}

	evaluatedPath, err := filepath.EvalSymlinks(tempDir)
	if err != nil {
		return "", err
	}
	var absoluteSrcPath string
	if filepath.IsAbs(srcDir) {
		absoluteSrcPath = srcDir
	} else {
		absoluteSrcPath = path.Join(prevDir, srcDir)
	}
	relPath, err := filepath.Rel(evaluatedPath, absoluteSrcPath)
	if err != nil {
		return "", err
	}
	baseFile := []byte("bases:\n- " + relPath + "\n")
	kustomizationPath := path.Join(tempDir, "kustomization.yaml")
	if err := r.WriteFile(kustomizationPath, baseFile, 0644); err != nil {
		return "", err
	}

	if len(kustomizeOpts.Images) > 0 {
		args := []string{"edit", "set", "image"}
		for _, image := range kustomizeOpts.Images {
			args = append(args, image.String())
		}
		_, err := r.runInDir(tempDir, r.kustomizeBin(), args...)
		if err != nil {
			return "", err
		}
	}
	if kustomizeOpts.NamePrefix != "" {
		_, err := r.runInDir(tempDir, r.kustomizeBin(), "edit", "set", "nameprefix", kustomizeOpts.NamePrefix)
		if err != nil {
			fmt.Println(err)
			return "", err
		}
	}
	if kustomizeOpts.NameSuffix != "" {
		// "--" is there to avoid `namesuffix -acme` to fail due to `-a` being considered as a flag
		_, err := r.runInDir(tempDir, r.kustomizeBin(), "edit", "set", "namesuffix", "--", kustomizeOpts.NameSuffix)
		if err != nil {
			return "", err
		}
	}
	if kustomizeOpts.Namespace != "" {
		_, err := r.runInDir(tempDir, r.kustomizeBin(), "edit", "set", "namespace", kustomizeOpts.Namespace)
		if err != nil {
			return "", err
		}
	}
	// sortOptions is appended directly to kustomization.yaml because kustomize
	// has no `edit set sortoptions` command unlike images, nameprefix, etc.
	if kustomizeOpts.SortOptions != nil {
		sortOptsBytes, err := marshalSortOptions(kustomizeOpts.SortOptions)
		if err != nil {
			return "", err
		}
		f, err := r.ReadFile(kustomizationPath)
		if err != nil {
			return "", fmt.Errorf("reading kustomization.yaml for sortOptions: %w", err)
		}
		if err := r.WriteFile(kustomizationPath, append(f, sortOptsBytes...), 0644); err != nil {
			return "", err
		}
	}
	outputFile := filepath.Join(tempDir, "templates", "kustomized.yaml")
	kustomizeArgs := []string{"-o", outputFile, "build"}

	if u.EnableAlphaPlugins {
		f, err := r.kustomizeEnableAlphaPluginsFlag()
		if err != nil {
			return "", err
		}
		kustomizeArgs = append(kustomizeArgs, f)
	}
	f, err := r.kustomizeLoadRestrictionsNoneFlag()
	if err != nil {
		return "", err
	}
	kustomizeArgs = append(kustomizeArgs, f, "--enable-helm")

	if u.HelmBinary != "" {
		kustomizeArgs = append(kustomizeArgs, "--helm-command="+u.HelmBinary)
	}

	out, err := r.runInDir(tempDir, r.kustomizeBin(), append(kustomizeArgs, tempDir)...)
	if err != nil {
		return "", err
	}
	fmt.Println(out)

	if err := os.RemoveAll(kustomizationPath); err != nil {
		return "", fmt.Errorf("removing unnecessary kustomization.yaml after build: %v", err)
	}

	return outputFile, nil
}

// kustomizeVersion returns the kustomize binary version.
func (r *Runner) kustomizeVersion() (*semver.Version, error) {
	versionInfo, err := r.run(nil, r.kustomizeBin(), "version")
	if err != nil {
		return nil, err
	}

	vi, err := FindSemVerInfo(versionInfo)
	if err != nil {
		return nil, err
	}
	version, err := semver.NewVersion(vi)
	if err != nil {
		return nil, err
	}
	return version, nil
}

// kustomizeEnableAlphaPluginsFlag returns the kustomize binary alpha plugin argument.
// Above Kustomize v3, it is `--enable-alpha-plugins`.
// Below Kustomize v3 (including v3), it is `--enable_alpha_plugins`.
func (r *Runner) kustomizeEnableAlphaPluginsFlag() (string, error) {
	version, err := r.kustomizeVersion()
	if err != nil {
		return "", err
	}
	if version.Major() > 3 {
		return "--enable-alpha-plugins", nil
	}
	return "--enable_alpha_plugins", nil
}

// kustomizeLoadRestrictionsNoneFlag returns the kustomize loading files from outside
// the root argument.
// Above Kustomize v3, it is `--load-restrictor=LoadRestrictionsNone`.
// Below Kustomize v3 (including v3), it is `--load_restrictor=none`.
func (r *Runner) kustomizeLoadRestrictionsNoneFlag() (string, error) {
	version, err := r.kustomizeVersion()
	if err != nil {
		return "", err
	}
	if version.Major() > 3 {
		return "--load-restrictor=LoadRestrictionsNone", nil
	}
	return "--load_restrictor=none", nil
}
