/******************************************************************************
 * Copyright (c)  2022 PingCAP, Inc.                                          *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 * http://www.apache.org/licenses/LICENSE-2.0                                 *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

/*******************************************************************************
 * @File: configmapimpl.go
 * @Description:
 * @Author: jiangxunyu@pingcap.com
 * @Version: 1.0.0
 * @Date: 2022/6/7 14:41
*******************************************************************************/

package etcd

import (
	"context"
	"fmt"
	"sync"

	"github.com/tidbcloud/observability/pkg/common/constants"

	"github.com/pingcap/log"
	"go.uber.org/zap"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/kubernetes"
)

type ConfigMapMetaService struct {
	kubeClient kubernetes.Interface
}

var cmMetaService *ConfigMapMetaService
var once sync.Once

// NewConfigMapMetaService
// @Description: new meta service instances
// @Parameter kubeClient
// @return *ConfigMapMetaService
func NewConfigMapMetaService(kubeClient kubernetes.Interface) *ConfigMapMetaService {
	once.Do(func() {
		if cmMetaService == nil {
			cmMetaService = &ConfigMapMetaService{
				kubeClient: kubeClient,
			}
		}
	})
	return cmMetaService
}

// DetailMeta
// @Description: detail metadata for services
// @Receiver m
// @Parameter ctx
// @Parameter nsAndCmName
// @return metadata
// @return err
func (m ConfigMapMetaService) DetailMeta(ctx context.Context, nsAndCmName string) (metadata string, err error) {
	cmMeta, err := m.kubeClient.CoreV1().
		ConfigMaps(nsAndCmName).
		Get(ctx, nsAndCmName, metav1.GetOptions{})
	if err != nil {
		log.Error("detail meta failed", zap.Error(err))
		return metadata, fmt.Errorf("detail meta failed. error: %s", err.Error())
	}
	metadata = cmMeta.Data[constants.DefaultServerCmMetaFileName]
	return metadata, nil
}

// UpdateMeta
// @Description: update metadata for services
// @Receiver m
// @Parameter ctx
// @Parameter nsAndCmName
// @Parameter metadata
// @return err
func (m ConfigMapMetaService) UpdateMeta(ctx context.Context, nsAndCmName string, metadata string) (err error) {
	_, err = m.kubeClient.CoreV1().
		ConfigMaps(nsAndCmName).
		Update(ctx, &corev1.ConfigMap{
			ObjectMeta: metav1.ObjectMeta{
				Name: nsAndCmName,
			},
			Data: map[string]string{
				constants.DefaultServerCmMetaFileName: metadata,
			},
		}, metav1.UpdateOptions{})
	if err != nil {
		log.Error("update meta failed", zap.Error(err))
		return err
	}
	return nil
}

// CreateMeta
// @Description: create metadata for services
// @Receiver m
// @Parameter ctx
// @Parameter nsAndCmName
// @Parameter metadata
// @return err
func (m ConfigMapMetaService) CreateMeta(ctx context.Context, nsAndCmName string, metadata string) (err error) {
	_, err = m.kubeClient.CoreV1().
		ConfigMaps(nsAndCmName).
		Create(ctx, &corev1.ConfigMap{
			ObjectMeta: metav1.ObjectMeta{
				Name: nsAndCmName,
			},
			Data: map[string]string{
				constants.DefaultServerCmMetaFileName: metadata,
			},
		}, metav1.CreateOptions{})
	if err != nil {
		log.Error("create meta failed", zap.Error(err))
		return err
	}
	return nil
}

// WatchMeta
// @Description: metadata change listening for services
// @Receiver m
// @Parameter ctx
// @Parameter nsName
// @return watch.Interface
// @return error
func (m ConfigMapMetaService) WatchMeta(ctx context.Context, nsName string) (watch.Interface, error) {
	return m.kubeClient.CoreV1().
		ConfigMaps(nsName).
		Watch(ctx, metav1.ListOptions{
			FieldSelector: fmt.Sprintf("metadata.name=%s", nsName),
			Watch:         true,
		})
}
