package componentnodevcpu

import (
	"context"
	"log"
	"slices"
	"strconv"
	"strings"

	"github.com/tidbcloud/cluster-service-ng/internal/dataplane-exporter/pkg/attribution/nodecomponent"
	"github.com/tidbcloud/cluster-service-ng/internal/dataplane-exporter/pkg/source/kubernetes"
)

type LiveReader struct {
	source kubernetes.NodePodSource
}

func NewLiveReader(source kubernetes.NodePodSource) *LiveReader {
	return &LiveReader{source: source}
}

func (r *LiveReader) Read(ctx context.Context) (Snapshot, error) {
	nodes, err := r.source.ListNodes(ctx)
	if err != nil {
		return Snapshot{}, err
	}

	pods, err := r.source.ListPods(ctx)
	if err != nil {
		return Snapshot{}, err
	}

	attributions := nodecomponent.AttributeNodes(nodes, pods)
	diagnosticsByKey := map[string]Diagnostic{
		diagnosticKey(nodecomponent.StateOwned, nodecomponent.NodeClassAdmin):    {State: nodecomponent.StateOwned, NodeClass: nodecomponent.NodeClassAdmin, Nodes: 0},
		diagnosticKey(nodecomponent.StateIdle, nodecomponent.NodeClassWorker):    {State: nodecomponent.StateIdle, NodeClass: nodecomponent.NodeClassWorker, Nodes: 0},
		diagnosticKey(nodecomponent.StateMixed, nodecomponent.NodeClassWorker):   {State: nodecomponent.StateMixed, NodeClass: nodecomponent.NodeClassWorker, Nodes: 0},
		diagnosticKey(nodecomponent.StateOwned, nodecomponent.NodeClassWorker):   {State: nodecomponent.StateOwned, NodeClass: nodecomponent.NodeClassWorker, Nodes: 0},
		diagnosticKey(nodecomponent.StateUnknown, nodecomponent.NodeClassWorker): {State: nodecomponent.StateUnknown, NodeClass: nodecomponent.NodeClassWorker, Nodes: 0},
	}
	mismatchDiagnosticsByKey := map[string]MismatchDiagnostic{
		nodecomponent.NodeClassWorker: {NodeClass: nodecomponent.NodeClassWorker, Nodes: 0},
	}
	samplesByKey := make(map[string]Sample)
	mismatchedNodeNames := make(map[string]struct{})
	for _, node := range nodes {
		if workerNodeCPUMismatch(node) {
			mismatchedNodeNames[node.Name] = struct{}{}
			log.Printf("component-node-vcpu: worker node %q node-spec %q mismatches capacity CPU %.0f", node.Name, node.NodeSpec, node.CapacityCPU)
		}
	}

	for _, item := range attributions {
		key := diagnosticKey(item.State, item.NodeClass)
		diagnostic := diagnosticsByKey[key]
		diagnostic.State = item.State
		diagnostic.NodeClass = item.NodeClass
		diagnostic.Nodes++
		diagnosticsByKey[key] = diagnostic

		if _, ok := mismatchedNodeNames[item.NodeName]; ok {
			mismatchDiagnostic := mismatchDiagnosticsByKey[item.NodeClass]
			mismatchDiagnostic.NodeClass = item.NodeClass
			mismatchDiagnostic.Nodes++
			mismatchDiagnosticsByKey[item.NodeClass] = mismatchDiagnostic
		}

		if item.State == nodecomponent.StateIdle || item.VCPU == 0 {
			continue
		}

		sampleKey := sampleMetricKey(item.Tenant, item.ResourcePool, item.Component, item.Cluster, item.NodeClass)
		sample := samplesByKey[sampleKey]
		sample.Tenant = item.Tenant
		sample.ResourcePool = item.ResourcePool
		sample.Component = item.Component
		sample.Cluster = item.Cluster
		sample.NodeClass = item.NodeClass
		sample.VCPU += item.VCPU
		samplesByKey[sampleKey] = sample
	}

	samples := make([]Sample, 0, len(samplesByKey))
	for _, sample := range samplesByKey {
		samples = append(samples, sample)
	}
	slices.SortFunc(samples, func(a, b Sample) int {
		return strings.Compare(sampleMetricKey(a.Tenant, a.ResourcePool, a.Component, a.Cluster, a.NodeClass), sampleMetricKey(b.Tenant, b.ResourcePool, b.Component, b.Cluster, b.NodeClass))
	})

	diagnostics := make([]Diagnostic, 0, len(diagnosticsByKey))
	for _, diagnostic := range diagnosticsByKey {
		diagnostics = append(diagnostics, diagnostic)
	}
	slices.SortFunc(diagnostics, func(a, b Diagnostic) int {
		if cmp := strings.Compare(a.State, b.State); cmp != 0 {
			return cmp
		}
		return strings.Compare(a.NodeClass, b.NodeClass)
	})

	mismatchDiagnostics := make([]MismatchDiagnostic, 0, len(mismatchDiagnosticsByKey))
	for _, mismatchDiagnostic := range mismatchDiagnosticsByKey {
		mismatchDiagnostics = append(mismatchDiagnostics, mismatchDiagnostic)
	}
	slices.SortFunc(mismatchDiagnostics, func(a, b MismatchDiagnostic) int {
		return strings.Compare(a.NodeClass, b.NodeClass)
	})

	return Snapshot{
		Samples:             samples,
		Diagnostics:         diagnostics,
		MismatchDiagnostics: mismatchDiagnostics,
	}, nil
}

func diagnosticKey(state, nodeClass string) string {
	return state + ":" + nodeClass
}

func sampleMetricKey(tenant, resourcePool, component, cluster, nodeClass string) string {
	return strings.Join([]string{
		tenant,
		resourcePool,
		component,
		cluster,
		nodeClass,
	}, "|")
}

func workerNodeCPUMismatch(node kubernetes.Node) bool {
	nodeSpecCPU := parseNodeSpecCPU(node.NodeSpec)
	if nodeSpecCPU == 0 || node.CapacityCPU == 0 {
		return false
	}

	return nodeSpecCPU != node.CapacityCPU
}

func parseNodeSpecCPU(nodeSpec string) float64 {
	nodeSpec = strings.TrimSpace(nodeSpec)
	if nodeSpec == "" {
		return 0
	}

	// Dataplane worker nodes encode node spec like "8C16G"; we only need the CPU
	// prefix before "C" and accept lowercase variants defensively.
	upperNodeSpec := strings.ToUpper(nodeSpec)
	idx := strings.Index(upperNodeSpec, "C")
	if idx <= 0 {
		return 0
	}

	vcpu, err := strconv.Atoi(nodeSpec[:idx])
	if err != nil {
		return 0
	}

	return float64(vcpu)
}
