package backwardcompatibility

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/kyverno/kyverno/pkg/autogen"
	kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
	kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
	"github.com/kyverno/kyverno/pkg/config"
	dclient "github.com/kyverno/kyverno/pkg/dclient"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"sigs.k8s.io/controller-runtime/pkg/log"
)

// AddLabels - adds labels to all the existing generate requests
func AddLabels(client *kyvernoclient.Clientset, grInformer kyvernoinformer.GenerateRequestInformer) {
	// Get all the GR's that are existing
	// Extract and Update all of them with the with the labels
	grList, err := grInformer.Lister().List(labels.NewSelector())
	if err != nil {
		log.Log.Error(err, "failed to get generate request list")
		return
	}

	for _, gr := range grList {

		grLabels := gr.Labels
		if len(grLabels) == 0 {
			grLabels = make(map[string]string)
		}
		grLabels["generate.kyverno.io/policy-name"] = gr.Spec.Policy
		grLabels["generate.kyverno.io/resource-name"] = gr.Spec.Resource.Name
		grLabels["generate.kyverno.io/resource-kind"] = gr.Spec.Resource.Kind
		grLabels["generate.kyverno.io/resource-namespace"] = gr.Spec.Resource.Namespace

		gr.SetLabels(grLabels)

		_, err = client.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Update(context.TODO(), gr, metav1.UpdateOptions{})
		if err != nil {
			log.Log.V(4).Info(fmt.Sprintf("failed to update the GR %v. error: %v", gr.Name, err))
			for n := 0; n <= 3; n++ {
				log.Log.V(4).Info(fmt.Sprintf("retrying to get GR %v", gr.Name))
				time.Sleep(100 * time.Millisecond)
				errInGettingGR := addLabelForGR(gr.Name, gr.Namespace, client, grInformer)
				if errInGettingGR != nil {
					continue
				} else {
					break
				}
			}
		}
	}
}

func addLabelForGR(name string, namespace string, client *kyvernoclient.Clientset, grInformer kyvernoinformer.GenerateRequestInformer) error {
	gr, err := grInformer.Lister().GenerateRequests(namespace).Get(name)
	if err != nil {
		log.Log.Error(err, fmt.Sprintf("failed to update the GR %v", name))
		return err
	}

	grLabels := gr.Labels
	if len(grLabels) == 0 {
		grLabels = make(map[string]string)
	}
	grLabels["generate.kyverno.io/policy-name"] = gr.Spec.Policy
	grLabels["generate.kyverno.io/resource-name"] = gr.Spec.Resource.Name
	grLabels["generate.kyverno.io/resource-kind"] = gr.Spec.Resource.Kind
	grLabels["generate.kyverno.io/resource-namespace"] = gr.Spec.Resource.Namespace

	gr.SetLabels(grLabels)

	_, err = client.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Update(context.TODO(), gr, metav1.UpdateOptions{})
	if err != nil {
		log.Log.Error(err, fmt.Sprintf("failed to update the GR %v", gr.Name))
		return err
	}

	return nil
}

// AddCloneLabel - add label to the source resource about the new clone
func AddCloneLabel(client *dclient.Client, pInformer kyvernoinformer.ClusterPolicyInformer) {
	// Get all the Generate Policies which has clone
	// Get the resource with Kind, NameSpace, Name
	// Add Policy name if label not found
	policies, err := pInformer.Lister().List(labels.NewSelector())
	if err != nil {
		log.Log.Error(err, "failed to get policies")
		return
	}

	for _, policy := range policies {
		for _, rule := range autogen.ComputeRules(policy) {
			if rule.HasGenerate() {
				clone := rule.Generation.Clone
				if clone.Name != "" {
					namespace := clone.Namespace
					name := clone.Name
					kind := rule.Generation.Kind

					obj, err := client.GetResource("", kind, namespace, name)
					if err != nil {
						log.Log.Error(err, fmt.Sprintf("source not found  name:%v namespace:%v kind:%v", name, namespace, kind))
						continue
					}
					updateSource := true

					// add label
					label := obj.GetLabels()
					if len(label) == 0 {
						label = make(map[string]string)
						label["generate.kyverno.io/clone-policy-name"] = policy.GetName()
					} else {
						if label["generate.kyverno.io/clone-policy-name"] != "" {
							policyNames := label["generate.kyverno.io/clone-policy-name"]
							if !strings.Contains(policyNames, policy.GetName()) {
								policyNames = policyNames + "," + policy.GetName()
								label["generate.kyverno.io/clone-policy-name"] = policyNames
							} else {
								updateSource = false
							}
						} else {
							label["generate.kyverno.io/clone-policy-name"] = policy.GetName()
						}
					}

					if updateSource {
						log.Log.V(4).Info("updating existing clone source")
						obj.SetLabels(label)
						_, err = client.UpdateResource(obj.GetAPIVersion(), kind, namespace, obj, false)
						if err != nil {
							log.Log.Error(err, fmt.Sprintf("failed to update source  name:%v namespace:%v kind:%v\n", obj.GetName(), obj.GetNamespace(), obj.GetKind()))
							return
						}
						log.Log.V(4).Info(fmt.Sprintf("updated source  name:%v namespace:%v kind:%v\n", obj.GetName(), obj.GetNamespace(), obj.GetKind()))
					}
				}
			}
		}
	}
}
