// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors
// SPDX-License-Identifier: Apache-2.0

package clientutil

import (
	"fmt"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"

	greenhouseapis "github.com/cloudoperators/greenhouse/api"
)

func ValidateSecretForKubeConfig(secret *corev1.Secret) error {
	switch {
	// Secret is not of the right type
	case secret.Type != greenhouseapis.SecretTypeKubeConfig && secret.Type != greenhouseapis.SecretTypeOIDCConfig:
		return fmt.Errorf("secret %s/%s is not of type %s", secret.GetNamespace(), secret.GetName(), greenhouseapis.SecretTypeKubeConfig)
	// Try the kubeconfig generated by Greenhouse first.
	// Fallback to user-provided kubeconfig.
	case !IsSecretContainsKey(secret, greenhouseapis.GreenHouseKubeConfigKey) && !IsSecretContainsKey(secret, greenhouseapis.KubeConfigKey):
		return fmt.Errorf(
			"secret %s/%s does not contain a kubeconfig", secret.GetNamespace(), secret.GetName(),
		)
	}
	return nil
}

func NewClientConfigLoaderFromBytes(kubeConfig []byte) clientcmd.ClientConfigLoader {
	return &ClientConfigGetter{
		kubeConfigGetter: kubeConfigGetterFromBytes(kubeConfig),
	}
}

func NewClientConfigLoaderFromSecret(kubeConfig *corev1.Secret) clientcmd.ClientConfigLoader {
	return &ClientConfigGetter{
		kubeConfigGetter: kubeConfigGetterFromSecret(kubeConfig),
	}
}

// ClientConfigGetter implements the clientcmd.ClientConfigLoader interface.
type ClientConfigGetter struct {
	kubeConfigGetter clientcmd.KubeconfigGetter
}

func (g *ClientConfigGetter) Load() (*clientcmdapi.Config, error) {
	return g.kubeConfigGetter()
}
func (g *ClientConfigGetter) GetLoadingPrecedence() []string {
	return nil
}
func (g *ClientConfigGetter) GetStartingConfig() (*clientcmdapi.Config, error) {
	return g.kubeConfigGetter()
}
func (g *ClientConfigGetter) GetDefaultFilename() string {
	return ""
}
func (g *ClientConfigGetter) IsExplicitFile() bool {
	return false
}
func (g *ClientConfigGetter) GetExplicitFile() string {
	return ""
}
func (g *ClientConfigGetter) IsDefaultConfig(_ *rest.Config) bool {
	return false
}

// KubeConfigGetterFromBytes implements clientcmd.KubeConfigGetter interface.
func kubeConfigGetterFromBytes(kubeConfig []byte) clientcmd.KubeconfigGetter {
	return func() (*clientcmdapi.Config, error) {
		cfg, err := clientcmd.NewClientConfigFromBytes(kubeConfig)
		if err != nil {
			return nil, err
		}
		apiCfg, err := cfg.RawConfig()
		if err != nil {
			return nil, err
		}
		return &apiCfg, nil
	}
}

// KubeConfigGetterFromSecret implements clientcmd.KubeConfigGetter interface.
func kubeConfigGetterFromSecret(secret *corev1.Secret) clientcmd.KubeconfigGetter {
	return func() (*clientcmdapi.Config, error) {
		if err := ValidateSecretForKubeConfig(secret); err != nil {
			return nil, err
		}
		switch {
		// Try the kubeconfig generated by Greenhouse first.
		case IsSecretContainsKey(secret, greenhouseapis.GreenHouseKubeConfigKey):
			return kubeConfigGetterFromBytes(secret.Data[greenhouseapis.GreenHouseKubeConfigKey])()
		// Fallback to user-provided kubeconfig.
		case IsSecretContainsKey(secret, greenhouseapis.KubeConfigKey):
			return kubeConfigGetterFromBytes(secret.Data[greenhouseapis.KubeConfigKey])()
		// Error if the secret does not contain a kubeconfig.
		default:
			return nil, fmt.Errorf(
				"secret %s/%s does not contain a kubeconfig", secret.GetNamespace(), secret.GetName(),
			)
		}
	}
}
