package prometheus

import (
	"bytes"
	"context"
	"crypto/sha256"
	"fmt"
	"sort"
	"strings"

	"github.com/pingcap/log"
	"github.com/spf13/viper"

	"github.com/tidbcloud/metrics-integration/pipeline/central/config"
	"github.com/tidbcloud/metrics-integration/pipeline/central/entity"
	"github.com/tidbcloud/metrics-integration/pipeline/central/metrics/prom"
	"github.com/tidbcloud/metrics-integration/pipeline/internal/datasource"
	"go.uber.org/zap"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

type Manager struct {
	// MgmtClient grpcli.MgmtSvcGrpcClient
}

func NewManager() *Manager {
	// todo: get
	// cli := grpcli.NewMgmtSvcGrpcClient(context.Background(), viper.GetString("mgmt_cli_addr"))
	return &Manager{}
}

func (m *Manager) QueryMetrics(ctx context.Context, cluster *config.TargetCluster) ([]byte, error) {
	type result struct {
		value prom.Metric
		err   error
		index int
	}

	var allMetricQueries []datasource.MetricQuerierFunc
	allMetricQueries = registryClusterMetrics(cluster)

	tidbClusterInfo := datasource.TidbClusterInfo{
		Vendor:      cluster.CloudProviderName,
		Region:      cluster.RegionName,
		TenantID:    entity.ID(cluster.TenantId),
		ProjectID:   entity.ID(cluster.ProjectId),
		ClusterID:   entity.ID(cluster.Id),
		ServicePlan: cluster.ServicePlan,
	}
	ctx, err := datasource.SetMetricDataAddressToContextWithTidbClusterInfoV2(ctx, tidbClusterInfo)
	if err != nil {
		return nil, fmt.Errorf("failed to call datasource.SetMetricDataAddressToContextWithTidbClusterInfoV2: %s", err.Error())
	}

	resultsCh := make(chan result, len(allMetricQueries))
	for idx, query := range allMetricQueries {
		go func(idx int, query datasource.MetricQuerierFunc) {
			m, err := query(ctx)
			resultsCh <- result{
				value: m,
				err:   err,
				index: idx,
			}
		}(idx, query)
	}
	results := make([]result, 0, len(allMetricQueries))
	for range allMetricQueries {
		r := <-resultsCh
		if r.err != nil {
			log.Warn("GetMeasurements API failed to query metrics",
				zap.Uint64("clusterID", cluster.Id),
				zap.Error(r.err))
		}
		results = append(results, r)
	}
	sort.Slice(results, func(i, j int) bool {
		return results[i].index < results[j].index
	})
	outputData := bytes.NewBuffer(make([]byte, 0, 10*1024))
	for _, r := range results {
		if r.err == nil {
			_ = r.value.EncodePrometheusFormat(outputData)
		}
	}

	return outputData.Bytes(), nil
}

func (m *Manager) QueryClusterMetrics(ctx context.Context, cluster *config.TargetCluster) ([]byte, error) {
	data, err := m.QueryMetrics(ctx, cluster)
	if err != nil {
		queryFailureCounter.WithLabelValues(fmt.Sprintf("%d", cluster.Id)).Inc()
		log.Info("write query failure metric", zap.Uint64("clusterID", cluster.Id))
		return nil, err
	}

	outputData := bytes.NewBuffer(make([]byte, 0, 100*1024))
	outputData.Write(data)

	return outputData.Bytes(), 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 (m *Manager) ValidClusterTokenAndGetConfig(ctx context.Context, clusterID uint64, token string) (*config.TargetCluster, error) {
	// Check static config token mappings first (takes priority over k8s ConfigMap, useful for local debugging).
	if config.VerifyClusterTokenByID(clusterID, token) {
		cluster, err := config.LoadClusterConfigByID(clusterID)
		if err != nil {
			return nil, fmt.Errorf("token matched static config but cluster not found: %w", err)
		}
		return cluster, nil
	}

	restConfig, err := rest.InClusterConfig()
	if err != nil {
		fmt.Println("Failed to get in-cluster config:", err)
		return nil, fmt.Errorf("failed to get in-cluster config: %w", err)
	}
	clientset, err := kubernetes.NewForConfig(restConfig)
	if err != nil {
		fmt.Println("Failed to create k8s client:", err)
		return nil, fmt.Errorf("failed to create k8s client: %w", err)
	}

	cmName := generateConfigMapName(fmt.Sprintf("%d", clusterID), token)

	cm, err := clientset.CoreV1().ConfigMaps(config.Global().PrometheusIntegrationNamespace).Get(ctx, cmName, metav1.GetOptions{})
	if err != nil {
		fmt.Println("ConfigMap not found:", err)
		return nil, fmt.Errorf("ConfigMap not found: %w", err)
	}

	if cm.Data["hashed_metrics_key"] != token || cm.Data["cluster_id"] != fmt.Sprintf("%d", clusterID) {
		return nil, fmt.Errorf("token or cluster_id validation failed")
	}

	clusterConfigMapStr, exists := cm.Data["cluster_config_map"]
	if !exists {
		return nil, fmt.Errorf("cluster_config_map not found in ConfigMap")
	}

	var targetCluster config.TargetCluster
	v := viper.New()
	v.SetConfigType("yaml")
	if err := v.ReadConfig(strings.NewReader(clusterConfigMapStr)); err != nil {
		return nil, fmt.Errorf("failed to read config: %w", err)
	}
	err = v.Unmarshal(&targetCluster)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal cluster_config_map: %w", err)
	}
	if err := normalizeClusterConfig(clusterID, cmName, &targetCluster); err != nil {
		return nil, err
	}
	// if err := yaml.Unmarshal([]byte(clusterConfigMapStr), &targetCluster); err != nil {
	// 	return nil, fmt.Errorf("failed to unmarshal cluster_config_map: %w", err)
	// }
	fmt.Println("config map is: ", targetCluster)

	return &targetCluster, nil
}

func normalizeClusterConfig(clusterID uint64, cmName string, targetCluster *config.TargetCluster) error {
	originalServicePlan := targetCluster.ServicePlan
	if err := config.NormalizeTargetCluster(targetCluster); err != nil {
		return fmt.Errorf(
			"invalid cluster_config_map for clusterID %d (ConfigMap %s, servicePlan %q): %w",
			clusterID, cmName, originalServicePlan, err,
		)
	}
	return nil
}
