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

package imagetag

import (
	"sigs.k8s.io/kustomize/api/internal/utils"
	"sigs.k8s.io/kustomize/api/types"
	"sigs.k8s.io/kustomize/kyaml/errors"
	"sigs.k8s.io/kustomize/kyaml/kio"
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

// LegacyFilter is an implementation of the kio.Filter interface
// that scans through the provided kyaml data structure and updates
// any values of any image fields that is inside a sequence under
// a field called either containers or initContainers. The field is only
// update if it has a value that matches and image reference and the name
// of the image is a match with the provided ImageTag.
type LegacyFilter struct {
	ImageTag types.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"`
}

var _ kio.Filter = LegacyFilter{}

func (lf LegacyFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
	return kio.FilterAll(yaml.FilterFunc(lf.filter)).Filter(nodes)
}

func (lf LegacyFilter) filter(node *yaml.RNode) (*yaml.RNode, error) {
	meta, err := node.GetMeta()
	if err != nil {
		return nil, err
	}

	// We do not make any changes if the type of the resource
	// is CustomResourceDefinition.
	if meta.Kind == `CustomResourceDefinition` {
		return node, nil
	}

	fff := findFieldsFilter{
		fields:        []string{"containers", "initContainers"},
		fieldCallback: checkImageTagsFn(lf.ImageTag),
	}
	if err := node.PipeE(fff); err != nil {
		return nil, err
	}
	return node, nil
}

type fieldCallback func(node *yaml.RNode) error

// findFieldsFilter is an implementation of the kio.Filter
// interface. It will walk the data structure and look for fields
// that matches the provided list of field names. For each match,
// the value of the field will be passed in as a parameter to the
// provided fieldCallback.
// TODO: move this to kyaml/filterutils
type findFieldsFilter struct {
	fields []string

	fieldCallback fieldCallback
}

func (f findFieldsFilter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
	return obj, f.walk(obj)
}

func (f findFieldsFilter) walk(node *yaml.RNode) error {
	switch node.YNode().Kind {
	case yaml.MappingNode:
		return node.VisitFields(func(n *yaml.MapNode) error {
			err := f.walk(n.Value)
			if err != nil {
				return err
			}
			key := n.Key.YNode().Value
			if utils.StringSliceContains(f.fields, key) {
				return f.fieldCallback(n.Value)
			}
			return nil
		})
	case yaml.SequenceNode:
		return errors.Wrap(node.VisitElements(f.walk))
	}
	return nil
}

func checkImageTagsFn(imageTag types.Image) fieldCallback {
	return func(node *yaml.RNode) error {
		if node.YNode().Kind != yaml.SequenceNode {
			return nil
		}

		return node.VisitElements(func(n *yaml.RNode) error {
			// Look up any fields on the provided node that is named
			// image.
			return n.PipeE(yaml.Get("image"), imageTagUpdater{
				ImageTag: imageTag,
			})
		})
	}
}
