package converter

import (
	"errors"
	"fmt"
	"strings"

	runtimeutil "github.com/RafayLabs/rcloud-base/pkg/controller/runtime"
	"github.com/RafayLabs/rcloud-base/pkg/hasher"

	"github.com/RafayLabs/rcloud-base/pkg/log"
	controllerv2 "github.com/RafayLabs/rcloud-base/proto/types/controller"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	kjson "k8s.io/apimachinery/pkg/runtime/serializer/json"
	k8sapijson "sigs.k8s.io/kustomize/pseudo/k8s/apimachinery/pkg/runtime/serializer/json"
)

const (
	delimiter                         = "---"
	ingressAnnotationConfigSnippetKey = "nginx.ingress.kubernetes.io/configuration-snippet"
)

var (
	// ErrInvalidObject is retuned for invalid object
	ErrInvalidObject = errors.New("object does not support object interface")
	json             = k8sapijson.CaseSensitiveJsonIterator()
	dmf              = kjson.DefaultMetaFactory
)

var _log = log.GetLogger()

func getIngressAnnotations(name string, orgID, partnerID string) map[string]string {
	orgHash, err := hasher.HashFromHex(orgID)
	if err != nil {
		orgHash = "unknown"
	}
	partnerHash, err := hasher.HashFromHex(partnerID)
	if err != nil {
		partnerHash = "unknown"
	}
	return map[string]string{
		ingressAnnotationConfigSnippetKey: fmt.Sprintf("set $workload_name \"%s\";set $orgId \"%s\";set $partnerId \"%s\";", name, orgHash, partnerHash),
	}
}

func addIngressAnnotations(annotations map[string]string, name string, orgId, partnerId string) {
	if _, ok := annotations[ingressAnnotationConfigSnippetKey]; !ok {
		orgHash, err := hasher.HashFromHex(orgId)
		if err != nil {
			orgHash = "unknown"
		}
		partnerHash, err := hasher.HashFromHex(partnerId)
		if err != nil {
			partnerHash = "unknown"
		}
		annotations[ingressAnnotationConfigSnippetKey] = fmt.Sprintf("set $workload_name \"%s\";set $orgId \"%s\";set $partnerId \"%s\";",
			name, orgHash, partnerHash)
	}
}

func addDebugLabels(stepTemplate *controllerv2.StepTemplate, debugLabels []byte) error {

	if stepTemplate.Object != nil {
		accessor, err := stepTemplate.Object.Accessor()
		if err != nil {
			return err
		}
		kind, err := accessor.Kind()
		if err != nil {
			return err
		}
		_log.Infow("addDebugLabels", "kind", kind)
		switch strings.ToLower(kind) {
		case "pod":
			accessor.SetRaw(debugLabels, "metadata", "labels")
		case "deployment", "replicationcontroller", "replicaset", "statefulset", "daemonset", "job":
			accessor.SetRaw(debugLabels, "spec", "template", "metadata", "labels")
		case "cronjob":
			accessor.SetRaw(debugLabels, "spec", "jobTemplate", "spec", "template", "metadata", "labels")
		default:
			_log.Warnw("Unknown Install component in TaskSet. Debug is not possible.", "Kind:", kind)
			return nil
		}

		stepTemplate.Object.Raw = accessor.Bytes()

	}

	return nil
}

func getDebugLabelsMap(orgID, partnerID, projectID string, name string, isSystemWorkload bool) (map[string]string, error) {
	orgHashID, err := hasher.HashFromHex(orgID)
	if err != nil {
		err = fmt.Errorf("failed to convert org id %s %s", orgID, err.Error())
		return nil, err
	}
	partnerHashID, err := hasher.HashFromHex(partnerID)
	if err != nil {
		err = fmt.Errorf("failed to convert partner id %s %s", partnerID, err.Error())
		return nil, err
	}
	projectHashID, err := hasher.HashFromHex(projectID)
	if err != nil {
		err = fmt.Errorf("failed to convert project id %s %s", orgID, err.Error())
		return nil, err
	}

	labels := make(map[string]string)
	labels["rep-organization"] = orgHashID
	labels["rep-partner"] = partnerHashID
	labels["rep-project"] = projectHashID
	if isSystemWorkload {
		labels["rep-addon"] = name
	} else {
		labels["rep-workload"] = name
	}

	return labels, nil
}

func resourceToStepTemplate(resource []byte) (sts []controllerv2.StepTemplate, err error) {

	so := &controllerv2.StepObject{Raw: resource}
	var accessor controllerv2.Accessor
	var gvk schema.GroupVersionKind

	accessor, err = so.Accessor()
	if err != nil {
		return
	}

	gvk, err = accessor.GroupVersionKind()
	if err != nil {
		return
	}

	if IsListGVK(gvk) {
		var ro runtime.Object
		ro, _, err = runtimeutil.ToObject(so)
		if err != nil {
			return
		}
		if list, ok := ro.(*corev1.List); ok {
			for _, item := range list.Items {

				var lso = &controllerv2.StepObject{Raw: item.Raw}
				var la controllerv2.Accessor
				var ln string
				var lgvk schema.GroupVersionKind
				la, err = lso.Accessor()
				if err != nil {
					return
				}
				ln, err = la.Name()
				if err != nil {
					return
				}
				lgvk, err = la.GroupVersionKind()
				if err != nil {
					return
				}
				var st controllerv2.StepTemplate
				st.Name = strings.ToLower(fmt.Sprintf("step-%s-%s", lgvk.Kind, ln))
				st.Object = lso
				sts = append(sts, st)
			}
		}
	} else {
		var name string
		var st controllerv2.StepTemplate

		name, err = accessor.Name()
		if err != nil {
			return
		}

		accessor.ResetAutoFields()

		so.Raw = accessor.Bytes()

		st.Name = strings.ToLower(fmt.Sprintf("step-%s-%s", gvk.Kind, name))
		st.Object = so
		sts = append(sts, st)
	}

	return
}

// toRuntimeObject converts JSON bytes into runtime object of
// latest version
func toRuntimeObject(gvk schema.GroupVersionKind, b []byte) (runtime.Object, error) {
	var sa controllerv2.StepObject

	err := json.Unmarshal(b, &sa)
	if err != nil {
		return nil, err
	}

	ro, _, err := runtimeutil.ToObject(&sa)
	if err != nil {
		return nil, err
	}

	return ro, nil

}

func toStepTemplate(o runtime.Object) (controllerv2.StepTemplate, error) {
	so, err := runtimeutil.FromObject(o)
	if err != nil {
		return controllerv2.StepTemplate{}, err
	}
	return stepObjectToStepTemplate(*so)
}

func stepObjectToStepTemplate(so controllerv2.StepObject) (controllerv2.StepTemplate, error) {
	var st controllerv2.StepTemplate

	accessor, err := so.Accessor()
	if err != nil {
		return st, err
	}

	gvk, err := accessor.GroupVersionKind()
	if err != nil {
		return st, err
	}

	name, err := accessor.Name()
	if err != nil {
		return st, err
	}

	st.Name = strings.ToLower(fmt.Sprintf("step-%s-%s", gvk.Kind, name))
	st.Object = &so

	return st, nil
}

// tryConvert attempts to convert the given object to the provided versions in order. This function assumes
// the object is in internal version.
func tryConvert(converter runtime.ObjectConvertor, object runtime.Object, versions ...schema.GroupVersion) (runtime.Object, error) {
	var last error
	for _, version := range versions {
		if version.Empty() {
			return object, nil
		}
		obj, err := converter.ConvertToVersion(object, version)
		if err != nil {
			last = err
			continue
		}
		return obj, nil
	}
	return nil, last
}
