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

package kioutil

import (
	"fmt"
	"sort"
	"strconv"

	"sigs.k8s.io/kustomize/kyaml/errors"
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

type AnnotationKey = string

const (
	// IndexAnnotation records the index of a specific resource in a file or input stream.
	IndexAnnotation AnnotationKey = "config.kubernetes.io/index"

	// PathAnnotation records the path to the file the Resource was read from
	PathAnnotation AnnotationKey = "config.kubernetes.io/path"

	// PackageAnnotation records the name of the package the Resource was read from
	PackageAnnotation AnnotationKey = "config.kubernetes.io/package"
)

func GetFileAnnotations(rn *yaml.RNode) (string, string, error) {
	meta, err := rn.GetMeta()
	if err != nil {
		return "", "", err
	}
	path := meta.Annotations[PathAnnotation]
	index := meta.Annotations[IndexAnnotation]
	return path, index, nil
}

// ErrorIfMissingAnnotation validates the provided annotations are present on the given resources
func ErrorIfMissingAnnotation(nodes []*yaml.RNode, keys ...AnnotationKey) error {
	for _, key := range keys {
		for _, node := range nodes {
			val, err := node.Pipe(yaml.GetAnnotation(key))
			if err != nil {
				return errors.Wrap(err)
			}
			if val == nil {
				return errors.Errorf("missing package annotation %s", key)
			}
		}
	}
	return nil
}

// Map invokes fn for each element in nodes.
func Map(nodes []*yaml.RNode, fn func(*yaml.RNode) (*yaml.RNode, error)) ([]*yaml.RNode, error) {
	var returnNodes []*yaml.RNode
	for i := range nodes {
		n, err := fn(nodes[i])
		if err != nil {
			return nil, errors.Wrap(err)
		}
		if n != nil {
			returnNodes = append(returnNodes, n)
		}
	}
	return returnNodes, nil
}

func MapMeta(nodes []*yaml.RNode, fn func(*yaml.RNode, yaml.ResourceMeta) (*yaml.RNode, error)) (
	[]*yaml.RNode, error) {
	var returnNodes []*yaml.RNode
	for i := range nodes {
		meta, err := nodes[i].GetMeta()
		if err != nil {
			return nil, errors.Wrap(err)
		}
		n, err := fn(nodes[i], meta)
		if err != nil {
			return nil, errors.Wrap(err)
		}
		if n != nil {
			returnNodes = append(returnNodes, n)
		}
	}
	return returnNodes, nil
}

// SortNodes sorts nodes in place:
// - by PathAnnotation annotation
// - by IndexAnnotation annotation
func SortNodes(nodes []*yaml.RNode) error {
	var err error
	// use stable sort to keep ordering of equal elements
	sort.SliceStable(nodes, func(i, j int) bool {
		if err != nil {
			return false
		}
		var iMeta, jMeta yaml.ResourceMeta
		if iMeta, _ = nodes[i].GetMeta(); err != nil {
			return false
		}
		if jMeta, _ = nodes[j].GetMeta(); err != nil {
			return false
		}

		iValue := iMeta.Annotations[PathAnnotation]
		jValue := jMeta.Annotations[PathAnnotation]
		if iValue != jValue {
			return iValue < jValue
		}

		iValue = iMeta.Annotations[IndexAnnotation]
		jValue = jMeta.Annotations[IndexAnnotation]

		// put resource config without an index first
		if iValue == jValue {
			return false
		}
		if iValue == "" {
			return true
		}
		if jValue == "" {
			return false
		}

		// sort by index
		var iIndex, jIndex int
		iIndex, err = strconv.Atoi(iValue)
		if err != nil {
			err = fmt.Errorf("unable to parse config.kubernetes.io/index %s :%v", iValue, err)
			return false
		}
		jIndex, err = strconv.Atoi(jValue)
		if err != nil {
			err = fmt.Errorf("unable to parse config.kubernetes.io/index %s :%v", jValue, err)
			return false
		}
		if iIndex != jIndex {
			return iValue < jValue
		}

		// elements are equal
		return false
	})
	return errors.Wrap(err)
}
