package opsservice

import (
	"fmt"
	"strconv"
	"strings"

	o11yv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"github.com/tidbcloud/observability/pkg/common/message"
	"github.com/tidbcloud/observability/pkg/resourceworker/controllers/clusterupgradeoperation/providers"
)

// The k8s control plane version compatible with node version is at most 3 minor versions apart
const maxMinorVersionDiff = 3

// calculateIntermediateVersions calculates all intermediate versions between current and target
// Each step upgrades one minor version (e.g., 1.27 -> 1.28 -> 1.29)
func calculateIntermediateVersions(current, target string) ([]string, error) {
	currentParts := strings.Split(current, ".")
	targetParts := strings.Split(target, ".")

	if len(currentParts) < 2 || len(targetParts) < 2 {
		return nil, fmt.Errorf("invalid version format: current=%s, target=%s", current, target)
	}

	currentMajor, err := strconv.Atoi(currentParts[0])
	if err != nil {
		return nil, fmt.Errorf("invalid major version: %s", currentParts[0])
	}
	currentMinor, err := strconv.Atoi(currentParts[1])
	if err != nil {
		return nil, fmt.Errorf("invalid minor version: %s", currentParts[1])
	}

	targetMajor, err := strconv.Atoi(targetParts[0])
	if err != nil {
		return nil, fmt.Errorf("invalid major version: %s", targetParts[0])
	}
	targetMinor, err := strconv.Atoi(targetParts[1])
	if err != nil {
		return nil, fmt.Errorf("invalid minor version: %s", targetParts[1])
	}

	var versions []string
	major := currentMajor
	minor := currentMinor

	if currentMajor != targetMajor {
		return nil, fmt.Errorf("major version mismatch, can't handle it: current=%d, target=%d", currentMajor, targetMajor)
	}

	// Step through minor versions from current to target
	for minor < targetMinor {
		minor++
		if minor == targetMinor {
			// Use target as-is to preserve patch (e.g. "1.34.1")
			versions = append(versions, target)
		} else {
			versions = append(versions, fmt.Sprintf("%d.%d", major, minor))
		}
	}
	// Same minor but different patch (e.g. current="1.34" -> target="1.34.1")
	if minor == targetMinor && current != target && len(versions) == 0 {
		versions = append(versions, target)
	}

	return versions, nil
}

// isVersionCompatible checks if node group version is compatible with control plane version
// Kubernetes allows node group to be at most one minor version behind the control plane
func isVersionCompatible(cloudProvider o11yv1.CloudProvider, nodeGroupVersion, controlPlaneVersion string) bool {
	ngParts := strings.Split(nodeGroupVersion, ".")
	cpParts := strings.Split(controlPlaneVersion, ".")

	if len(ngParts) < 2 || len(cpParts) < 2 {
		return false
	}

	ngMajor, err1 := strconv.Atoi(ngParts[0])
	ngMinor, err2 := strconv.Atoi(ngParts[1])
	cpMajor, err3 := strconv.Atoi(cpParts[0])
	cpMinor, err4 := strconv.Atoi(cpParts[1])

	if err1 != nil || err2 != nil || err3 != nil || err4 != nil {
		return false
	}

	// Major version must match
	if ngMajor != cpMajor {
		return false
	}

	// Node group can be at most one minor version behind, or same version
	diff := cpMinor - ngMinor
	if cloudProvider != o11yv1.CloudProviderACK {
		return diff >= 0 && diff <= maxMinorVersionDiff
	} else {
		return diff >= 0 && diff <= 1
	}
}

// checkNodeGroupsCompatible returns nil if all node groups are compatible with the target control plane version,
// otherwise returns an error describing which node groups are incompatible.
func checkNodeGroupsCompatible(req message.CreateClusterUpgradeReq, nodeGroups []providers.NodeGroup, targetCPVersion string) error {
	var incompatible []string
	for _, ng := range nodeGroups {
		// For ACK, we can ignore the version of the node group,
		// because the version of the node group is the same as the control plane version.
		if req.CloudProvider == o11yv1.CloudProviderACK && ng.Version == "" {
			continue
		}
		if !isVersionCompatible(req.CloudProvider, ng.Version, targetCPVersion) {
			incompatible = append(incompatible, fmt.Sprintf("%s (version %s)", ng.Name, ng.Version))
		}
	}
	if len(incompatible) == 0 {
		return nil
	}
	return fmt.Errorf("node groups not compatible with target version %s: %s", targetCPVersion, strings.Join(incompatible, ", "))
}
