package service

import (
	"context"
	"fmt"
	"strings"
	"time"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/cluster-agent/v1"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/labels"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

// Component type constants for label mapping
const (
	TiFlashComputeLabel = "compute-tiflash"
	TiFlashWriteLabel   = "write-tiflash"
	TiDBWorkerLabel     = "worker-tidb"
	TiKVWorkerLabel     = "tikv-worker"
	CoprocessorLabel    = "coprocessor-worker"
	SchemaManagerLabel  = "schema-manager"
	TSOLabel            = "tso"
	SchedulingLabel     = "scheduling"
)

// GetComponentTypeLabel maps component type string to the label value for app.kubernetes.io/component
func GetComponentTypeLabel(componentType string) string {
	compType := strings.ToLower(strings.ReplaceAll(componentType, "_", "-"))

	switch compType {
	case "tiflash-compute", "compute-tiflash":
		return TiFlashComputeLabel
	case "tiflash-write", "write-tiflash":
		return TiFlashWriteLabel
	case "tidb-worker", "worker-tidb":
		return TiDBWorkerLabel
	case "tikv-worker", "default-tikv-worker":
		return TiKVWorkerLabel
	case "coprocessor-worker", "coprocessor-tikv-worker":
		return CoprocessorLabel
	case "schema-manager", "schema-manager-tikv-worker":
		return SchemaManagerLabel
	case "pd-tso":
		return TSOLabel
	case "pd-scheduling":
		return SchedulingLabel
	default:
		return compType
	}
}

// GetComponentInstanceLabel maps component type and ID to the label value for app.kubernetes.io/instance
func GetComponentInstanceLabel(componentType, componentId string) string {
	compType := strings.ToLower(strings.ReplaceAll(componentType, "_", "-"))

	switch compType {
	case "tiflash-compute", "compute-tiflash":
		return fmt.Sprintf("db-%s-compute", componentId)
	case "tiflash-write", "write-tiflash":
		return fmt.Sprintf("db-%s-write", componentId)
	case "tidb-worker", "worker-tidb":
		return fmt.Sprintf("db-%s-worker", componentId)
	default:
		return fmt.Sprintf("db-%s", componentId)
	}
}

// GetComponentResourceMetrics returns CPU and Memory usage metrics for specified component
func (s *AgentService) GetComponentResourceMetrics(ctx context.Context, req *apiv1.GetComponentResourceMetricsReq) (*apiv1.GetComponentResourceMetricsResp, error) {
	componentType := req.GetComponentType()
	componentId := req.GetComponentId()

	// Validate required fields
	if componentType == "" {
		return nil, status.Errorf(codes.InvalidArgument, "component_type is required")
	}
	if componentId == "" {
		return nil, status.Errorf(codes.InvalidArgument, "component_id is required")
	}

	logger := tilog.WithContextL(ctx).With(
		zap.String("cluster_id", req.ClusterId),
		zap.String("component_type", componentType),
		zap.String("component_id", componentId),
	)
	logger.Info("get component resource metrics")

	ns := buildNamespace(req.ClusterId)

	// Get labels for logging
	componentLabel := GetComponentTypeLabel(componentType)
	instanceLabel := GetComponentInstanceLabel(componentType, componentId)

	// List pods for specified component type and ID
	podList, err := s.listComponentPods(ctx, ns, componentType, componentId)
	if err != nil {
		return nil, fmt.Errorf("failed to list pods: %w", err)
	}

	if len(podList.Items) == 0 {
		logger.Error("no pods found for component", zap.String("component_label", componentLabel), zap.String("instance_label", instanceLabel))
		return nil, status.Errorf(codes.NotFound, "no pods found for component %s/%s", componentType, componentId)
	}

	// Build metrics response
	result := make([]*apiv1.ComponentResourceMetrics, 0, len(podList.Items))
	metricsServerAvailable := s.metricsCli != nil

	logger.Info("building metrics for pods", zap.Int("pod_count", len(podList.Items)))
	for _, pod := range podList.Items {
		logger.Debug("processing pod", zap.String("pod_name", pod.Name))
		metrics, success := s.buildPodMetrics(ctx, &pod, ns, componentType, componentId)
		if !success {
			metricsServerAvailable = false
		}
		result = append(result, metrics)
	}

	return &apiv1.GetComponentResourceMetricsResp{
		Components:             result,
		TimestampMs:            time.Now().UnixMilli(),
		MetricsServerAvailable: metricsServerAvailable,
	}, nil
}

func (s *AgentService) listComponentPods(ctx context.Context, namespace, componentType, componentId string) (*corev1.PodList, error) {
	componentLabel := GetComponentTypeLabel(componentType)
	instanceLabel := GetComponentInstanceLabel(componentType, componentId)

	tilog.Debug(ctx, "listing component pods",
		zap.String("component_type", componentType),
		zap.String("component_id", componentId),
		zap.String("component_label", componentLabel),
		zap.String("instance_label", instanceLabel),
	)

	labelSelector := labels.SelectorFromSet(labels.Set{
		K8sComponentLabelKey: componentLabel,
		K8sInstanceLabelKey:  instanceLabel,
	})

	podList := &corev1.PodList{}
	err := s.dataplaneCli.List(ctx, podList,
		client.InNamespace(namespace),
		client.MatchingLabelsSelector{Selector: labelSelector})
	if err != nil {
		return nil, fmt.Errorf("failed to list pods: %w", err)
	}

	return podList, nil
}

func (s *AgentService) buildPodMetrics(ctx context.Context, pod *corev1.Pod, namespace, componentType, componentId string) (*apiv1.ComponentResourceMetrics, bool) {
	cpuTotal := getPodCPULimit(pod)
	memoryTotal := getPodMemoryLimit(pod)

	metrics := &apiv1.ComponentResourceMetrics{
		ComponentType:    componentType,
		ComponentId:      componentId,
		PodName:          pod.Name,
		NodeName:         pod.Spec.NodeName,
		CpuCoresTotal:    cpuTotal,
		MemoryBytesTotal: memoryTotal,
	}

	// Fetch actual usage from metrics-server if client is available
	if s.metricsCli == nil {
		return metrics, false
	}

	cpuUsed, memoryUsed, err := s.metricsCli.GetPodCPUAndMemory(ctx, namespace, pod.Name)
	if err != nil {
		tilog.Error(ctx, "failed to get pod metrics from metrics-server",
			zap.String("pod", pod.Name),
			zap.String("namespace", namespace),
			zap.Error(err),
		)
		return metrics, false
	}

	metrics.CpuCoresUsed = cpuUsed
	metrics.MemoryBytesUsed = memoryUsed
	return metrics, true
}

func getPodCPULimit(pod *corev1.Pod) float64 {
	totalMilliCPU := int64(0)
	for _, container := range pod.Spec.Containers {
		if container.Resources.Limits != nil {
			if limit := container.Resources.Limits.Cpu(); limit != nil {
				totalMilliCPU += limit.MilliValue()
				continue
			}
		}
		if container.Resources.Requests != nil {
			if request := container.Resources.Requests.Cpu(); request != nil {
				totalMilliCPU += request.MilliValue()
			}
		}
	}
	return float64(totalMilliCPU) / 1000.0
}

func getPodMemoryLimit(pod *corev1.Pod) uint64 {
	totalBytes := int64(0)
	for _, container := range pod.Spec.Containers {
		if container.Resources.Limits != nil {
			if limit := container.Resources.Limits.Memory(); limit != nil {
				totalBytes += limit.Value()
				continue
			}
		}
		if container.Resources.Requests != nil {
			if request := container.Resources.Requests.Memory(); request != nil {
				totalBytes += request.Value()
			}
		}
	}
	return uint64(totalBytes)
}

// SumComponentMetrics calculates total CPU/Memory usage across all components
func SumComponentMetrics(components []*apiv1.ComponentResourceMetrics) (cpuCoresTotal, cpuCoresUsed float64, memoryBytesTotal, memoryBytesUsed uint64) {
	for _, c := range components {
		cpuCoresTotal += c.CpuCoresTotal
		cpuCoresUsed += c.CpuCoresUsed
		memoryBytesTotal += c.MemoryBytesTotal
		memoryBytesUsed += c.MemoryBytesUsed
	}
	return
}
