package util

import (
	"fmt"
	"hash/fnv"
	"math/rand"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/go-openapi/runtime"
	"github.com/pkg/errors"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/klog"
)

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func init() {
	rand.Seed(time.Now().UnixNano())
}

func Is404(err error) bool {
	return GetStatusCodeOrZero(err) == 404
}

func GetStatusCodeOrZero(err error) int {
	apiError, ok := err.(*runtime.APIError)
	if !ok {
		return 0
	}
	return apiError.Code
}

func RandString(length int) string {
	b := make([]rune, length)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

/* Ship some simply utilities of dbaas below to avoid introduce extra package dependencies*/

func ShootName(tenantID int64, cloud string, region string) string {
	rawName := fmt.Sprintf("%d-%s-%s", tenantID, cloud, region)
	return FnvHash(rawName)
}

func ShootNamespace(project string) string {
	return fmt.Sprintf("garden-%s", project)
}

func FnvHash(raw string) string {
	hash := fnv.New32()
	if _, err := hash.Write([]byte(raw)); err != nil {
		// impossible path
		klog.Error("failed to write fnv hash")
	}
	return fmt.Sprintf("%x", hash.Sum32())
}

func KubeClientFromSecret(sec *corev1.Secret) (kubernetes.Interface, error) {

	restConfig, err := RestConfigFromSecret(sec)
	if err != nil {
		return nil, errors.Wrap(err, "Cannot get kube rest config from secret")
	}
	kubeCli, err := kubernetes.NewForConfig(restConfig)
	if err != nil {
		return nil, errors.Wrap(err, "cannot get kubernetes client from rest config")
	}
	return kubeCli, nil
}

func RestConfigFromSecret(sec *corev1.Secret) (*rest.Config, error) {
	configData, ok := sec.Data["kubeconfig"]
	if !ok {
		return nil, errors.New("cannot find kubeconfig from secret")
	}
	return clientcmd.RESTConfigFromKubeConfig(configData)
}

// OverlayToml apply the rawToml to the base struct under paths and override the same key
func OverlayToml(base map[string]interface{}, rawToml string, keyPath ...string) error {
	var overlay map[string]interface{}
	if _, err := toml.Decode(rawToml, &overlay); err != nil {
		return errors.Wrap(err, "unmarshal toml")
	}
	return Overlay(base, overlay, keyPath...)
}

// Overlay apply the overlay struct to the base struct and override the same key
func Overlay(base map[string]interface{}, overlay map[string]interface{}, keyPath ...string) error {
	child, err := GetChild(base, keyPath...)
	if err != nil {
		return errors.Wrap(err, "get child")
	}
	for k, v := range overlay {
		switch v.(type) {
		case map[string]interface{}:
			if _, ok := child[k]; !ok {
				// object key does not exist in base, shortcut to copy the entire Object
				child[k] = v.(map[string]interface{})
			} else {
				if err := Overlay(child, v.(map[string]interface{}), k); err != nil {
					return errors.Wrap(err, fmt.Sprintf("error overlay processing key %s", k))
				}
			}
		default:
			child[k] = v
		}
	}
	return nil
}

func GetChild(base map[string]interface{}, keyPath ...string) (map[string]interface{}, error) {
	child := base
	for _, key := range keyPath {
		next, ok := child[key]
		if !ok {
			return nil, errors.New(fmt.Sprintf("key %s not found", strings.Join(keyPath, ".")))
		}
		child, ok = next.(map[string]interface{})
		if !ok {
			return nil, errors.New(fmt.Sprintf("key %s is not an Object", strings.Join(keyPath, ".")))
		}
	}
	return child, nil
}
