package v1

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"regexp"
	"strings"

	"github.com/pingcap/errors"
)

var gkeNodePoolNamePattern = regexp.MustCompile(`^[a-z]([-a-z0-9]*[a-z0-9])?$`)
var gkeResourceNamePattern = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$`)

// Keep this in sync with infra-provider/pkg/namer.GKENameLimit. infra-api
// cannot import pkg/namer because pkg/namer imports this API module.
const gkeNodePoolNameLimit = 40
const gkeResourceNameLimit = 63

const (
	GKEComputeClassNodePoolRoleBase  = "base"
	GKEComputeClassNodePoolRoleExtra = "extra"
)

type GKEComputeClassBindingAnnotation struct {
	ComputeClassName string                           `json:"computeClassName"`
	NodePools        []GKEComputeClassNodePoolBinding `json:"nodePools"`
}

type GKEComputeClassNodePoolBinding struct {
	Role         string `json:"role"`
	InstanceType string `json:"instanceType"`
	NodePoolName string `json:"nodePoolName"`
}

type gkeComputeClassCanonicalBinding struct {
	Index        int    `json:"index"`
	Role         string `json:"role"`
	InstanceType string `json:"instanceType"`
	NodePoolName string `json:"nodePoolName"`
}

// NormalizeGKENodePoolNames validates the user-facing API input before it is
// persisted into cluster annotations or sent to lower layers.
func NormalizeGKENodePoolNames(nodePoolNames []string) ([]string, error) {
	out := make([]string, 0, len(nodePoolNames))
	seen := map[string]struct{}{}
	for _, nodePoolName := range nodePoolNames {
		normalized, err := normalizeGKENodePoolName(nodePoolName)
		if err != nil {
			if strings.TrimSpace(nodePoolName) == "" {
				return nil, errors.Errorf("node_pool_names contains empty item")
			}
			return nil, errors.Errorf("node_pool_names contains invalid GKE node pool name %q", strings.TrimSpace(nodePoolName))
		}
		if _, ok := seen[normalized]; ok {
			return nil, errors.Errorf("node_pool_names contains duplicate item %s", normalized)
		}
		seen[normalized] = struct{}{}
		out = append(out, normalized)
	}
	return out, nil
}

func NormalizeGKEComputeClassName(name string) (string, error) {
	field := "compute_class_name"
	name = strings.TrimSpace(name)
	if name == "" {
		return "", errors.Errorf("%s is required", field)
	}
	if !gkeResourceNamePattern.MatchString(name) || len(name) > gkeResourceNameLimit {
		return "", errors.Errorf("invalid %s %q", field, name)
	}
	return name, nil
}

func normalizeGKENodePoolName(nodePoolName string) (string, error) {
	nodePoolName = strings.TrimSpace(nodePoolName)
	if nodePoolName == "" {
		return "", errors.Errorf("node_pool_name is required")
	}
	if !gkeNodePoolNamePattern.MatchString(nodePoolName) || len(nodePoolName) > gkeNodePoolNameLimit {
		return "", errors.Errorf("invalid GKE node pool name %q", nodePoolName)
	}
	return nodePoolName, nil
}

func NormalizeGKEComputeClassOrderedBindings(bindings []GKEComputeClassNodePoolBinding) ([]GKEComputeClassNodePoolBinding, error) {
	if len(bindings) != 2 {
		return nil, errors.Errorf("gke compute class bindings must contain exactly 2 nodePools, got %d", len(bindings))
	}

	out := make([]GKEComputeClassNodePoolBinding, 0, len(bindings))
	seenInstanceTypes := map[string]struct{}{}
	seenNodePoolNames := map[string]struct{}{}
	for i, binding := range bindings {
		role := strings.TrimSpace(binding.Role)
		expectedRole := GKEComputeClassNodePoolRoleExtra
		if i == 0 {
			expectedRole = GKEComputeClassNodePoolRoleBase
		}
		if role != expectedRole {
			return nil, errors.Errorf("gke compute class bindings[%d] role must be %s, got %s", i, expectedRole, role)
		}

		instanceType := strings.TrimSpace(binding.InstanceType)
		if instanceType == "" {
			return nil, errors.Errorf("gke compute class bindings[%d] has empty instanceType", i)
		}
		if _, ok := seenInstanceTypes[instanceType]; ok {
			return nil, errors.Errorf("gke compute class bindings contains duplicate instanceType %s", instanceType)
		}
		seenInstanceTypes[instanceType] = struct{}{}

		nodePoolName, err := normalizeGKENodePoolName(binding.NodePoolName)
		if err != nil {
			return nil, errors.Errorf("gke compute class bindings[%d] has invalid nodePoolName: %s", i, err)
		}
		if _, ok := seenNodePoolNames[nodePoolName]; ok {
			return nil, errors.Errorf("gke compute class bindings contains duplicate nodePoolName %s", nodePoolName)
		}
		seenNodePoolNames[nodePoolName] = struct{}{}

		out = append(out, GKEComputeClassNodePoolBinding{
			Role:         role,
			InstanceType: instanceType,
			NodePoolName: nodePoolName,
		})
	}
	return out, nil
}

func EncodeGKEComputeClassBindings(bindings []GKEComputeClassNodePoolBinding) (string, error) {
	bindings, err := NormalizeGKEComputeClassOrderedBindings(bindings)
	if err != nil {
		return "", err
	}
	canonical := make([]gkeComputeClassCanonicalBinding, 0, len(bindings))
	for i, binding := range bindings {
		canonical = append(canonical, gkeComputeClassCanonicalBinding{
			Index:        i,
			Role:         binding.Role,
			InstanceType: binding.InstanceType,
			NodePoolName: binding.NodePoolName,
		})
	}
	data, err := json.Marshal(canonical)
	if err != nil {
		return "", err
	}
	return string(data), nil
}

func DecodeGKEComputeClassBindings(raw string) ([]GKEComputeClassNodePoolBinding, error) {
	var canonical []gkeComputeClassCanonicalBinding
	if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &canonical); err != nil {
		return nil, err
	}
	bindings := make([]GKEComputeClassNodePoolBinding, 0, len(canonical))
	for i, binding := range canonical {
		if binding.Index != i {
			return nil, errors.Errorf("gke compute class bindings[%d] index must be %d, got %d", i, i, binding.Index)
		}
		bindings = append(bindings, GKEComputeClassNodePoolBinding{
			Role:         binding.Role,
			InstanceType: binding.InstanceType,
			NodePoolName: binding.NodePoolName,
		})
	}
	return NormalizeGKEComputeClassOrderedBindings(bindings)
}

func GKEComputeClassBindingsHash(bindings []GKEComputeClassNodePoolBinding) (string, error) {
	canonical, err := EncodeGKEComputeClassBindings(bindings)
	if err != nil {
		return "", err
	}
	sum := sha256.Sum256([]byte(canonical))
	return hex.EncodeToString(sum[:8]), nil
}

func GKEComputeClassBindingsEqual(a, b []GKEComputeClassNodePoolBinding) bool {
	a, err := NormalizeGKEComputeClassOrderedBindings(a)
	if err != nil {
		return false
	}
	b, err = NormalizeGKEComputeClassOrderedBindings(b)
	if err != nil {
		return false
	}
	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

// MergeGKEComputeClassInstanceTypes normalizes the desired instance-type order.
// The base instance type is always first; annotated or requested instance types
// are appended after it with duplicates removed.
func MergeGKEComputeClassInstanceTypes(baseInstanceType string, instanceTypes []string) []string {
	out := make([]string, 0, len(instanceTypes)+1)
	seen := map[string]struct{}{}
	for _, instanceType := range append([]string{baseInstanceType}, instanceTypes...) {
		instanceType = strings.TrimSpace(instanceType)
		if instanceType == "" {
			continue
		}
		if _, ok := seen[instanceType]; ok {
			continue
		}
		seen[instanceType] = struct{}{}
		out = append(out, instanceType)
	}
	return out
}

// ClusterGKEComputeClassBindingAnnotation parses and validates one structured
// compute-class binding annotation when it is present. The annotation is the
// single source of truth and always stores ordered base/extra bindings.
func ClusterGKEComputeClassBindingAnnotation(cluster *Cluster, annotationKey, baseInstanceType, baseNodePoolName string) (GKEComputeClassBindingAnnotation, bool, error) {
	var annotations map[string]string
	var annoResult GKEComputeClassBindingAnnotation
	if cluster != nil {
		annotations = cluster.GetAnnotations()
	}

	raw := strings.TrimSpace(annotations[annotationKey])
	if raw == "" {
		return GKEComputeClassBindingAnnotation{}, false, nil
	}

	annoResult, err := DecodeGKEComputeClassBindingAnnotation(raw)
	if err != nil {
		return GKEComputeClassBindingAnnotation{}, false, errors.Errorf("decode gke compute class binding annotation %s: %s", annotationKey, err)
	}

	annoResult, err = normalizeGKEComputeClassBindingAnnotation(annoResult, annotationKey, baseInstanceType, baseNodePoolName, false)
	if err != nil {
		return GKEComputeClassBindingAnnotation{}, false, err
	}
	return annoResult, true, nil
}

func DecodeGKEComputeClassBindingAnnotation(raw string) (GKEComputeClassBindingAnnotation, error) {
	var annotation GKEComputeClassBindingAnnotation
	if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &annotation); err != nil {
		return GKEComputeClassBindingAnnotation{}, err
	}
	return annotation, nil
}

func EncodeGKEComputeClassBindingAnnotation(annotation GKEComputeClassBindingAnnotation, annotationKey, baseInstanceType, baseNodePoolName string) (string, error) {
	annotation, err := NormalizeGKEComputeClassBindingAnnotation(annotation, annotationKey, baseInstanceType, baseNodePoolName)
	if err != nil {
		return "", err
	}
	data, err := json.Marshal(annotation)
	if err != nil {
		return "", err
	}
	return string(data), nil
}

func NormalizeGKEComputeClassBindingAnnotation(annotation GKEComputeClassBindingAnnotation, annotationKey, baseInstanceType, baseNodePoolName string) (GKEComputeClassBindingAnnotation, error) {
	return normalizeGKEComputeClassBindingAnnotation(annotation, annotationKey, baseInstanceType, baseNodePoolName, true)
}

func normalizeGKEComputeClassBindingAnnotation(annotation GKEComputeClassBindingAnnotation, annotationKey, baseInstanceType, baseNodePoolName string, requireBaseNodePoolName bool) (GKEComputeClassBindingAnnotation, error) {
	computeClassName, err := NormalizeGKEComputeClassName(annotation.ComputeClassName)
	if err != nil {
		return GKEComputeClassBindingAnnotation{}, errors.Errorf("gke compute class binding annotation %s has invalid computeClassName: %s", annotationKey, err)
	}
	baseInstanceType = strings.TrimSpace(baseInstanceType)
	if baseInstanceType == "" {
		return GKEComputeClassBindingAnnotation{}, errors.Errorf("base instance type is required to validate gke compute class binding annotation %s", annotationKey)
	}
	baseNodePoolName = strings.TrimSpace(baseNodePoolName)

	nodePools, err := NormalizeGKEComputeClassOrderedBindings(annotation.NodePools)
	if err != nil {
		return GKEComputeClassBindingAnnotation{}, errors.Errorf("gke compute class binding annotation %s is invalid: %s", annotationKey, err)
	}
	if nodePools[0].InstanceType != baseInstanceType {
		return GKEComputeClassBindingAnnotation{}, errors.Errorf("gke compute class binding annotation %s base instanceType must be %s, got %s", annotationKey, baseInstanceType, nodePools[0].InstanceType)
	}
	if requireBaseNodePoolName && baseNodePoolName != "" && nodePools[0].NodePoolName != baseNodePoolName {
		return GKEComputeClassBindingAnnotation{}, errors.Errorf("gke compute class binding annotation %s base nodePoolName must be %s, got %s", annotationKey, baseNodePoolName, nodePools[0].NodePoolName)
	}
	expectedComputeClassName, err := GKEComputeClassNameForBindings(nodePools)
	if err != nil {
		return GKEComputeClassBindingAnnotation{}, errors.Errorf("gke compute class binding annotation %s has invalid bindings: %s", annotationKey, err)
	}
	if computeClassName != expectedComputeClassName {
		return GKEComputeClassBindingAnnotation{}, errors.Errorf("gke compute class binding annotation %s computeClassName must be %s, got %s", annotationKey, expectedComputeClassName, computeClassName)
	}
	normalized := GKEComputeClassBindingAnnotation{
		ComputeClassName: computeClassName,
		NodePools:        nodePools,
	}
	return normalized, nil
}

func GKEComputeClassBindingAnnotationInstanceTypes(annotation GKEComputeClassBindingAnnotation) map[string]struct{} {
	out := map[string]struct{}{}
	for _, binding := range annotation.NodePools {
		if instanceType := strings.TrimSpace(binding.InstanceType); instanceType != "" {
			out[instanceType] = struct{}{}
		}
	}
	return out
}
