package integration

import (
	context "context"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"strconv"

	helmpackage "github.com/tidbcloud/metrics-integration/global/pkg/helm/pipeline"
	apiv1 "github.com/tidbcloud/metrics-integration/regional/api/integration/v1"
	"github.com/tidbcloud/metrics-integration/regional/internal/infrastructure/config"
	"github.com/tidbcloud/metrics-integration/regional/internal/infrastructure/dataplane"
	"github.com/tidbcloud/metrics-integration/regional/internal/infrastructure/helm"
	"google.golang.org/protobuf/types/known/emptypb"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

type integrationServiceServer struct {
	apiv1.UnimplementedIntegrationServiceServer
}

func NewIntegrationServiceServer() apiv1.IntegrationServiceServer {
	return &integrationServiceServer{}
}

func RefreshDedicatedClusterCPUConfig(ctx context.Context, pipelineID string, components []helmpackage.ClusterComponent) error {
	if _, err := strconv.ParseUint(pipelineID, 10, 64); err != nil {
		return fmt.Errorf("invalid pipeline_id %q: must be numeric", pipelineID)
	}

	restConfig, err := dataplane.GetRestConfig("local")
	if err != nil {
		return err
	}
	clientset, err := kubernetes.NewForConfig(restConfig)
	if err != nil {
		return err
	}
	namespace := fmt.Sprintf("pipeline-%s", pipelineID)
	name := fmt.Sprintf("pipeline-%s-meta-config", pipelineID)
	cm, err := clientset.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{})
	if err != nil {
		return err
	}
	if cm.Data == nil {
		return fmt.Errorf("configmap %s/%s has no data", namespace, name)
	}
	configYAML, ok := cm.Data["config.yaml"]
	if !ok {
		return fmt.Errorf("configmap %s/%s missing config.yaml", namespace, name)
	}

	patchedYAML, err := helmpackage.PatchDedicatedClusterCPUConfigYAML(configYAML, components)
	if err != nil {
		return err
	}
	cm.Data["config.yaml"] = patchedYAML
	_, err = clientset.CoreV1().ConfigMaps(namespace).Update(ctx, cm, metav1.UpdateOptions{})
	return err
}

func (i *integrationServiceServer) RefreshDedicatedClusterCPUConfig(ctx context.Context, req *apiv1.RefreshDedicatedClusterCPUConfigRequest) (*emptypb.Empty, error) {
	components := make([]helmpackage.ClusterComponent, 0, len(req.Components))
	for _, component := range req.Components {
		components = append(components, helmpackage.ClusterComponent{
			ComponentType: component.ComponentType,
			CpuNum:        component.CpuNum,
		})
	}

	if err := RefreshDedicatedClusterCPUConfig(ctx, req.PipelineId, components); err != nil {
		return nil, err
	}

	return &emptypb.Empty{}, nil
}

func (i *integrationServiceServer) ApplyIntegration(ctx context.Context, req *apiv1.ApplyIntegrationRequest) (resp *apiv1.IntegrationStatusResp, err error) {
	pipelineHelmConfig := helmpackage.PipelineConfig{}
	if err := json.Unmarshal([]byte(req.HelmConfig), &pipelineHelmConfig); err != nil {
		return nil, err
	}
	restConfig, err := dataplane.GetRestConfig("local")
	if err != nil {
		return nil, err
	}
	if err := helm.Apply(ctx, restConfig, &pipelineHelmConfig); err != nil {
		return nil, err
	}
	resp = &apiv1.IntegrationStatusResp{}
	return resp, nil
}

func (i *integrationServiceServer) UninstallIntegration(ctx context.Context, req *apiv1.UninstallIntegrationRequest) (resp *apiv1.IntegrationStatusResp, err error) {
	pipelineHelmConfig := helmpackage.PipelineConfig{}
	if err := json.Unmarshal([]byte(req.HelmConfig), &pipelineHelmConfig); err != nil {
		return nil, err
	}
	restConfig, err := dataplane.GetRestConfig("local")
	if err != nil {
		return nil, err
	}
	if err := helm.Uninstall(ctx, restConfig, &pipelineHelmConfig); err != nil {
		return nil, err
	}
	if err := helm.RemoveNamespaceIfExists(ctx, restConfig, &pipelineHelmConfig); err != nil {
		return nil, err
	}
	resp = &apiv1.IntegrationStatusResp{}
	return resp, nil
}

func generateConfigMapName(clusterID, hashedMetricsKey string) string {
	combined := fmt.Sprintf("%s-%s", clusterID, hashedMetricsKey)

	hash := sha256.Sum256([]byte(combined))

	hashStr := fmt.Sprintf("%x", hash)[:8]

	return fmt.Sprintf("metrics-key-%s", hashStr)
}

func ensureNamespaceExists(ctx context.Context, clientset kubernetes.Interface, namespace string) error {
	_, err := clientset.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{})
	if err == nil {
		return nil
	}
	if !apierrors.IsNotFound(err) {
		return err
	}

	_, err = clientset.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: namespace,
		},
	}, metav1.CreateOptions{})
	if err != nil && !apierrors.IsAlreadyExists(err) {
		return err
	}

	return nil
}

func (i *integrationServiceServer) CreateMetricsKey(ctx context.Context, req *apiv1.CreateMetricsKeyRequest) (*emptypb.Empty, error) {
	configMapData := map[string]string{
		"hashed_metrics_key": req.HashedMetricsKey,
		"cluster_id":         req.ClusterId,
		"cluster_config_map": req.ClusterConfigMap,
	}

	namespace := config.Global().PrometheusIntegrationNamespace
	cm := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      generateConfigMapName(req.ClusterId, req.HashedMetricsKey),
			Namespace: namespace,
			Labels: map[string]string{
				"cluster_id": req.ClusterId,
				"app":        "prometheus-integration",
			},
		},
		Data: configMapData,
	}

	restConfig, err := dataplane.GetRestConfig("local")
	if err != nil {
		return nil, err
	}
	clientset, err := kubernetes.NewForConfig(restConfig)
	if err != nil {
		return nil, err
	}

	if err := ensureNamespaceExists(ctx, clientset, namespace); err != nil {
		return nil, err
	}

	_, err = clientset.CoreV1().ConfigMaps(cm.Namespace).Create(ctx, cm, metav1.CreateOptions{})
	if err != nil {
		return nil, err
	}

	return &emptypb.Empty{}, nil
}

func (i *integrationServiceServer) DeleteMetricsKey(ctx context.Context, req *apiv1.DeleteMetricsKeyRequest) (*emptypb.Empty, error) {
	name := generateConfigMapName(req.ClusterId, req.HashedMetricsKey)
	namespace := config.Global().PrometheusIntegrationNamespace
	restConfig, err := dataplane.GetRestConfig("local")
	if err != nil {
		return nil, err
	}
	clientset, err := kubernetes.NewForConfig(restConfig)
	if err != nil {
		return nil, err
	}

	err = clientset.CoreV1().ConfigMaps(namespace).Delete(ctx, name, metav1.DeleteOptions{})
	if err != nil {
		if apierrors.IsNotFound(err) {
			return &emptypb.Empty{}, nil
		}
		return nil, err
	}

	return &emptypb.Empty{}, nil
}

func (i *integrationServiceServer) ScaleCluster(ctx context.Context, req *apiv1.ScaleClusterRequest) (*emptypb.Empty, error) {
	namespace := config.Global().PrometheusIntegrationNamespace
	restConfig, err := dataplane.GetRestConfig("local")
	if err != nil {
		return nil, err
	}
	clientset, err := kubernetes.NewForConfig(restConfig)
	if err != nil {
		return nil, err
	}

	cmList, err := clientset.CoreV1().ConfigMaps(namespace).List(ctx, metav1.ListOptions{})
	if err != nil {
		if apierrors.IsNotFound(err) {
			return &emptypb.Empty{}, nil
		}
		return nil, err
	}
	for _, cm := range cmList.Items {
		if cm.Data["cluster_id"] == req.ClusterId {
			cm.Data["cluster_config_map"] = req.ClusterConfigMap
			_, err := clientset.CoreV1().ConfigMaps(namespace).Update(ctx, &cm, metav1.UpdateOptions{})
			if err != nil {
				return nil, err
			}
		}
	}

	return &emptypb.Empty{}, nil
}

func (i *integrationServiceServer) DeleteCluster(ctx context.Context, req *apiv1.DeleteClusterRequest) (*emptypb.Empty, error) {
	restConfig, err := dataplane.GetRestConfig("local")
	if err != nil {
		return nil, err
	}
	clientset, err := kubernetes.NewForConfig(restConfig)
	if err != nil {
		return nil, err
	}

	namespace := config.Global().PrometheusIntegrationNamespace
	cmList, err := clientset.CoreV1().ConfigMaps(namespace).List(ctx, metav1.ListOptions{})
	if err != nil {
		if apierrors.IsNotFound(err) {
			return &emptypb.Empty{}, nil
		}
		return nil, err
	}
	for _, cm := range cmList.Items {
		if cm.Data["cluster_id"] == req.ClusterId {
			err := clientset.CoreV1().ConfigMaps(namespace).Delete(ctx, cm.Name, metav1.DeleteOptions{})
			if err != nil {
				return nil, err
			}
		}
	}

	return &emptypb.Empty{}, nil
}
