package configmanager

import (
	"sync"

	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
	webhookutil "k8s.io/apiserver/pkg/util/webhook"
	"k8s.io/client-go/rest"

	configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
)

// WebhookAccessor provides a common interface to get webhook configuration.
type WebhookAccessor interface {
	// GetUID gets a string that uniquely identifies the webhook.
	GetUID() string
	// GetConfigurationName gets the name of the webhook configuration that owns this webhook.
	GetConfigurationName() string
	// GetName gets the webhook Name field.
	GetName() string
	// GetClientConfig gets the webhook ClientConfig field.
	GetClientConfig() admissionregistrationv1.WebhookClientConfig
	// GetRules gets the webhook Rules field.
	GetRules() []configv1alpha1.RuleWithOperations
	// GetTimeoutSeconds gets the webhook TimeoutSeconds field.
	GetTimeoutSeconds() *int32
	// GetInterpreterContextVersions gets the webhook InterpreterContextVersions field.
	GetInterpreterContextVersions() []string

	// GetRESTClient gets the webhook client.
	GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error)
}

type resourceExploringAccessor struct {
	*configv1alpha1.ResourceInterpreterWebhook
	uid               string
	configurationName string

	initClient sync.Once
	client     *rest.RESTClient
	clientErr  error
}

// NewResourceExploringAccessor create an accessor for webhook.
func NewResourceExploringAccessor(uid, configurationName string, hook *configv1alpha1.ResourceInterpreterWebhook) WebhookAccessor {
	return &resourceExploringAccessor{uid: uid, configurationName: configurationName, ResourceInterpreterWebhook: hook}
}

// GetUID gets a string that uniquely identifies the webhook.
func (a *resourceExploringAccessor) GetUID() string {
	return a.uid
}

// GetConfigurationName gets the name of the webhook configuration that owns this webhook.
func (a *resourceExploringAccessor) GetConfigurationName() string {
	return a.configurationName
}

// GetName gets the webhook Name field.
func (a *resourceExploringAccessor) GetName() string {
	return a.Name
}

// GetClientConfig gets the webhook ClientConfig field.
func (a *resourceExploringAccessor) GetClientConfig() admissionregistrationv1.WebhookClientConfig {
	return a.ClientConfig
}

// GetRules gets the webhook Rules field.
func (a *resourceExploringAccessor) GetRules() []configv1alpha1.RuleWithOperations {
	return a.Rules
}

// GetTimeoutSeconds gets the webhook TimeoutSeconds field.
func (a *resourceExploringAccessor) GetTimeoutSeconds() *int32 {
	return a.TimeoutSeconds
}

// GetInterpreterContextVersions gets the webhook InterpreterContextVersions field.
func (a *resourceExploringAccessor) GetInterpreterContextVersions() []string {
	return a.InterpreterContextVersions
}

// GetRESTClient gets the webhook client.
func (a *resourceExploringAccessor) GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error) {
	a.initClient.Do(func() {
		a.client, a.clientErr = clientManager.HookClient(hookClientConfigForWebhook(a.Name, a.ClientConfig))
	})
	return a.client, a.clientErr
}

// hookClientConfigForWebhook construct a webhookutil.ClientConfig using an admissionregistrationv1.WebhookClientConfig
// to access v1alpha1.ResourceInterpreterWebhook. webhookutil.ClientConfig is used to create a HookClient
// and the purpose of the config struct is to share that with other packages that need to create a HookClient.
func hookClientConfigForWebhook(hookName string, config admissionregistrationv1.WebhookClientConfig) webhookutil.ClientConfig {
	clientConfig := webhookutil.ClientConfig{Name: hookName, CABundle: config.CABundle}
	if config.URL != nil {
		clientConfig.URL = *config.URL
	}
	if config.Service != nil {
		clientConfig.Service = &webhookutil.ClientConfigService{
			Name:      config.Service.Name,
			Namespace: config.Service.Namespace,
		}
		if config.Service.Port != nil {
			clientConfig.Service.Port = *config.Service.Port
		} else {
			clientConfig.Service.Port = 443
		}
		if config.Service.Path != nil {
			clientConfig.Service.Path = *config.Service.Path
		}
	}
	return clientConfig
}
