// Copyright 2019 Istio Authors
//
// Licensed 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 helmreconciler

import (
	"context"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"istio.io/istio/operator/pkg/object"
	"istio.io/pkg/log"
)

// Prune removes any resources not specified in manifests generated by HelmReconciler h. If all is set to true, this
// function prunes all resources.
func (h *HelmReconciler) Prune(excluded map[string]bool, all bool) error {
	allErrors := []error{}
	namespacedResources, clusterResources := h.customizer.PruningDetails().GetResourceTypes()
	targetNamespace := h.customizer.Input().GetTargetNamespace()
	err := h.PruneUnlistedResources(append(namespacedResources, clusterResources...), excluded, all, targetNamespace)
	if all {
		h.FlushObjectCaches()
	}
	if err != nil {
		allErrors = append(allErrors, err)
	}
	return utilerrors.NewAggregate(allErrors)
}

func (h *HelmReconciler) PruneUnlistedResources(gvks []schema.GroupVersionKind, excluded map[string]bool, all bool, namespace string) error {
	allErrors := []error{}
	ownerLabels := h.customizer.PruningDetails().GetOwnerLabels()
	for _, gvk := range gvks {
		objects := &unstructured.UnstructuredList{}
		objects.SetGroupVersionKind(gvk)
		err := h.client.List(context.TODO(), objects, client.MatchingLabels(ownerLabels), client.InNamespace(namespace))
		if err != nil {
			// we only want to retrieve resources clusters
			log.Warnf("retrieving resources to prune type %s: %s not found", gvk.String(), err)
			continue
		}
		for _, o := range objects.Items {
			oh := object.NewK8sObject(&o, nil, nil).Hash()
			if excluded[oh] && !all {
				continue
			}
			err = h.client.Delete(context.TODO(), &o, client.PropagationPolicy(metav1.DeletePropagationBackground))
			if err != nil {
				allErrors = append(allErrors, err)
			}
			log.Infof("Pruned object %s.", oh)

		}
	}
	return utilerrors.NewAggregate(allErrors)
}
