// Copyright 2019 Altinity Ltd and/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.
// 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 labeler

import (
	"fmt"
	"github.com/altinity/clickhouse-operator/pkg/chop"
	"strings"

	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
	k8sLabels "k8s.io/apimachinery/pkg/labels"

	api "github.com/altinity/clickhouse-operator/pkg/apis/clickhouse.altinity.com/v1"
	"github.com/altinity/clickhouse-operator/pkg/util"
)

func (l *Labeler) appendConfigLabels(host *api.Host, labels map[string]string) map[string]string {
	if !host.HasCurStatefulSet() {
		return labels
	}
	// Have CurStatefulSet
	stsLabels := host.Runtime.CurStatefulSet.GetLabels()
	if stsLabels == nil {
		return labels
	}
	// Have labels
	if val, exists := stsLabels[l.Get(LabelZookeeperConfigVersion)]; exists {
		labels[l.Get(LabelZookeeperConfigVersion)] = val
	}
	if val, exists := stsLabels[l.Get(LabelSettingsConfigVersion)]; exists {
		labels[l.Get(LabelSettingsConfigVersion)] = val
	}
	//labels[l.Get(ZookeeperConfigVersion] = host.Config.ZookeeperFingerprint
	//labels[l.Get(SettingsConfigVersion] = host.Config.SettingsFingerprint
	return labels
}

// GetReclaimPolicy gets reclaim policy from meta
func (l *Labeler) GetReclaimPolicy(meta meta.Object) api.PVCReclaimPolicy {
	defaultReclaimPolicy := api.PVCReclaimPolicyDelete

	labels := meta.GetLabels()
	if labels == nil {
		return defaultReclaimPolicy
	}

	if value, ok := labels[l.Get(LabelPVCReclaimPolicyName)]; ok {
		reclaimPolicy := api.NewPVCReclaimPolicyFromString(value)
		if reclaimPolicy.IsValid() {
			return reclaimPolicy
		}
	}

	return defaultReclaimPolicy
}

// makeSetFromObjectMeta makes k8sLabels.Set from ObjectMeta
func (l *Labeler) MakeSetFromObjectMeta(meta meta.Object) (k8sLabels.Set, error) {
	// Check mandatory labels are in place
	if !util.MapHasKeys(meta.GetLabels(), l.Get(LabelNamespace), l.Get(LabelAppName), l.Get(LabelCRName)) {
		return nil, fmt.Errorf(
			"UNABLE to make set from object. Need to have at least labels '%s', '%s' and '%s'. Available Labels: %v",
			l.Get(LabelNamespace), l.Get(LabelAppName), l.Get(LabelCRName), meta.GetLabels(),
		)
	}

	labels := []string{
		// Mandatory labels
		l.Get(LabelNamespace),
		l.Get(LabelAppName),
		l.Get(LabelCRName),

		// Optional labels
		l.Get(LabelClusterName),
		l.Get(LabelShardName),
		l.Get(LabelReplicaName),
		l.Get(LabelConfigMap),
		l.Get(LabelService),
	}

	set := k8sLabels.Set{}
	util.MergeStringMapsOverwrite(set, meta.GetLabels(), labels...)

	// skip StatefulSet
	// skip Zookeeper

	return set, nil
}

// MakeSelectorFromObjectMeta makes selector from meta
// TODO review usage
func (l *Labeler) MakeSelectorFromObjectMeta(meta meta.Object) (k8sLabels.Selector, error) {
	set, err := l.MakeSetFromObjectMeta(meta)
	if err != nil {
		// Unable to make set
		return nil, err
	}
	return k8sLabels.SelectorFromSet(set), nil
}

// IsCHOPGeneratedObject check whether object is generated by an operator. Check is label-based
func (l *Labeler) IsCHOPGeneratedObject(meta meta.Object) bool {
	labels := meta.GetLabels()
	if !util.MapHasKeys(labels, l.Get(LabelAppName)) {
		return false
	}
	return labels[l.Get(LabelAppName)] == l.Get(LabelAppValue)
}

// GetCRNameFromObjectMeta extracts CR name from ObjectMeta. Based on labels.
func (l *Labeler) GetCRNameFromObjectMeta(meta meta.Object) (string, error) {
	labels := meta.GetLabels()
	if !util.MapHasKeys(labels, l.Get(LabelCRName)) {
		return "", fmt.Errorf("unable to find in meta.labels required label: '%s'", l.Get(LabelCRName))
	}
	return labels[l.Get(LabelCRName)], nil
}

// GetClusterNameFromObjectMeta extracts cluster name from ObjectMeta. Based on labels.
func (l *Labeler) GetClusterNameFromObjectMeta(meta meta.Object) (string, error) {
	labels := meta.GetLabels()
	if !util.MapHasKeys(labels, l.Get(LabelClusterName)) {
		return "", fmt.Errorf("unable to find in meta.labels required label: '%s'", l.Get(LabelClusterName))
	}
	return labels[l.Get(LabelClusterName)], nil
}

// MakeObjectVersion makes object version label
func (l *Labeler) MakeObjectVersion(meta meta.Object, obj interface{}) {
	meta.SetLabels(
		util.MergeStringMapsOverwrite(
			meta.GetLabels(),
			map[string]string{
				l.Get(LabelObjectVersion): util.Fingerprint(obj),
			},
		),
	)
}

// GetObjectVersion gets version of the object
func (l *Labeler) GetObjectVersion(meta meta.Object) (string, bool) {
	labels := meta.GetLabels()
	if labels == nil {
		return "", false
	}
	label, ok := labels[l.Get(LabelObjectVersion)]
	return label, ok
}

// GetCHOpSignature gets CHOp signature
func (l *Labeler) GetCHOpSignature() map[string]string {
	return map[string]string{
		l.Get(LabelAppName):    l.Get(LabelAppValue),
		l.Get(LabelCHOP):       chop.Get().Version,
		l.Get(LabelCHOPCommit): chop.Get().Commit,
		l.Get(LabelCHOPDate):   strings.ReplaceAll(chop.Get().Date, ":", "."),
	}
}

// appendKeyReady sets "Ready" key to Ready state (used with labels and annotations)
func (l *Labeler) appendKeyReady(dst map[string]string) map[string]string {
	return util.MergeStringMapsOverwrite(
		dst,
		map[string]string{
			l.Get(LabelReadyName): l.Get(LabelReadyValueReady),
		},
	)
}

// deleteKeyReady sets "Ready" key to NotReady state (used with labels and annotations)
func (l *Labeler) deleteKeyReady(dst map[string]string) map[string]string {
	return util.MergeStringMapsOverwrite(
		dst,
		map[string]string{
			l.Get(LabelReadyName): l.Get(LabelReadyValueNotReady),
		},
	)
}

// hasKeyReady checks whether "Ready" key has Ready state (used with labels and annotations)
func (l *Labeler) hasKeyReady(src map[string]string) bool {
	if _, ok := src[l.Get(LabelReadyName)]; ok {
		return src[l.Get(LabelReadyName)] == l.Get(LabelReadyValueReady)
	}
	return false
}

// AppendLabelReady appends "Ready" label to ObjectMeta.Labels.
// Returns true in case label was not in place and was added.
func (l *Labeler) AppendLabelReady(meta meta.Object) bool {
	if meta == nil {
		// Nowhere to add to, not added
		return false
	}
	if l.hasKeyReady(meta.GetLabels()) {
		// Already in place, value not added
		return false
	}
	// Need to add
	meta.SetLabels(l.appendKeyReady(meta.GetLabels()))
	return true
}

// DeleteLabelReady deletes "Ready" label from ObjectMeta.Labels
// Returns true in case label was in place and was deleted.
func (l *Labeler) DeleteLabelReady(meta meta.Object) bool {
	if meta == nil {
		// Nowhere to delete from, not deleted
		return false
	}
	if l.hasKeyReady(meta.GetLabels()) {
		// In place, need to delete
		meta.SetLabels(l.deleteKeyReady(meta.GetLabels()))
		return true
	}
	// Not available, not deleted
	return false
}

// AppendAnnotationReady appends "Ready" annotation to ObjectMeta.Annotations
// Returns true in case annotation was not in place and was added.
func (l *Labeler) AppendAnnotationReady(meta meta.Object) bool {
	if meta == nil {
		// Nowhere to add to, not added
		return false
	}
	if l.hasKeyReady(meta.GetAnnotations()) {
		// Already in place, not added
		return false
	}
	// Need to add
	meta.SetAnnotations(l.appendKeyReady(meta.GetAnnotations()))
	return true
}

// DeleteAnnotationReady deletes "Ready" annotation from ObjectMeta.Annotations
// Returns true in case annotation was in place and was deleted.
func (l *Labeler) DeleteAnnotationReady(meta meta.Object) bool {
	if meta == nil {
		// Nowhere to delete from, not deleted
		return false
	}
	if l.hasKeyReady(meta.GetAnnotations()) {
		// In place, need to delete
		meta.SetAnnotations(l.deleteKeyReady(meta.GetAnnotations()))
		return true
	}
	// Not available, not deleted
	return false
}
