package server

import (
	"context"
	"math"
	"net/netip"
	"sort"
	"strings"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type bootstrapRURange struct {
	Min       uint32
	Max       uint32
	Unbounded bool
}

func normalizeCreateSharedPoolOptions(optionList []*CreateSharedPoolOptions) (*CreateSharedPoolOptions, error) {
	if len(optionList) > 1 {
		return nil, resourcePoolStatusError(codes.InvalidArgument, reasonInvalidBootstrapCapacity, "only one create options value is supported")
	}
	if len(optionList) == 0 || optionList[0] == nil {
		return &CreateSharedPoolOptions{}, nil
	}
	return optionList[0], nil
}

func validateAndResolveBootstrapCapacity(
	bootstrap *SharedPoolBootstrapCapacity,
	initConfig *entity.InitSharedPoolConfig,
	nodeSpecs []*entity.NodeSpec,
	ruConfig *entity.RuConfig,
) (*SharedPoolBootstrapCapacity, error) {
	if bootstrap == nil || bootstrap.TiKV == nil || bootstrap.TiKV.NodeSpec == nil {
		return nil, resourcePoolStatusError(codes.InvalidArgument, reasonInvalidBootstrapCapacity, "bootstrap_capacity.tikv.node_spec is required")
	}
	if err := validateCreationNodeCount(apiv1.ComponentType_TIKV, bootstrap.TiKV.NodeCount, ruConfig); err != nil {
		return nil, err
	}
	tikvWithLocalDisk := initConfig.GetTikvDataStorage().GetType() == entity.LocalSSD
	tikvSpecs := filterNodeSpecsForComponent(nodeSpecs, apiv1.ComponentType_TIKV, ruConfig)
	tikvSpec, ok := resolveBootstrapNodeSpec(bootstrap.TiKV.NodeSpec, tikvSpecs, tikvWithLocalDisk)
	if !ok {
		return nil, resourcePoolStatusError(codes.InvalidArgument, reasonInvalidBootstrapCapacity,
			"bootstrap_capacity.tikv.node_spec is unavailable or incompatible with TiKV storage")
	}

	resolved := &SharedPoolBootstrapCapacity{
		TiKV: &BootstrapComponentCapacity{
			NodeSpec:  tikvSpec,
			NodeCount: bootstrap.TiKV.NodeCount,
		},
	}
	if bootstrap.TiFlashCompute == nil {
		return resolved, nil
	}
	if bootstrap.TiFlashCompute.NodeSpec == nil {
		return nil, resourcePoolStatusError(codes.InvalidArgument, reasonInvalidBootstrapCapacity, "bootstrap_capacity.tiflash_compute.node_spec is required")
	}
	if err := validateCreationNodeCount(apiv1.ComponentType_TIFLASH_COMPUTE, bootstrap.TiFlashCompute.NodeCount, ruConfig); err != nil {
		return nil, err
	}
	computeWithLocalDisk := initConfig.GetTiflashCnStorage().GetType() == entity.LocalSSD
	computeSpecs := filterNodeSpecsForComponent(nodeSpecs, apiv1.ComponentType_TIFLASH_COMPUTE, ruConfig)
	computeSpec, ok := resolveBootstrapNodeSpec(bootstrap.TiFlashCompute.NodeSpec, computeSpecs, computeWithLocalDisk)
	if !ok {
		return nil, resourcePoolStatusError(codes.InvalidArgument, reasonInvalidBootstrapCapacity,
			"bootstrap_capacity.tiflash_compute.node_spec is unavailable or incompatible with TiFlash Compute storage")
	}
	resolved.TiFlashCompute = &BootstrapComponentCapacity{
		NodeSpec:  computeSpec,
		NodeCount: bootstrap.TiFlashCompute.NodeCount,
	}
	return resolved, nil
}

func deriveBootstrapRURange(bootstrap *SharedPoolBootstrapCapacity, ruConfig *entity.RuConfig) (*bootstrapRURange, error) {
	if bootstrap == nil || bootstrap.TiKV == nil || bootstrap.TiKV.NodeSpec == nil || ruConfig == nil {
		return nil, bootstrapRURangeError("TiKV bootstrap capacity and RU configuration are required")
	}

	tikvConfig := ruConfig.GetComponents()[strings.ToLower(apiv1.ComponentType_TIKV.String())]
	if tikvConfig == nil {
		return nil, bootstrapRURangeError("TiKV RU configuration is unavailable")
	}

	tiers, err := normalizedBootstrapRUTiers(tikvConfig)
	if err != nil {
		return nil, err
	}

	var selectedShapeTiers []bootstrapRUTier
	for configuredSpec, ruRange := range tikvConfig.GetNodeSpecRuRange() {
		cpu, memoryGi, err := utils.ParseNodeSpec(configuredSpec)
		if err != nil || uint32(cpu) != bootstrap.TiKV.NodeSpec.GetCpu() || uint32(memoryGi) != bootstrap.TiKV.NodeSpec.GetMemoryGi() {
			continue
		}
		selectedShapeTiers = appendUniqueBootstrapRUTier(selectedShapeTiers, bootstrapRUTier{Min: ruRange.GetMin(), Max: ruRange.GetMax()})
	}
	if len(selectedShapeTiers) == 0 {
		return nil, bootstrapRURangeError("TiKV node spec does not map to an RU tier")
	}
	if len(selectedShapeTiers) != 1 {
		return nil, bootstrapRURangeError("TiKV node spec maps to multiple RU tiers")
	}

	perNodeRU := uint64(bootstrap.TiKV.NodeSpec.GetCpu()) * uint64(tikvPerCpuRu)
	if bootstrap.TiKV.NodeCount == 0 || perNodeRU > math.MaxUint32/uint64(bootstrap.TiKV.NodeCount) {
		return nil, bootstrapRURangeError("TiKV bootstrap capacity exceeds the supported RCU range")
	}
	capacityRU := uint64(bootstrap.TiKV.NodeCount) * perNodeRU

	effectiveMin := max(selectedShapeTiers[0].Min, uint32(capacityRU))
	var effectiveTier *bootstrapRUTier
	for i := range tiers {
		if tiers[i].contains(effectiveMin) {
			effectiveTier = &tiers[i]
			break
		}
	}
	if effectiveTier == nil {
		return nil, bootstrapRURangeError("TiKV bootstrap capacity does not map to a configured RU tier")
	}

	effectiveMax := effectiveTier.Max
	unbounded := effectiveMax == 0
	if unbounded {
		effectiveMax = math.MaxUint32
	}

	return &bootstrapRURange{Min: effectiveMin, Max: effectiveMax, Unbounded: unbounded}, nil
}

type bootstrapRUTier struct {
	Min uint32
	Max uint32
}

func (t bootstrapRUTier) contains(ru uint32) bool {
	return ru >= t.Min && (t.Max == 0 || ru <= t.Max)
}

func normalizedBootstrapRUTiers(tikvConfig *entity.RuConfigComponentsMap) ([]bootstrapRUTier, error) {
	tiers := make([]bootstrapRUTier, 0, len(tikvConfig.GetNodeSpecRuRange()))
	for configuredSpec, ruRange := range tikvConfig.GetNodeSpecRuRange() {
		cpu, memoryGi, err := utils.ParseNodeSpec(configuredSpec)
		if err != nil || cpu <= 0 || memoryGi <= 0 || ruRange == nil {
			return nil, bootstrapRURangeError("TiKV RU configuration contains an invalid node-spec tier")
		}
		tier := bootstrapRUTier{Min: ruRange.GetMin(), Max: ruRange.GetMax()}
		if tier.Max != 0 && tier.Max < tier.Min {
			return nil, bootstrapRURangeError("TiKV RU configuration contains an invalid interval")
		}
		tiers = appendUniqueBootstrapRUTier(tiers, tier)
	}
	if len(tiers) == 0 {
		return nil, bootstrapRURangeError("TiKV RU configuration has no tiers")
	}

	sort.Slice(tiers, func(i, j int) bool {
		if tiers[i].Min != tiers[j].Min {
			return tiers[i].Min < tiers[j].Min
		}
		return tiers[i].Max < tiers[j].Max
	})
	for i := 1; i < len(tiers); i++ {
		previous := tiers[i-1]
		if previous.Max == 0 || tiers[i].Min <= previous.Max {
			return nil, bootstrapRURangeError("TiKV RU configuration contains overlapping intervals")
		}
	}
	return tiers, nil
}

func appendUniqueBootstrapRUTier(tiers []bootstrapRUTier, candidate bootstrapRUTier) []bootstrapRUTier {
	for _, tier := range tiers {
		if tier == candidate {
			return tiers
		}
	}
	return append(tiers, candidate)
}

func bootstrapRURangeError(message string) error {
	return resourcePoolStatusError(codes.FailedPrecondition, reasonBootstrapRURangeUnavailable, message)
}

func (s *serverImpl) validateO11yNetworkNonOverlap(ctx context.Context, networkCIDR string, enforce bool) error {
	if !enforce {
		return nil
	}
	if !s.o11yClusterCIDRConfigured {
		tilog.Warn(ctx, "cannot validate O11Y network overlap because configuration is missing")
		return resourcePoolStatusError(codes.FailedPrecondition, reasonO11yCIDRConfigurationMissing, "o11y cluster cidr is not configured")
	}

	networkPrefix, err := netip.ParsePrefix(networkCIDR)
	if err != nil {
		return status.Errorf(codes.InvalidArgument, "shared-pool network cidr is invalid: %v", err)
	}
	networkPrefix = networkPrefix.Masked()
	if networkPrefix.Overlaps(s.o11yClusterPrefix) {
		tilog.Warn(ctx, "shared-pool network overlaps O11Y network",
			zap.String("networkCIDR", networkPrefix.String()),
			zap.String("o11yClusterCIDR", s.o11yClusterPrefix.String()),
		)
		return resourcePoolStatusError(codes.FailedPrecondition, reasonO11yCIDRConflict, "shared-pool network cidr overlaps the configured o11y cluster cidr")
	}

	tilog.Info(ctx, "validated shared-pool O11Y network separation",
		zap.String("networkCIDR", networkPrefix.String()),
		zap.String("o11yClusterCIDR", s.o11yClusterPrefix.String()),
	)
	return nil
}
