// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package target

import (
	"fmt"

	"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
	"sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers"
	"sigs.k8s.io/kustomize/api/resmap"
	"sigs.k8s.io/kustomize/api/types"
)

// Functions dedicated to configuring the builtin
// transformer and generator plugins using config data
// read from a kustomization file and from the
// config.TransformerConfig, whose data may be a
// mix of hardcoded values and data read from file.
//
// Non-builtin plugins will get their configuration
// from their own dedicated structs and YAML files.
//
// There are some loops in the functions below because
// the kustomization file would, say, allow someone to
// request multiple secrets be made, or run multiple
// image tag transforms.  In these cases, we'll need
// N plugin instances with differing configurations.

func (kt *KustTarget) configureBuiltinGenerators() (
	result []resmap.Generator, err error) {
	for _, bpt := range []builtinhelpers.BuiltinPluginType{
		builtinhelpers.ConfigMapGenerator,
		builtinhelpers.SecretGenerator,
		builtinhelpers.HelmChartInflationGenerator,
	} {
		r, err := generatorConfigurators[bpt](
			kt, bpt, builtinhelpers.GeneratorFactories[bpt])
		if err != nil {
			return nil, err
		}
		result = append(result, r...)
	}
	return result, nil
}

func (kt *KustTarget) configureBuiltinTransformers(
	tc *builtinconfig.TransformerConfig) (
	result []resmap.Transformer, err error) {
	for _, bpt := range []builtinhelpers.BuiltinPluginType{
		builtinhelpers.PatchStrategicMergeTransformer,
		builtinhelpers.PatchTransformer,
		builtinhelpers.NamespaceTransformer,
		builtinhelpers.PrefixSuffixTransformer,
		builtinhelpers.LabelTransformer,
		builtinhelpers.AnnotationsTransformer,
		builtinhelpers.PatchJson6902Transformer,
		builtinhelpers.ReplicaCountTransformer,
		builtinhelpers.ImageTagTransformer,
	} {
		r, err := transformerConfigurators[bpt](
			kt, bpt, builtinhelpers.TransformerFactories[bpt], tc)
		if err != nil {
			return nil, err
		}
		result = append(result, r...)
	}
	return result, nil
}

type gFactory func() resmap.GeneratorPlugin

var generatorConfigurators = map[builtinhelpers.BuiltinPluginType]func(
	kt *KustTarget,
	bpt builtinhelpers.BuiltinPluginType,
	factory gFactory) (result []resmap.Generator, err error){
	builtinhelpers.SecretGenerator: func(kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f gFactory) (
		result []resmap.Generator, err error) {
		var c struct {
			types.SecretArgs
		}
		for _, args := range kt.kustomization.SecretGenerator {
			c.SecretArgs = args
			c.SecretArgs.Options = types.MergeGlobalOptionsIntoLocal(
				c.SecretArgs.Options, kt.kustomization.GeneratorOptions)
			p := f()
			err := kt.configureBuiltinPlugin(p, c, bpt)
			if err != nil {
				return nil, err
			}
			result = append(result, p)
		}
		return
	},

	builtinhelpers.ConfigMapGenerator: func(kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f gFactory) (
		result []resmap.Generator, err error) {
		var c struct {
			types.ConfigMapArgs
		}
		for _, args := range kt.kustomization.ConfigMapGenerator {
			c.ConfigMapArgs = args
			c.ConfigMapArgs.Options = types.MergeGlobalOptionsIntoLocal(
				c.ConfigMapArgs.Options, kt.kustomization.GeneratorOptions)
			p := f()
			err := kt.configureBuiltinPlugin(p, c, bpt)
			if err != nil {
				return nil, err
			}
			result = append(result, p)
		}
		return
	},

	builtinhelpers.HelmChartInflationGenerator: func(
		kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f gFactory) (
		result []resmap.Generator, err error) {
		var c struct {
			types.HelmGlobals
			types.HelmChart
		}
		var globals types.HelmGlobals
		if kt.kustomization.HelmGlobals != nil {
			globals = *kt.kustomization.HelmGlobals
		}
		for _, chart := range kt.kustomization.HelmCharts {
			c.HelmGlobals = globals
			c.HelmChart = chart
			p := f()
			if err = kt.configureBuiltinPlugin(p, c, bpt); err != nil {
				return nil, err
			}
			result = append(result, p)
		}
		return
	},
}

type tFactory func() resmap.TransformerPlugin

var transformerConfigurators = map[builtinhelpers.BuiltinPluginType]func(
	kt *KustTarget,
	bpt builtinhelpers.BuiltinPluginType,
	f tFactory,
	tc *builtinconfig.TransformerConfig) (result []resmap.Transformer, err error){
	builtinhelpers.NamespaceTransformer: func(
		kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
		result []resmap.Transformer, err error) {
		var c struct {
			types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
			FieldSpecs       []types.FieldSpec
		}
		c.Namespace = kt.kustomization.Namespace
		c.FieldSpecs = tc.NameSpace
		p := f()
		err = kt.configureBuiltinPlugin(p, c, bpt)
		if err != nil {
			return nil, err
		}
		result = append(result, p)
		return
	},

	builtinhelpers.PatchJson6902Transformer: func(
		kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, _ *builtinconfig.TransformerConfig) (
		result []resmap.Transformer, err error) {
		var c struct {
			Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
			Path   string          `json:"path,omitempty" yaml:"path,omitempty"`
			JsonOp string          `json:"jsonOp,omitempty" yaml:"jsonOp,omitempty"`
		}
		for _, args := range kt.kustomization.PatchesJson6902 {
			c.Target = args.Target
			c.Path = args.Path
			c.JsonOp = args.Patch
			p := f()
			err = kt.configureBuiltinPlugin(p, c, bpt)
			if err != nil {
				return nil, err
			}
			result = append(result, p)
		}
		return
	},
	builtinhelpers.PatchStrategicMergeTransformer: func(
		kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, _ *builtinconfig.TransformerConfig) (
		result []resmap.Transformer, err error) {
		if len(kt.kustomization.PatchesStrategicMerge) == 0 {
			return
		}
		var c struct {
			Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
		}
		c.Paths = kt.kustomization.PatchesStrategicMerge
		p := f()
		err = kt.configureBuiltinPlugin(p, c, bpt)
		if err != nil {
			return nil, err
		}
		result = append(result, p)
		return
	},
	builtinhelpers.PatchTransformer: func(
		kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, _ *builtinconfig.TransformerConfig) (
		result []resmap.Transformer, err error) {
		if len(kt.kustomization.Patches) == 0 {
			return
		}
		var c struct {
			Path    string          `json:"path,omitempty" yaml:"path,omitempty"`
			Patch   string          `json:"patch,omitempty" yaml:"patch,omitempty"`
			Target  *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
			Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"`
		}
		for _, pc := range kt.kustomization.Patches {
			c.Target = pc.Target
			c.Patch = pc.Patch
			c.Path = pc.Path
			c.Options = pc.Options
			p := f()
			err = kt.configureBuiltinPlugin(p, c, bpt)
			if err != nil {
				return nil, err
			}
			result = append(result, p)
		}
		return
	},
	builtinhelpers.LabelTransformer: func(
		kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
		result []resmap.Transformer, err error) {
		for _, label := range kt.kustomization.Labels {
			var c struct {
				Labels     map[string]string
				FieldSpecs []types.FieldSpec
			}
			c.Labels = label.Pairs
			fss := types.FsSlice(label.FieldSpecs)
			// merge the custom fieldSpecs with the default
			if label.IncludeSelectors {
				fss, err = fss.MergeAll(tc.CommonLabels)
			} else {
				// only add to metadata by default
				fss, err = fss.MergeOne(types.FieldSpec{Path: "metadata/labels", CreateIfNotPresent: true})
			}
			if err != nil {
				return nil, err
			}
			c.FieldSpecs = fss
			p := f()
			err = kt.configureBuiltinPlugin(p, c, bpt)
			if err != nil {
				return nil, err
			}
			result = append(result, p)
		}
		var c struct {
			Labels     map[string]string
			FieldSpecs []types.FieldSpec
		}
		c.Labels = kt.kustomization.CommonLabels
		c.FieldSpecs = tc.CommonLabels
		p := f()
		err = kt.configureBuiltinPlugin(p, c, bpt)
		if err != nil {
			return nil, err
		}
		result = append(result, p)
		return
	},
	builtinhelpers.AnnotationsTransformer: func(
		kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
		result []resmap.Transformer, err error) {
		var c struct {
			Annotations map[string]string
			FieldSpecs  []types.FieldSpec
		}
		c.Annotations = kt.kustomization.CommonAnnotations
		c.FieldSpecs = tc.CommonAnnotations
		p := f()
		err = kt.configureBuiltinPlugin(p, c, bpt)
		if err != nil {
			return nil, err
		}
		result = append(result, p)
		return
	},
	builtinhelpers.PrefixSuffixTransformer: func(
		kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
		result []resmap.Transformer, err error) {
		var c struct {
			Prefix     string
			Suffix     string
			FieldSpecs []types.FieldSpec
		}
		c.Prefix = kt.kustomization.NamePrefix
		c.Suffix = kt.kustomization.NameSuffix
		c.FieldSpecs = tc.NamePrefix
		p := f()
		err = kt.configureBuiltinPlugin(p, c, bpt)
		if err != nil {
			return nil, err
		}
		result = append(result, p)
		return
	},
	builtinhelpers.ImageTagTransformer: func(
		kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
		result []resmap.Transformer, err error) {
		var c struct {
			ImageTag   types.Image
			FieldSpecs []types.FieldSpec
		}
		for _, args := range kt.kustomization.Images {
			c.ImageTag = args
			c.FieldSpecs = tc.Images
			p := f()
			err = kt.configureBuiltinPlugin(p, c, bpt)
			if err != nil {
				return nil, err
			}
			result = append(result, p)
		}
		return
	},
	builtinhelpers.ReplicaCountTransformer: func(
		kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
		result []resmap.Transformer, err error) {
		var c struct {
			Replica    types.Replica
			FieldSpecs []types.FieldSpec
		}
		for _, args := range kt.kustomization.Replicas {
			c.Replica = args
			c.FieldSpecs = tc.Replicas
			p := f()
			err = kt.configureBuiltinPlugin(p, c, bpt)
			if err != nil {
				return nil, err
			}
			result = append(result, p)
		}
		return
	},
	// No kustomization file keyword for this yet.
	builtinhelpers.ValueAddTransformer: func(
		kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
		result []resmap.Transformer, err error) {
		return nil, fmt.Errorf("valueadd keyword not yet defined")
	},
}
