package opsservice

import (
	"context"
	"encoding/json"
	"errors"
	"testing"

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

// createTestService creates a test UpgradeOperationService with a mock cloud provider
func createTestService() *UpgradeOperationService {
	return &UpgradeOperationService{
		rw: nil, // buildUpgradeSteps doesn't use rw
	}
}

func TestBuildUpgradeSteps_ControlPlaneOnly(t *testing.T) {
	// Setup
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{Name: "ng1", Version: "1.27.0"},
			{Name: "ng2", Version: "1.27.0"},
		},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.29.0",
	}

	// Execute
	steps, err := service.buildUpgradeSteps(context.Background(), req)

	// Assert
	require.NoError(t, err)
	require.NotNil(t, steps)
	assert.Equal(t, 2, steps.Size(), "Should have 2 control plane upgrade steps (1.28 and 1.29)")

	// Verify control plane steps
	step1 := steps[0]
	assert.Equal(t, o11yv1.ProcessorUpgradeControlPlane, step1.Processor)
	var opts1 o11yv1.ControlPlaneUpgradeOptions
	require.NoError(t, json.Unmarshal(step1.Options.Raw, &opts1))
	assert.Equal(t, "1.28", opts1.TargetVersion)

	step2 := steps[1]
	assert.Equal(t, o11yv1.ProcessorUpgradeControlPlane, step2.Processor)
	var opts2 o11yv1.ControlPlaneUpgradeOptions
	require.NoError(t, json.Unmarshal(step2.Options.Raw, &opts2))
	assert.Equal(t, "1.29.0", opts2.TargetVersion)
}

func TestBuildUpgradeSteps_WithClusterAddons(t *testing.T) {
	// Setup
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{Name: "ng1", Version: "1.27.0"},
		},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.28.0",
		ClusterAddons: []message.AddonUpgradeConfig{
			{Name: "vpc-cni", Version: "1.2.0", ResolveConflicts: "OVERWRITE"},
			{Name: "coredns", Version: "1.3.0"},
		},
	}

	// Execute
	steps, err := service.buildUpgradeSteps(context.Background(), req)

	// Assert
	require.NoError(t, err)
	require.NotNil(t, steps)
	assert.Equal(t, 3, steps.Size(), "Should have 1 control plane step + 2 addon steps (each addon is a separate task)")

	// Verify control plane step
	step1 := steps[0]
	assert.Equal(t, o11yv1.ProcessorUpgradeControlPlane, step1.Processor)

	// Verify addon steps - each addon is a separate task
	step2 := steps[1]
	assert.Equal(t, o11yv1.ProcessorUpgradeClusterAddons, step2.Processor)
	var addonsOpts2 o11yv1.ClusterAddonsUpgradeOptions
	require.NoError(t, json.Unmarshal(step2.Options.Raw, &addonsOpts2))
	assert.Len(t, addonsOpts2.Addons, 1)
	assert.Equal(t, "vpc-cni", addonsOpts2.Addons[0].Name)
	assert.Equal(t, "1.2.0", addonsOpts2.Addons[0].Version)
	assert.Equal(t, "OVERWRITE", addonsOpts2.Addons[0].ResolveConflicts)

	step3 := steps[2]
	assert.Equal(t, o11yv1.ProcessorUpgradeClusterAddons, step3.Processor)
	var addonsOpts3 o11yv1.ClusterAddonsUpgradeOptions
	require.NoError(t, json.Unmarshal(step3.Options.Raw, &addonsOpts3))
	assert.Len(t, addonsOpts3.Addons, 1)
	assert.Equal(t, "coredns", addonsOpts3.Addons[0].Name)
	assert.Equal(t, "1.3.0", addonsOpts3.Addons[0].Version)
}

func TestBuildUpgradeSteps_WithNodeGroups(t *testing.T) {
	// Setup
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{Name: "ng1", Version: "1.27.0"},
		},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.28.0",
		NodeGroupUpgrades: []message.NodeGroupUpgradeConfig{
			{
				NodeGroupName:            "ng1",
				RequiresVMStorageRollout: false,
				Strategy:                 "in-place",
			},
		},
	}

	// Execute
	steps, err := service.buildUpgradeSteps(context.Background(), req)

	// Assert
	require.NoError(t, err)
	require.NotNil(t, steps)
	assert.Equal(t, 2, steps.Size(), "Should have 1 control plane step + 1 node group step")

	// Verify control plane step
	step1 := steps[0]
	assert.Equal(t, o11yv1.ProcessorUpgradeControlPlane, step1.Processor)

	// Verify node group step
	step2 := steps[1]
	assert.Equal(t, o11yv1.ProcessorUpgradeNodeGroup, step2.Processor)
	var ngOpts o11yv1.NodeGroupUpgradeOptions
	require.NoError(t, json.Unmarshal(step2.Options.Raw, &ngOpts))
	assert.Equal(t, "ng1", ngOpts.NodeGroupName)
	assert.Equal(t, o11yv1.NodeGroupUpgradeStrategyInPlace, ngOpts.Strategy)
	assert.False(t, ngOpts.RequiresVMStorageRollout)
}

func TestBuildUpgradeSteps_EKSLaunchTemplateInPlaceAllowsName(t *testing.T) {
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{
				Name:                  "ng1",
				Version:               "1.27.0",
				LaunchTemplateVersion: "1",
				LaunchTemplateName:    "lt-ng1",
			},
		},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderEKS, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderEKS,
		TargetVersion:  "1.28.0",
		NodeGroupUpgrades: []message.NodeGroupUpgradeConfig{
			{
				NodeGroupName:            "ng1",
				RequiresVMStorageRollout: false,
				Strategy:                 o11yv1.NodeGroupUpgradeStrategyInPlace,
				LaunchTemplateVersion:    "2",
				LaunchTemplateName:       "lt-ng1",
			},
		},
	}

	steps, err := service.buildUpgradeSteps(context.Background(), req)

	require.NoError(t, err)
	require.NotNil(t, steps)
	require.Equal(t, 2, steps.Size())

	var ngOpts o11yv1.NodeGroupUpgradeOptions
	require.NoError(t, json.Unmarshal(steps[1].Options.Raw, &ngOpts))
	assert.Equal(t, "2", ngOpts.LaunchTemplateVersion)
	assert.Equal(t, "lt-ng1", ngOpts.LaunchTemplateName)
	assert.Empty(t, ngOpts.LaunchTemplateId)
}

func TestBuildUpgradeSteps_EKSLaunchTemplateInPlaceAllowsDefaultAMIRelease(t *testing.T) {
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{
				Name:                  "ng1",
				Version:               "1.27.0",
				LaunchTemplateVersion: "1",
			},
		},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderEKS, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderEKS,
		TargetVersion:  "1.28.0",
		NodeGroupUpgrades: []message.NodeGroupUpgradeConfig{
			{
				NodeGroupName:            "ng1",
				RequiresVMStorageRollout: false,
				Strategy:                 o11yv1.NodeGroupUpgradeStrategyInPlace,
			},
		},
	}

	steps, err := service.buildUpgradeSteps(context.Background(), req)

	require.NoError(t, err)
	require.NotNil(t, steps)
	require.Equal(t, 2, steps.Size())

	var ngOpts o11yv1.NodeGroupUpgradeOptions
	require.NoError(t, json.Unmarshal(steps[1].Options.Raw, &ngOpts))
	assert.Empty(t, ngOpts.LaunchTemplateVersion)
	assert.Empty(t, ngOpts.LaunchTemplateId)
	assert.Empty(t, ngOpts.LaunchTemplateName)
}

func TestBuildUpgradeSteps_WithVMStorageRollout(t *testing.T) {
	// Setup
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{Name: "vmstorage-ng", Version: "1.27.0"},
		},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.28.0",
		NodeGroupUpgrades: []message.NodeGroupUpgradeConfig{
			{
				NodeGroupName:            "vmstorage-ng",
				RequiresVMStorageRollout: true,
				Strategy:                 "blue-green",
				VMClusterHelmValues: map[string]interface{}{
					"vmstorage": map[string]interface{}{
						"replicaCount": 3,
					},
				},
				VMStorageSTSName:      "vmstorage-vmcluster",
				VMStorageSTSNamespace: "observability",
			},
		},
	}

	// Execute
	steps, err := service.buildUpgradeSteps(context.Background(), req)

	// Assert
	require.NoError(t, err)
	require.NotNil(t, steps)
	assert.Equal(t, 2, steps.Size(), "Should have 1 control plane step + 1 node group step")

	// Verify node group step includes VMCluster helm values
	step2 := steps[1]
	var ngOpts o11yv1.NodeGroupUpgradeOptions
	require.NoError(t, json.Unmarshal(step2.Options.Raw, &ngOpts))
	assert.True(t, ngOpts.RequiresVMStorageRollout)
	assert.NotNil(t, ngOpts.VMClusterHelmValues)
	assert.Equal(t, "vmstorage-vmcluster", ngOpts.VMStorageSTSName)
	assert.Equal(t, "observability", ngOpts.VMStorageSTSNamespace)
}

func TestBuildUpgradeSteps_WithVMStorageRolloutDefaultsSTS(t *testing.T) {
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups:          []providers.NodeGroup{{Name: "vmstorage-ng", Version: "1.27.0"}},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.28.0",
		NodeGroupUpgrades: []message.NodeGroupUpgradeConfig{
			{
				NodeGroupName:            "vmstorage-ng",
				RequiresVMStorageRollout: true,
				Strategy:                 o11yv1.NodeGroupUpgradeStrategyBlueGreen,
				VMClusterHelmValues: map[string]interface{}{
					"vmstorage": map[string]interface{}{"replicaCount": 3},
				},
			},
		},
	}

	steps, err := service.buildUpgradeSteps(context.Background(), req)

	require.NoError(t, err)
	require.Equal(t, 2, steps.Size())
	var ngOpts o11yv1.NodeGroupUpgradeOptions
	require.NoError(t, json.Unmarshal(steps[1].Options.Raw, &ngOpts))
	assert.True(t, ngOpts.RequiresVMStorageRollout)
	assert.Equal(t, "vmstorage-vmcluster", ngOpts.VMStorageSTSName)
	assert.Equal(t, "observability", ngOpts.VMStorageSTSNamespace)
}

func TestBuildUpgradeSteps_WithVMStorageRolloutEmptyVMClusterHelmValues(t *testing.T) {
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups:          []providers.NodeGroup{{Name: "vmstorage-ng", Version: "1.27.0"}},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.28.0",
		NodeGroupUpgrades: []message.NodeGroupUpgradeConfig{
			{
				NodeGroupName:            "vmstorage-ng",
				RequiresVMStorageRollout: true,
				Strategy:                 o11yv1.NodeGroupUpgradeStrategyBlueGreen,
				VMClusterHelmValues:      map[string]interface{}{},
			},
		},
	}

	steps, err := service.buildUpgradeSteps(context.Background(), req)

	require.NoError(t, err)
	require.Equal(t, 2, steps.Size())
	var ngOpts o11yv1.NodeGroupUpgradeOptions
	require.NoError(t, json.Unmarshal(steps[1].Options.Raw, &ngOpts))
	assert.True(t, ngOpts.RequiresVMStorageRollout)
	require.NotNil(t, ngOpts.VMClusterHelmValues)
	assert.JSONEq(t, `{}`, string(ngOpts.VMClusterHelmValues.Raw))
}

func TestBuildUpgradeSteps_VMStorageRolloutRejectsInPlace(t *testing.T) {
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups:          []providers.NodeGroup{{Name: "vmstorage-ng", Version: "1.27.0"}},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.28.0",
		NodeGroupUpgrades: []message.NodeGroupUpgradeConfig{
			{
				NodeGroupName:            "vmstorage-ng",
				RequiresVMStorageRollout: true,
				Strategy:                 o11yv1.NodeGroupUpgradeStrategyInPlace,
				VMClusterHelmValues: map[string]interface{}{
					"vmstorage": map[string]interface{}{"replicaCount": 3},
				},
			},
		},
	}

	_, err := service.buildUpgradeSteps(context.Background(), req)

	require.Error(t, err)
	assert.Contains(t, err.Error(), "vmstorage rollout is only supported for blue-green")
}

func TestBuildUpgradeSteps_VMClusterHelmValuesRequiresVMStorageRollout(t *testing.T) {
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups:          []providers.NodeGroup{{Name: "vmstorage-ng", Version: "1.27.0"}},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.28.0",
		NodeGroupUpgrades: []message.NodeGroupUpgradeConfig{
			{
				NodeGroupName: "vmstorage-ng",
				Strategy:      o11yv1.NodeGroupUpgradeStrategyInPlace,
				VMClusterHelmValues: map[string]interface{}{
					"vmstorage": map[string]interface{}{"replicaCount": 3},
				},
			},
		},
	}

	_, err := service.buildUpgradeSteps(context.Background(), req)

	require.Error(t, err)
	assert.Contains(t, err.Error(), "vmClusterHelmValues requires requiresVMStorageRollout")
}

func TestBuildUpgradeSteps_VMStorageRolloutRequiresVMClusterHelmValues(t *testing.T) {
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups:          []providers.NodeGroup{{Name: "vmstorage-ng", Version: "1.27.0"}},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.28.0",
		NodeGroupUpgrades: []message.NodeGroupUpgradeConfig{
			{
				NodeGroupName:            "vmstorage-ng",
				RequiresVMStorageRollout: true,
				Strategy:                 o11yv1.NodeGroupUpgradeStrategyBlueGreen,
			},
		},
	}

	_, err := service.buildUpgradeSteps(context.Background(), req)

	require.Error(t, err)
	assert.Contains(t, err.Error(), "vmClusterHelmValues is required")
}

func TestBuildUpgradeSteps_AllUpgradeTypes(t *testing.T) {
	// Setup
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{Name: "ng1", Version: "1.27.0"},
		},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.29.0",
		ClusterAddons: []message.AddonUpgradeConfig{
			{Name: "vpc-cni", Version: "1.2.0"},
		},
		NodeGroupUpgrades: []message.NodeGroupUpgradeConfig{
			{
				NodeGroupName:            "ng1",
				RequiresVMStorageRollout: false,
				Strategy:                 "in-place",
			},
		},
	}

	// Execute
	steps, err := service.buildUpgradeSteps(context.Background(), req)

	// Assert
	require.NoError(t, err)
	require.NotNil(t, steps)
	assert.Equal(t, 4, steps.Size(), "Should have 2 control plane steps + 1 addons step + 1 node group step")

	// Verify order: control plane -> addons -> node groups
	assert.Equal(t, o11yv1.ProcessorUpgradeControlPlane, steps[0].Processor)
	assert.Equal(t, o11yv1.ProcessorUpgradeControlPlane, steps[1].Processor)
	assert.Equal(t, o11yv1.ProcessorUpgradeClusterAddons, steps[2].Processor)
	assert.Equal(t, o11yv1.ProcessorUpgradeNodeGroup, steps[3].Processor)
}

func TestBuildUpgradeSteps_FailToGetProvider(t *testing.T) {
	// Setup
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, nil)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  "invalid-provider",
		TargetVersion:  "1.28.0",
	}

	// Execute
	steps, err := service.buildUpgradeSteps(context.Background(), req)

	// Assert
	assert.Error(t, err)
	assert.Nil(t, steps)
	assert.Contains(t, err.Error(), "failed to get cloud provider")
}

func TestBuildUpgradeSteps_FailToGetNodeGroups(t *testing.T) {
	// Setup
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		GetNodeGroupsErr:    errors.New("failed to get node groups"),
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.28.0",
	}

	// Execute
	steps, err := service.buildUpgradeSteps(context.Background(), req)

	// Assert
	assert.Error(t, err)
	assert.Nil(t, steps)
	assert.Contains(t, err.Error(), "failed to get node groups")
}

func TestBuildUpgradeSteps_IncompatibleNodeGroups(t *testing.T) {
	// Setup
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{Name: "ng1", Version: "1.20.0"}, // Too old, incompatible with 1.28
		},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.28.0",
	}

	// Execute
	steps, err := service.buildUpgradeSteps(context.Background(), req)

	// Assert
	assert.Error(t, err)
	assert.Nil(t, steps)
	assert.Contains(t, err.Error(), "couldn't upgrade cluster")
	assert.Contains(t, err.Error(), "not compatible")
}

func TestBuildUpgradeSteps_InvalidVersionFormat(t *testing.T) {
	// Setup
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{Name: "ng1", Version: "1.27.0"},
		},
		ControlPlaneVersion: "invalid", // invalid version from cluster
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.28.0",
	}

	// Execute
	steps, err := service.buildUpgradeSteps(context.Background(), req)

	// Assert
	assert.Error(t, err)
	assert.Nil(t, steps)
	assert.Contains(t, err.Error(), "failed to calculate intermediate versions")
}

func TestBuildUpgradeSteps_MajorVersionMismatch(t *testing.T) {
	// Setup
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{Name: "ng1", Version: "2.0.0"},
		},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "2.0.0",
	}

	// Execute
	steps, err := service.buildUpgradeSteps(context.Background(), req)

	// Assert
	assert.Error(t, err)
	assert.Nil(t, steps)
	assert.Contains(t, err.Error(), "major version mismatch")
}

func TestBuildUpgradeSteps_SameVersion(t *testing.T) {
	// Setup
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{Name: "ng1", Version: "1.27.0"},
		},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.27.0",
	}

	// Execute
	steps, err := service.buildUpgradeSteps(context.Background(), req)

	// Assert
	require.NoError(t, err)
	require.Equal(t, 0, steps.Size(), "Should have no control plane steps when current == target")
}

func TestBuildUpgradeSteps_MultipleNodeGroups(t *testing.T) {
	// Setup
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{Name: "ng1", Version: "1.27.0"},
			{Name: "ng2", Version: "1.27.0"},
		},
		ControlPlaneVersion: "1.27.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.28.0",
		NodeGroupUpgrades: []message.NodeGroupUpgradeConfig{
			{
				NodeGroupName:            "ng1",
				RequiresVMStorageRollout: false,
				Strategy:                 "in-place",
			},
			{
				NodeGroupName:            "ng2",
				RequiresVMStorageRollout: false,
				Strategy:                 "blue-green",
			},
		},
	}

	// Execute
	steps, err := service.buildUpgradeSteps(context.Background(), req)

	// Assert
	require.NoError(t, err)
	require.NotNil(t, steps)
	assert.Equal(t, 3, steps.Size(), "Should have 1 control plane step + 2 node group steps")

	// Verify node group steps
	step2 := steps[1]
	var ngOpts1 o11yv1.NodeGroupUpgradeOptions
	require.NoError(t, json.Unmarshal(step2.Options.Raw, &ngOpts1))
	assert.Equal(t, "ng1", ngOpts1.NodeGroupName)

	step3 := steps[2]
	var ngOpts2 o11yv1.NodeGroupUpgradeOptions
	require.NoError(t, json.Unmarshal(step3.Options.Raw, &ngOpts2))
	assert.Equal(t, "ng2", ngOpts2.NodeGroupName)
}

func TestBuildUpgradeSteps_ACK_IncompatibleNodeGroups(t *testing.T) {
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{Name: "np1", Version: "1.27.0"},
			{Name: "np2", Version: "1.27.0"},
		},
		ControlPlaneVersion: "1.27.0",
	}

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderACK, mockProvider)
	defer restore()

	service := createTestService()
	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "ack-cluster-cn-hangzhou",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderACK,
		TargetVersion:  "1.29.0",
	}

	_, err := service.buildUpgradeSteps(context.Background(), req)

	require.Error(t, err)
	require.Contains(t, err.Error(), "couldn't upgrade cluster")
	require.Contains(t, err.Error(), "not compatible")
}

func TestBuildUpgradeSteps_PatchVersionUpgrade_1_33_2_to_1_33_3(t *testing.T) {
	// Scenario: patch upgrade within the same minor version (1.33.2 -> 1.33.3)
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{Name: "ng1", Version: "1.33.2"},
		},
		ControlPlaneVersion: "1.33.2",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.33.3",
	}

	steps, err := service.buildUpgradeSteps(context.Background(), req)

	require.NoError(t, err)
	require.NotNil(t, steps)
	assert.Equal(t, 1, steps.Size(), "Patch upgrade within same minor should have only 1 control plane step")

	step1 := steps[0]
	assert.Equal(t, o11yv1.ProcessorUpgradeControlPlane, step1.Processor)
	var opts o11yv1.ControlPlaneUpgradeOptions
	require.NoError(t, json.Unmarshal(step1.Options.Raw, &opts))
	assert.Equal(t, "1.33.3", opts.TargetVersion)
}

func TestBuildUpgradeSteps_MultiMinorVersionUpgrade_1_31_to_1_33_5(t *testing.T) {
	// Scenario: multi-minor version upgrade (1.31 -> 1.33.5)
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{Name: "ng1", Version: "1.31.0"},
		},
		ControlPlaneVersion: "1.31.0",
	}
	service := createTestService()

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderMock, mockProvider)
	defer restore()

	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "test-cluster",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderMock,
		TargetVersion:  "1.33.5",
	}

	steps, err := service.buildUpgradeSteps(context.Background(), req)

	require.NoError(t, err)
	require.NotNil(t, steps)
	assert.Equal(t, 2, steps.Size(), "1.31 -> 1.33.5 should have 2 control plane steps (1.32 and 1.33.5)")

	step1 := steps[0]
	assert.Equal(t, o11yv1.ProcessorUpgradeControlPlane, step1.Processor)
	var opts1 o11yv1.ControlPlaneUpgradeOptions
	require.NoError(t, json.Unmarshal(step1.Options.Raw, &opts1))
	assert.Equal(t, "1.32", opts1.TargetVersion)

	step2 := steps[1]
	assert.Equal(t, o11yv1.ProcessorUpgradeControlPlane, step2.Processor)
	var opts2 o11yv1.ControlPlaneUpgradeOptions
	require.NoError(t, json.Unmarshal(step2.Options.Raw, &opts2))
	assert.Equal(t, "1.33.5", opts2.TargetVersion)
}

func TestBuildUpgradeSteps_ACK_BlueGreenStrategy(t *testing.T) {
	mockProvider := &clusterupgradeoperation.MockCloudProvider{
		NodeGroups: []providers.NodeGroup{
			{Name: "np1", Version: "1.27.0"},
		},
		ControlPlaneVersion: "1.27.0",
	}

	restore := providers.RegisterCloudProviderMock(o11yv1.CloudProviderACK, mockProvider)
	defer restore()

	service := createTestService()
	req := message.CreateClusterUpgradeReq{
		K8sClusterName: "ack-cluster-cn-hangzhou",
		Region:         "us-west-2",
		CloudProvider:  o11yv1.CloudProviderACK,
		TargetVersion:  "1.28.0",
		NodeGroupUpgrades: []message.NodeGroupUpgradeConfig{
			{
				NodeGroupName:            "np1",
				RequiresVMStorageRollout: false,
				Strategy:                 "blue-green",
			},
		},
	}

	_, err := service.buildUpgradeSteps(context.Background(), req)

	require.Error(t, err)
	require.Contains(t, err.Error(), "blue-green strategy is not supported for ACK node group upgrades")
}
