package utils

import (
	"context"
	"encoding/hex"
	"fmt"
	"hash/fnv"
	"regexp"
	"strconv"
	"strings"

	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/trace"
	"go.uber.org/zap"
)

const (
	TraceIdKey = "trace_id"
)

var tracer = otel.Tracer("Workflow")

func GetTraceId(ctx context.Context) actions.ActionArg {
	return actions.ActionArgString(getTraceId(ctx))
}

func getTraceId(ctx context.Context) string {
	spanCtx := trace.SpanContextFromContext(ctx)
	traceId := ""
	if spanCtx.HasTraceID() {
		traceId = spanCtx.TraceID().String()
	}
	return traceId
}

func GenerateContext(args actions.ActionArgs) context.Context {
	ctx := context.TODO()
	traceId, err := args.GetString(TraceIdKey)
	if err != nil {
		tilog.Error(ctx, "get traceId failed", zap.Error(err))
		return ctx
	}

	cctx, span := tracer.Start(ctx, "")
	if traceId == "" {
		return cctx
	}

	traceIdHex, err := hex.DecodeString(traceId)
	if err != nil {
		tilog.Error(cctx, "decode traceId failed", zap.Error(err))
		return ctx
	} else {
		cctx = trace.ContextWithSpanContext(cctx, span.SpanContext().WithTraceID(trace.TraceID(traceIdHex)))
	}

	return cctx
}

func GeneratedDagExecId(ctx context.Context, key string) string {

	id := fmt.Sprintf("%s-%s", key, FnvHash(getTraceId(ctx)))
	if len(id) > 128 {
		id = id[:127]
	}
	// Ensure the ID ends with an alphanumeric character (RFC 1123 compliance)
	id = strings.TrimRight(id, "-.")
	return id
}

func FnvHash(raw string) string {
	hash := fnv.New32()
	// fnv.New32().Write() never return error
	_, _ = hash.Write([]byte(raw))
	return fmt.Sprintf("%x", hash.Sum32())
}

func ParseNodeSpec(spec string) (cpu, memory int, err error) {
	// convert pd node spec
	if spec == "" {
		return 0, 0, fmt.Errorf("node spec is empty")
	}

	re := regexp.MustCompile(`(\d+)C(\d+)G`)
	matches := re.FindStringSubmatch(spec)
	if len(matches) != 3 {
		re := regexp.MustCompile(`(\d+)c(\d+)g`)
		matches = re.FindStringSubmatch(spec)
	}

	if len(matches) != 3 {
		return 0, 0, fmt.Errorf("invalid node spec format: %s", spec)
	}

	cpu, err = strconv.Atoi(matches[1])
	if err != nil {
		return 0, 0, fmt.Errorf("invalid CPU value: %s", matches[1])
	}

	memory, err = strconv.Atoi(matches[2])
	if err != nil {
		return 0, 0, fmt.Errorf("invalid memory value: %s", matches[2])
	}

	return cpu, memory, nil
}
