// 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.
// Copyright (c) 2020 Red Hat, Inc.

package sync

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"

	"github.com/golang/glog"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/cert"
)

// path used in the secret that holds the service account for the remote client
const (
	// #nosec G101 -- default path in k8s container for sa secret
	secretpath         = "/var/run/secrets/kubernetes.io/remote/serviceaccount"
	certsDir           = "/tmp/hcm"
	KubeconfigForCerts = "/tmp/hcm/config"
	checkCertInterval  = 5
)

func sameAPIEndpopint(rhs, lhs *rest.Config) bool {
	if rhs == nil && lhs == nil {
		return true
	}
	if rhs == nil || lhs == nil {
		return false
	}
	return rhs.Host == lhs.Host &&
		rhs.APIPath == lhs.APIPath
}

// BuildRemoteConfig builds a config for the remote cluster
func BuildRemoteConfig(kubeconfig string, certSecretName, namespace string, localConf *rest.Config) (*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 = InClusterConfigForRemoteCluster(certSecretName)
		if err != nil {
			return nil, err
		}
	}
	// handle the user certificate case (klusterlet boostrap model)
	if certSecretName != "" {
		localClient, err := kubernetes.NewForConfig(localConf)
		if err != nil {
			return nil, err
		}
		// ensure the dir for certs/config exists
		if _, err = os.Stat(certsDir); os.IsNotExist(err) {
			os.Mkdir(certsDir, 0700)
		}

		glog.V(3).Infof("Try to load the hub cluster certificates, it may depend on manual approvement, so it can require some time")
		for {
			err = LoadClientCert(config.Host, KubeconfigForCerts, certsDir, certSecretName, namespace, localClient)
			if err == nil {
				break
			}
			// NotFound errors can be temporary errors, K8s has not created the required objects, or klusterlet joint was not approved.
			if !errors.IsNotFound(err) {
				glog.Errorf("Error: LoadClientCert: %v", err)
				return nil, err
			}
			time.Sleep(time.Second * checkCertInterval)
		}

		config, err = clientcmd.BuildConfigFromFlags("", KubeconfigForCerts)
		if err != nil {
			return nil, err
		}
	}

	return config, nil
}

// InClusterConfigForRemoteCluster provides an in-cluster config for a remote cluster via kube secret
func InClusterConfigForRemoteCluster(certSecretName string) (*rest.Config, error) {
	var cfg *rest.Config
	// For this to work, need to mount a secret with url, token, ca.crt
	url, err := ioutil.ReadFile(fmt.Sprintf("%s/url", secretpath))
	if err != nil {
		return nil, err
	}
	if certSecretName == "" {
		token, err := ioutil.ReadFile(fmt.Sprintf("%s/token", secretpath))
		if err != nil {
			return nil, err
		}
		tlsClientConfig := rest.TLSClientConfig{}
		rootCAFile := fmt.Sprintf("%s/ca.crt", secretpath)
		if _, err := cert.NewPool(rootCAFile); err != nil {
			glog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
		} else {
			tlsClientConfig.CAFile = rootCAFile
		}
		cfg = &rest.Config{
			Host:            strings.TrimSuffix(string(url), "\n"),
			BearerToken:     string(token),
			TLSClientConfig: tlsClientConfig,
		}
	} else {
		cfg = &rest.Config{
			Host: strings.TrimSuffix(string(url), "\n"),
		}
	}

	return cfg, nil
}
