// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package kubernetes

import (
	"strings"

	"github.com/elastic/beats/libbeat/common"
	"github.com/elastic/beats/libbeat/common/safemapstr"
)

// MetaGenerator builds metadata objects for pods and containers
type MetaGenerator interface {
	// ResourceMetadata generates metadata for the given kubernetes object taking to account certain filters
	ResourceMetadata(obj Resource) common.MapStr

	// PodMetadata generates metadata for the given pod taking to account certain filters
	PodMetadata(pod *Pod) common.MapStr

	// Containermetadata generates metadata for the given container of a pod
	ContainerMetadata(pod *Pod, container string) common.MapStr
}

// MetaGeneratorConfig settings
type MetaGeneratorConfig struct {
	IncludeLabels      []string `config:"include_labels"`
	ExcludeLabels      []string `config:"exclude_labels"`
	IncludeAnnotations []string `config:"include_annotations"`

	// Undocumented settings, to be deprecated in favor of `drop_fields` processor:
	IncludePodUID          bool `config:"include_pod_uid"`
	IncludeCreatorMetadata bool `config:"include_creator_metadata"`
}

type metaGenerator = MetaGeneratorConfig

// NewMetaGenerator initializes and returns a new kubernetes metadata generator
func NewMetaGenerator(cfg *common.Config) (MetaGenerator, error) {
	// default settings:
	generator := metaGenerator{
		IncludeCreatorMetadata: true,
	}

	err := cfg.Unpack(&generator)
	return &generator, err
}

// NewMetaGeneratorFromConfig initializes and returns a new kubernetes metadata generator
func NewMetaGeneratorFromConfig(cfg *MetaGeneratorConfig) MetaGenerator {
	return cfg
}

// ResourceMetadata generates metadata for the given kubernetes object taking to account certain filters
func (g *metaGenerator) ResourceMetadata(obj Resource) common.MapStr {
	objMeta := obj.GetMetadata()
	labelMap := common.MapStr{}
	if len(g.IncludeLabels) == 0 {
		for k, v := range obj.GetMetadata().Labels {
			safemapstr.Put(labelMap, k, v)
		}
	} else {
		labelMap = generateMapSubset(objMeta.Labels, g.IncludeLabels)
	}

	// Exclude any labels that are present in the exclude_labels config
	for _, label := range g.ExcludeLabels {
		delete(labelMap, label)
	}

	annotationsMap := generateMapSubset(objMeta.Annotations, g.IncludeAnnotations)
	meta := common.MapStr{}
	if objMeta.GetNamespace() != "" {
		meta["namespace"] = objMeta.GetNamespace()
	}

	// Add controller metadata if present
	if g.IncludeCreatorMetadata {
		for _, ref := range objMeta.OwnerReferences {
			if ref.GetController() {
				switch ref.GetKind() {
				// TODO grow this list as we keep adding more `state_*` metricsets
				case "Deployment",
					"ReplicaSet",
					"StatefulSet":
					safemapstr.Put(meta, strings.ToLower(ref.GetKind())+".name", ref.GetName())
				}
			}
		}
	}

	if len(labelMap) != 0 {
		meta["labels"] = labelMap
	}

	if len(annotationsMap) != 0 {
		meta["annotations"] = annotationsMap
	}

	return meta
}

// PodMetadata generates metadata for the given pod taking to account certain filters
func (g *metaGenerator) PodMetadata(pod *Pod) common.MapStr {
	podMeta := g.ResourceMetadata(pod)

	// Add UID metadata if enabled
	if g.IncludePodUID {
		safemapstr.Put(podMeta, "pod.uid", pod.GetMetadata().GetUid())
	}

	safemapstr.Put(podMeta, "pod.name", pod.GetMetadata().GetName())
	safemapstr.Put(podMeta, "node.name", pod.Spec.GetNodeName())

	return podMeta
}

// Containermetadata generates metadata for the given container of a pod
func (g *metaGenerator) ContainerMetadata(pod *Pod, container string) common.MapStr {
	podMeta := g.PodMetadata(pod)

	// Add container details
	podMeta["container"] = common.MapStr{
		"name": container,
	}

	return podMeta
}

func generateMapSubset(input map[string]string, keys []string) common.MapStr {
	output := common.MapStr{}
	if input == nil {
		return output
	}

	for _, key := range keys {
		value, ok := input[key]
		if ok {
			safemapstr.Put(output, key, value)
		}
	}

	return output
}
