// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//     http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 pod

import (
	"fmt"
	"strings"

	"github.com/aws/amazon-vpc-resource-controller-k8s/pkg/config"
	v1 "k8s.io/api/core/v1"
	metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/cache"
)

// PodConverter implements the interface to convert k8s pod object to a stripped down
// version of pod to save on memory utilized
type PodConverter struct {
	K8sResource     string
	K8sResourceType runtime.Object
}

// ConcertObject converts original pod object to stripped down pod object
func (c *PodConverter) ConvertObject(originalObj interface{}) (convertedObj interface{}, err error) {
	pod, ok := originalObj.(*v1.Pod)
	if !ok {
		return nil, fmt.Errorf("failed to convert object to pod")
	}
	return c.StripDownPod(pod), nil
}

// ConcertList converts the original pod list to stripped down list of pod objects
func (c *PodConverter) ConvertList(originalList interface{}) (convertedList interface{}, err error) {
	podList, ok := originalList.(*v1.PodList)
	if !ok {
		return nil, fmt.Errorf("faield to convert object to pod list")
	}
	// We need to set continue in order to allow the pagination to work on converted
	// pod list object
	strippedPodList := v1.PodList{
		ListMeta: metaV1.ListMeta{
			Continue:        podList.Continue,
			ResourceVersion: podList.ResourceVersion,
		},
	}
	for _, pod := range podList.Items {
		pod := pod // Fix gosec G601, so we can use &node
		strippedPod := c.StripDownPod(&pod)
		strippedPodList.Items = append(strippedPodList.Items, *strippedPod)
	}
	return &strippedPodList, nil
}

// Resource to watch and list
func (c *PodConverter) Resource() string {
	return c.K8sResource
}

// ResourceType to watch and list
func (c *PodConverter) ResourceType() runtime.Object {
	return c.K8sResourceType
}

// NodeNameIndexer returns indexer to index in the data store using node name
func NodeNameIndexer() cache.Indexers {
	indexer := map[string]cache.IndexFunc{}
	indexer[NodeNameSpec] = func(obj interface{}) (strings []string, err error) {
		return []string{obj.(*v1.Pod).Spec.NodeName}, nil
	}
	return indexer
}

// NSKeyIndexer is the key function to index the pod object using namespace/name
func (c *PodConverter) Indexer(obj interface{}) (string, error) {
	pod := obj.(*v1.Pod)
	return types.NamespacedName{
		Namespace: pod.Namespace,
		Name:      pod.Name,
	}.String(), nil
}

// StripDownPod removes all the extra details from pod that are not
// required by the controller.
func (c *PodConverter) StripDownPod(pod *v1.Pod) *v1.Pod {
	return &v1.Pod{
		ObjectMeta: metaV1.ObjectMeta{
			Name:              pod.Name,
			Namespace:         pod.Namespace,
			UID:               pod.UID,
			DeletionTimestamp: pod.DeletionTimestamp,
			Annotations:       getVPCControllerAnnotations(pod.Annotations),
		},
		Spec: v1.PodSpec{
			Containers:         getContainersWithVPCLimits(pod.Spec.Containers),
			ServiceAccountName: pod.Spec.ServiceAccountName,
			NodeName:           pod.Spec.NodeName,
		},
		Status: v1.PodStatus{
			Phase: pod.Status.Phase,
		},
	}
}

// getVPCControllerAnnotations returns only the annotations that were marked by VPC
// Resource controller
func getVPCControllerAnnotations(annotations map[string]string) map[string]string {
	strippedDownAnnotations := map[string]string{}
	for key, val := range annotations {
		if strings.HasPrefix(key, config.VPCResourcePrefix) {
			strippedDownAnnotations[key] = val
		}
	}
	return strippedDownAnnotations
}

// getContainersWithVPCLimits returns only the container limits for vpc controller
// resources
func getContainersWithVPCLimits(containers []v1.Container) []v1.Container {
	var strippedContainers []v1.Container
	for _, container := range containers {
		add := false
		strippedContainer := v1.Container{
			Resources: v1.ResourceRequirements{
				Requests: v1.ResourceList{},
			},
		}
		for limitKey, limitVal := range container.Resources.Requests {
			if strings.HasPrefix(limitKey.String(), config.VPCResourcePrefix) {
				strippedContainer.Resources.Requests[limitKey] = limitVal
				add = true
			}
		}
		if add {
			strippedContainers = append(strippedContainers, strippedContainer)
		}
	}
	return strippedContainers
}
