// Licensed Materials - Property of IBM
// (c) Copyright IBM Corporation 2018, 2019. All Rights Reserved.
// Note to U.S. Government Users Restricted Rights:
// Use, duplication or disclosure restricted by GSA ADP Schedule
// Contract with IBM Corp.

// Package common contains common definitions and shared utilities for the different controllers
package common

import (
	"github.com/golang/glog"
	placement "github.com/open-cluster-management/multicloud-operators-foundation/pkg/apis/mcm/v1alpha1"
	policyv1alpha1 "github.com/open-cluster-management/hcm-compliance/pkg/apis/policy/v1alpha1"
	"github.com/open-cluster-management/seed-sdk/pkg/context"
	resv1 "github.com/open-cluster-management/seed-sdk/pkg/types/apis/resource/v1"
	core "k8s.io/api/core/v1"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes"
	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/tools/record"
	"k8s.io/cluster-registry/pkg/apis/clusterregistry/v1alpha1"
)

// Finalizer for hcm resources
const Finalizer = ".finalizer.mcm.ibm.com"
const OldFinalizer = "finalizer.mcm.ibm.com"

// HcmNamespace - TODO should be configurable
const HcmNamespace = "mcm"

// BuildConfig returns a config
func BuildConfig(kubeconfig *string) (*rest.Config, error) {
	var config *rest.Config
	var err error
	if *kubeconfig != "" { // off-cluster
		config, err = clientcmd.BuildConfigFromFlags("", *kubeconfig)
		if err != nil {
			return nil, err
		}
	} else { // in-cluster
		config, err = rest.InClusterConfig()
		if err != nil {
			return nil, err
		}
	}
	return config, nil
}

func RemoveFinalizerAndPut(ctx context.Context, obj runtime.Object, finalizers []string) error {
	glog.V(6).Infof("RemoveFinalizerAndPut from %s finalizers %v", obj.(metav1.ObjectMetaAccessor).GetObjectMeta().GetSelfLink(), finalizers)
	update := false
	for _, finName := range finalizers {
		if resv1.HasFinalizer(obj, finName) {
			resv1.RemoveFinalizer(obj, finName)
			update = true
		}
	}
	if update {
		if err := resv1.Put(obj, ctx.ResourceClient()); err != nil {
			ctx.Lens().Errorf("Error setting finalizer: %v", err)
			return err
		}
	}
	return nil
}

// IsInClusterNamespace check if policy is in cluster namespace
func IsInClusterNamespace(ns string, allClusters []*v1alpha1.Cluster) bool {
	for _, cluster := range allClusters {
		if ns == cluster.GetNamespace() {
			return true
		}
	}
	return false
}

// ConvertLabels returns label
func ConvertLabels(labelSelector *metav1.LabelSelector) (labels.Selector, error) {
	if labelSelector != nil {
		selector, err := metav1.LabelSelectorAsSelector(labelSelector)
		if err != nil {
			glog.Errorf("ConvertLabels error: %s\n", err)
			return labels.Nothing(), err
		}

		return selector, nil
	}

	return labels.Nothing(), nil
}

// IsMached2 checks that the cluster name and conditions match the selector
func IsMached2(cluster *v1alpha1.Cluster, clusterNames *[]string, conditions *[]placement.ClusterConditionFilter) bool {

	mached := false
	if clusterNames != nil && len(*clusterNames) > 0 {
		for _, name := range *clusterNames {
			if cluster.Name == name {
				mached = true
				break
			}
		}
		if !mached {
			return false
		}
	}

	if conditions != nil && len(*conditions) > 0 {
		clConditions := map[v1alpha1.ClusterConditionType]core.ConditionStatus{}
		for _, cond := range cluster.Status.Conditions {
			clConditions[cond.Type] = cond.Status
		}
		for _, selCond := range *conditions {
			if clConditions[selCond.Type] != selCond.Status {
				return false
			}
		}
	}
	return true

}

// HasOwnerReferencesOf check if obj is created by given propogator
func HasOwnerReferencesOf(obj runtime.Object, kind string) bool {
	objMeta := obj.(metav1.ObjectMetaAccessor).GetObjectMeta()
	ownerReferences := objMeta.GetOwnerReferences()
	for _, ownerRef := range ownerReferences {
		if ownerRef.Kind == kind {
			glog.V(6).Infof("HasOwnerReferencesOf: object %s has ownerRef.Kind `%s` == `kind` %s", objMeta.GetSelfLink(), ownerRef.Kind, kind)
			return true
		}
	}
	glog.V(6).Infof("HasOwnerReferencesOf: object %s doesn't have ownerReferenceOf %s", objMeta.GetSelfLink(), kind)
	return false
}

// RemoveStatusInSpec clean up status field on localobject before deepEqual compare
func RemoveStatusInSpec(obj runtime.Object) *policyv1alpha1.Policy {
	plc := obj.DeepCopyObject().(*policyv1alpha1.Policy)
	for _, objectT := range plc.Spec.ObjectTemplates {
		objectT.Status = policyv1alpha1.TemplateStatus{}
	}
	for _, policyT := range plc.Spec.PolicyTemplates {
		policyT.Status = policyv1alpha1.TemplateStatus{}
	}
	for _, roleT := range plc.Spec.RoleTemplates {
		roleT.Status = policyv1alpha1.TemplateStatus{}
	}
	for _, roleBindingT := range plc.Spec.RoleBindingTemplates {
		roleBindingT.Status = policyv1alpha1.TemplateStatus{}
	}

	for _, genericT := range plc.Spec.GenericTemplates {
		genericT.Status = policyv1alpha1.GenericTemplateStatus{}
	}
	return plc
}

// CreateRecorder return recorder
func CreateRecorder(kubeClient kubernetes.Interface, componentName string) (record.EventRecorder, error) {
	eventsScheme := runtime.NewScheme()
	if err := v1.AddToScheme(eventsScheme); err != nil {
		glog.Errorf("Error add to scheme : %v", err)
		return nil, err
	}

	eventBroadcaster := record.NewBroadcaster()
	eventBroadcaster.StartLogging(glog.Infof)
	eventBroadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")})

	return eventBroadcaster.NewRecorder(eventsScheme, v1.EventSource{Component: componentName}), nil
}
