// Copyright 2025 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ocm

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"strings"
	"testing"

	"dario.cat/mergo"
	"github.com/blang/semver/v4"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"

	azcorearm "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"

	arohcpv1alpha1 "github.com/openshift-online/ocm-sdk-go/arohcp/v1alpha1"
	ocmerrors "github.com/openshift-online/ocm-sdk-go/errors"

	"github.com/Azure/ARO-HCP/internal/api/coreapi"
	"github.com/Azure/ARO-HCP/internal/api/fleetapi"
	"github.com/Azure/ARO-HCP/internal/api/metadataapi"
	"github.com/Azure/ARO-HCP/internal/apitesting/coreapitesting"
)

const (
	dummyURL = "https://redhat.com"
	dummyCA  = `-----BEGIN CERTIFICATE-----
MIICMzCCAZygAwIBAgIJALiPnVsvq8dsMA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV
BAYTAlVTMQwwCgYDVQQIEwNmb28xDDAKBgNVBAcTA2ZvbzEMMAoGA1UEChMDZm9v
MQwwCgYDVQQLEwNmb28xDDAKBgNVBAMTA2ZvbzAeFw0xMzAzMTkxNTQwMTlaFw0x
ODAzMTgxNTQwMTlaMFMxCzAJBgNVBAYTAlVTMQwwCgYDVQQIEwNmb28xDDAKBgNV
BAcTA2ZvbzEMMAoGA1UEChMDZm9vMQwwCgYDVQQLEwNmb28xDDAKBgNVBAMTA2Zv
bzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzdGfxi9CNbMf1UUcvDQh7MYB
OveIHyc0E0KIbhjK5FkCBU4CiZrbfHagaW7ZEcN0tt3EvpbOMxxc/ZQU2WN/s/wP
xph0pSfsfFsTKM4RhTWD2v4fgk+xZiKd1p0+L4hTtpwnEw0uXRVd0ki6muwV5y/P
+5FHUeldq+pgTcgzuK8CAwEAAaMPMA0wCwYDVR0PBAQDAgLkMA0GCSqGSIb3DQEB
BQUAA4GBAJiDAAtY0mQQeuxWdzLRzXmjvdSuL9GoyT3BF/jSnpxz5/58dba8pWen
v3pj4P3w5DoOso0rzkZy2jEsEitlVM2mLSbQpMM+MUVQCQoiG6W9xuCFuxSrwPIS
pAqEAuV4DNoxQKKWmhVv+J0ptMWD25Pnpxeq5sXzghfJnslJlQND
-----END CERTIFICATE-----
`
)

var dummyAudiences = []string{"audience1", "audience2"}

func spcWithDesiredVersion(version string) *coreapi.ServiceProviderCluster {
	return &coreapi.ServiceProviderCluster{
		Spec: coreapi.ServiceProviderClusterSpec{
			ControlPlaneVersion: coreapi.ServiceProviderClusterSpecVersion{
				DesiredVersion: ptr.To(semver.MustParse(normalizeDesiredVersionForSemver(version))),
			},
		},
	}
}

func normalizeDesiredVersionForSemver(version string) string {
	if strings.Contains(version, "-") {
		return version
	}
	parts := strings.Split(version, ".")
	if len(parts) >= 3 {
		return version
	}
	if patch, ok := map[string]string{"4.19": "34", "4.20": "25", "4.21": "20", "4.22": "1"}[version]; ok {
		return version + "." + patch
	}
	return version + ".0"
}

func spcWithDesiredVersionFromHCPCluster(hcpCluster *coreapi.HCPOpenShiftCluster) *coreapi.ServiceProviderCluster {
	versionID := hcpCluster.CustomerProperties.Version.ID
	if versionID == "" {
		versionID = "4.20"
	}
	return spcWithDesiredVersion(versionID)
}

func TestWithImmutableAttributes(t *testing.T) {
	testCases := []struct {
		name       string
		hcpCluster *coreapi.HCPOpenShiftCluster
		want       *arohcpv1alpha1.Cluster
	}{
		{
			name:       "simple default",
			hcpCluster: &coreapi.HCPOpenShiftCluster{},
			want:       ocmCluster(t, ocmClusterDefaults(coreapitesting.TestLocation)),
		},
		{
			name: "converts stable version from RP to CS (adds patch and prefix)",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				CustomerProperties: coreapi.HCPOpenShiftClusterCustomerProperties{
					Version: coreapi.VersionProfile{
						ID:           "4.20",
						ChannelGroup: "stable",
					},
				},
			},
			want: ocmCluster(t, ocmClusterDefaults(coreapitesting.TestLocation).
				Version(arohcpv1alpha1.NewVersion().
					ID("openshift-v4.20.25").
					ChannelGroup("stable"))),
		},
		{
			name: "converts candidate version from RP to CS (preserves patch)",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				CustomerProperties: coreapi.HCPOpenShiftClusterCustomerProperties{
					Version: coreapi.VersionProfile{
						ID:           "4.21.19",
						ChannelGroup: "candidate",
					},
				},
			},
			want: ocmCluster(t, ocmClusterDefaults(coreapitesting.TestLocation).
				Version(arohcpv1alpha1.NewVersion().
					ID("openshift-v4.21.19-candidate").
					ChannelGroup("candidate"))),
		},
		{
			name: "converts nightly version from RP to CS (preserves semver)",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				CustomerProperties: coreapi.HCPOpenShiftClusterCustomerProperties{
					Version: coreapi.VersionProfile{
						ID:           "4.21.0-0.nightly-2025-01-01",
						ChannelGroup: "nightly",
					},
				},
			},
			want: ocmCluster(t, ocmClusterDefaults(coreapitesting.TestLocation).
				Version(arohcpv1alpha1.NewVersion().
					ID("openshift-v4.21.0-0.nightly-2025-01-01-nightly").
					ChannelGroup("nightly"))),
		},
		{
			name: "with version 4.19",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				CustomerProperties: coreapi.HCPOpenShiftClusterCustomerProperties{
					Version: coreapi.VersionProfile{ID: "4.19", ChannelGroup: "stable"},
				},
			},
			want: ocmCluster(t, ocmClusterDefaults(coreapitesting.TestLocation).Version(
				arohcpv1alpha1.NewVersion().ID("openshift-v4.19.34").ChannelGroup("stable"))),
		},
		{
			name: "with version 4.21",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				CustomerProperties: coreapi.HCPOpenShiftClusterCustomerProperties{
					Version: coreapi.VersionProfile{ID: "4.21", ChannelGroup: "stable"},
				},
			},
			want: ocmCluster(t, ocmClusterDefaults(coreapitesting.TestLocation).Version(
				arohcpv1alpha1.NewVersion().ID("openshift-v4.21.20").ChannelGroup("stable"))),
		},
		{
			name: "with CryptoRestrictions set to FIPS",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				CustomerProperties: coreapi.HCPOpenShiftClusterCustomerProperties{
					CryptoRestrictions: metadataapi.CryptoRestrictionsFIPS,
				},
			},
			want: ocmCluster(t, ocmClusterDefaults(coreapitesting.TestLocation).FIPS(true)),
		},
		{
			name: "with CryptoRestrictions set to None",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				CustomerProperties: coreapi.HCPOpenShiftClusterCustomerProperties{
					CryptoRestrictions: metadataapi.CryptoRestrictionsNone,
				},
			},
			want: ocmCluster(t, ocmClusterDefaults(coreapitesting.TestLocation).FIPS(false)),
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			var buf bytes.Buffer
			require.NoError(t, arohcpv1alpha1.MarshalCluster(tc.want, &buf))
			want := buf.String()
			hcpCluster := coreapitesting.ClusterTestCase(t, tc.hcpCluster)
			csVersionID, err := clusterCSVersionID(spcWithDesiredVersionFromHCPCluster(hcpCluster), hcpCluster)
			require.NoError(t, err)
			builder, azureBuilder, err := withImmutableAttributes(
				ocmClusterDefaults(coreapitesting.TestLocation),
				hcpCluster,
				coreapitesting.TestSubscriptionID,
				coreapitesting.TestResourceGroupName,
				coreapitesting.TestTenantID,
				coreapitesting.TestManagedIdentitiesDataPlaneIdentityURL,
				csVersionID,
			)
			require.NoError(t, err)
			result, err := builder.Azure(azureBuilder).Build()
			require.NoError(t, err)
			buf.Reset()
			require.NoError(t, arohcpv1alpha1.MarshalCluster(result, &buf))
			got := buf.String()
			assert.JSONEq(t, want, got)
		})
	}
}

func testResourceID(t *testing.T) *azcorearm.ResourceID {
	resourceID, err := azcorearm.ParseResourceID(coreapitesting.TestClusterResourceID)
	require.NoError(t, err)
	return resourceID
}

func ocmCluster(t *testing.T, builders ...*arohcpv1alpha1.ClusterBuilder) *arohcpv1alpha1.Cluster {
	var mergedCluster map[string]interface{}

	for _, builder := range builders {
		var rawCluster map[string]interface{}
		var buffer bytes.Buffer

		cluster, err := builder.Build()
		require.NoError(t, err)
		require.NoError(t, arohcpv1alpha1.MarshalCluster(cluster, &buffer))
		require.NoError(t, json.Unmarshal(buffer.Bytes(), &rawCluster))
		require.NoError(t, mergo.Merge(&mergedCluster, rawCluster, mergo.WithOverride))
	}

	data, err := coreapi.MarshalJSON(mergedCluster)
	require.NoError(t, err)
	cluster, err := arohcpv1alpha1.UnmarshalCluster(data)
	require.NoError(t, err)

	return cluster
}

func ocmClusterDefaults(azureLocation string) *arohcpv1alpha1.ClusterBuilder {
	// This reflects how the immutable attributes get set when passed a minimally
	// valid RP cluster, using constants from internal/api/testhelpers.go.
	return arohcpv1alpha1.NewCluster().
		API(arohcpv1alpha1.NewClusterAPI().
			Listening(arohcpv1alpha1.ListeningMethodExternal)).
		Azure(arohcpv1alpha1.NewAzure().
			ManagedResourceGroupName(coreapitesting.TestManagedResourceGroupName).
			NetworkSecurityGroupResourceID(coreapitesting.TestNetworkSecurityGroupResourceID).
			NodesOutboundConnectivity(arohcpv1alpha1.NewAzureNodesOutboundConnectivity().
				OutboundType(csOutboundType)).
			OperatorsAuthentication(arohcpv1alpha1.NewAzureOperatorsAuthentication().
				ManagedIdentities(arohcpv1alpha1.NewAzureOperatorsAuthenticationManagedIdentities().
					ControlPlaneOperatorsManagedIdentities(make(map[string]*arohcpv1alpha1.AzureControlPlaneManagedIdentityBuilder)).
					DataPlaneOperatorsManagedIdentities(make(map[string]*arohcpv1alpha1.AzureDataPlaneManagedIdentityBuilder)).
					ManagedIdentitiesDataPlaneIdentityUrl(coreapitesting.TestManagedIdentitiesDataPlaneIdentityURL))).
			ResourceGroupName(strings.ToLower(coreapitesting.TestResourceGroupName)).
			ResourceName(strings.ToLower(coreapitesting.TestClusterName)).
			SubnetResourceID(coreapitesting.TestSubnetResourceID).
			VnetIntegrationSubnetResourceID(coreapitesting.TestVnetIntegrationSubnetResourceID).
			SubscriptionID(strings.ToLower(coreapitesting.TestSubscriptionID)).
			TenantID(coreapitesting.TestTenantID),
		).
		CCS(arohcpv1alpha1.NewCCS().Enabled(true)).
		CloudProvider(arohcpv1alpha1.NewCloudProvider().
			ID("azure")).
		DomainPrefix("testcluster").
		Hypershift(arohcpv1alpha1.NewHypershift().
			Enabled(true)).
		ID(coreapitesting.TestPendingClusterServiceIDClusterIDSegment).
		Name(strings.ToLower(coreapitesting.TestClusterName)).
		Network(arohcpv1alpha1.NewNetwork().
			HostPrefix(23).
			MachineCIDR("10.0.0.0/16").
			PodCIDR("10.128.0.0/14").
			ServiceCIDR("172.30.0.0/16").
			Type("OVNKubernetes")).
		Autoscaler(arohcpv1alpha1.NewClusterAutoscaler().
			PodPriorityThreshold(-10).
			MaxNodeProvisionTime("15m").
			MaxPodGracePeriod(600)).
		Product(arohcpv1alpha1.NewProduct().
			ID("aro")).
		Region(arohcpv1alpha1.NewCloudRegion().
			ID(azureLocation)).
		Version(arohcpv1alpha1.NewVersion().
			ID("openshift-v4.20.25").
			ChannelGroup("stable")).
		ImageRegistry(arohcpv1alpha1.NewClusterImageRegistry().
			State(csImageRegistryStateEnabled)).
		RegistryConfig(arohcpv1alpha1.NewClusterRegistryConfig().
			ImageDigestMirrors()).
		FIPS(false)
}

func getHCPNodePoolResource(opts ...func(*coreapi.HCPOpenShiftClusterNodePool)) *coreapi.HCPOpenShiftClusterNodePool {
	nodePool := &coreapi.HCPOpenShiftClusterNodePool{
		Properties: coreapi.HCPOpenShiftClusterNodePoolProperties{
			Platform: coreapi.NodePoolPlatformProfile{
				OSDisk: coreapi.OSDiskProfile{
					// SizeGiB is initialized to 64 to reflect the default value set by SetDefaultValuesNodePool
					// in the real API flow. This ensures tests match production behavior where SizeGiB is never nil.
					SizeGiB:                ptr.To(int32(64)),
					DiskStorageAccountType: metadataapi.DiskStorageAccountTypePremium_LRS,
					DiskType:               metadataapi.OsDiskTypeManaged,
				},
			},
		},
	}

	for _, opt := range opts {
		opt(nodePool)
	}
	return nodePool
}

// Base CS nodepool builder that reflects the defaults set in getHCPNodePoolResource.
func getBaseCSNodePoolBuilder() *arohcpv1alpha1.NodePoolBuilder {
	return arohcpv1alpha1.NewNodePool().
		ID("").
		AvailabilityZone("").
		AzureNodePool(arohcpv1alpha1.NewAzureNodePool().
			ResourceName("").
			VMSize("").
			EncryptionAtHost(
				arohcpv1alpha1.NewAzureNodePoolEncryptionAtHost().
					State(csEncryptionAtHostStateDisabled),
			).
			OsDisk(arohcpv1alpha1.NewAzureNodePoolOsDisk().
				SizeGibibytes(64).
				StorageAccountType(string(metadataapi.DiskStorageAccountTypePremium_LRS)).
				Persistence("persistent"),
			),
		).
		Subnet("").
		Version(arohcpv1alpha1.NewVersion().
			ID("").
			ChannelGroup(""),
		).
		Replicas(0).
		AutoRepair(false)
}

func TestBuildCSNodePool(t *testing.T) {
	resourceID := testResourceID(t)
	testCases := []struct {
		name               string
		hcpNodePool        *coreapi.HCPOpenShiftClusterNodePool
		expectedCSNodePool *arohcpv1alpha1.NodePoolBuilder
	}{
		{
			name:               "zero",
			hcpNodePool:        getHCPNodePoolResource(),
			expectedCSNodePool: getBaseCSNodePoolBuilder(),
		},
		{
			name: "handle multiple taints",
			hcpNodePool: getHCPNodePoolResource(
				func(hsc *coreapi.HCPOpenShiftClusterNodePool) {
					hsc.Properties.Taints = []coreapi.Taint{
						{Effect: "a"},
						{Effect: "b"},
					}
				},
			),
			expectedCSNodePool: getBaseCSNodePoolBuilder().Taints(
				[]*arohcpv1alpha1.TaintBuilder{
					arohcpv1alpha1.NewTaint().
						Effect("a").
						Key("").
						Value(""),
					arohcpv1alpha1.NewTaint().Effect("b").
						Key("").
						Value(""),
				}...),
		},
		{
			name: "converts stable version from RP to CS (adds patch and prefix)",
			hcpNodePool: getHCPNodePoolResource(
				func(hsc *coreapi.HCPOpenShiftClusterNodePool) {
					hsc.Properties.Version = coreapi.NodePoolVersionProfile{
						ID:           "4.20",
						ChannelGroup: "stable",
					}
				},
			),
			expectedCSNodePool: getBaseCSNodePoolBuilder().
				Version(arohcpv1alpha1.NewVersion().
					ID("openshift-v4.20.25").
					ChannelGroup("stable")),
		},
		{
			name: "converts candidate version from RP to CS (adds channel suffix)",
			hcpNodePool: getHCPNodePoolResource(
				func(hsc *coreapi.HCPOpenShiftClusterNodePool) {
					hsc.Properties.Version = coreapi.NodePoolVersionProfile{
						ID:           "4.21.19",
						ChannelGroup: "candidate",
					}
				},
			),
			expectedCSNodePool: getBaseCSNodePoolBuilder().
				Version(arohcpv1alpha1.NewVersion().
					ID("openshift-v4.21.19-candidate").
					ChannelGroup("candidate")),
		},
		{
			name: "converts nightly version from RP to CS with semver",
			hcpNodePool: getHCPNodePoolResource(
				func(hsc *coreapi.HCPOpenShiftClusterNodePool) {
					hsc.Properties.Version = coreapi.NodePoolVersionProfile{
						ID:           "4.21.0-0.nightly-2025-01-01",
						ChannelGroup: "nightly",
					}
				},
			),
			expectedCSNodePool: getBaseCSNodePoolBuilder().
				Version(arohcpv1alpha1.NewVersion().
					ID("openshift-v4.21.0-0.nightly-2025-01-01-nightly").
					ChannelGroup("nightly")),
		},
		{
			name: "converts ephemeral disk type from RP to CS",
			hcpNodePool: getHCPNodePoolResource(
				func(hsc *coreapi.HCPOpenShiftClusterNodePool) {
					hsc.Properties.Platform.OSDisk.DiskType = metadataapi.OsDiskTypeEphemeral
				},
			),
			expectedCSNodePool: getBaseCSNodePoolBuilder().
				AzureNodePool(arohcpv1alpha1.NewAzureNodePool().
					ResourceName("").
					VMSize("").
					EncryptionAtHost(
						arohcpv1alpha1.NewAzureNodePoolEncryptionAtHost().
							State(csEncryptionAtHostStateDisabled),
					).
					OsDisk(arohcpv1alpha1.NewAzureNodePoolOsDisk().
						SizeGibibytes(64).
						StorageAccountType(string(metadataapi.DiskStorageAccountTypePremium_LRS)).
						Persistence("ephemeral"),
					),
				),
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			ctx := context.Background()
			expected, err := tc.expectedCSNodePool.Build()
			require.NoError(t, err)
			generatedCSNodePoolBuilder, err := BuildCSNodePool(ctx, tc.hcpNodePool, false)
			require.NoError(t, err)
			generatedCSNodePool, err := generatedCSNodePoolBuilder.Build()
			require.NoError(t, err)
			assert.Equalf(t, expected, generatedCSNodePool, "BuildCSNodePool(%v, %v)", resourceID, expected)
		})
	}
}

func externalAuthResource(opts ...func(*coreapi.HCPOpenShiftClusterExternalAuth)) *coreapi.HCPOpenShiftClusterExternalAuth {
	externalAuth := coreapi.NewDefaultHCPOpenShiftClusterExternalAuth(nil)

	for _, opt := range opts {
		opt(externalAuth)
	}
	return externalAuth
}

// Because we don't distinguish between unset and empty values in our JSON parsing
// we will get the resulting CS object from an empty HCPOpenShiftClusterExternalAuth object.
func getBaseCSExternalAuthBuilder() *arohcpv1alpha1.ExternalAuthBuilder {
	return arohcpv1alpha1.NewExternalAuth().
		ID("").
		Issuer(arohcpv1alpha1.NewTokenIssuer().
			Audiences().
			URL("").
			CA("")).
		Claim(arohcpv1alpha1.NewExternalAuthClaim().
			Mappings(arohcpv1alpha1.NewTokenClaimMappings().
				UserName(arohcpv1alpha1.NewUsernameClaim().
					Claim("").
					Prefix("").
					PrefixPolicy(""),
				),
			).
			ValidationRules(),
		).
		Clients()
}

func TestBuildCSExternalAuth(t *testing.T) {
	resourceID := testResourceID(t)
	testCases := []struct {
		name                   string
		hcpExternalAuth        *coreapi.HCPOpenShiftClusterExternalAuth
		expectedCSExternalAuth *arohcpv1alpha1.ExternalAuthBuilder
	}{
		{
			name:                   "zero",
			hcpExternalAuth:        externalAuthResource(),
			expectedCSExternalAuth: getBaseCSExternalAuthBuilder(),
		},
		{
			name: "correctly parse PrefixPolicy",
			hcpExternalAuth: externalAuthResource(
				func(hsc *coreapi.HCPOpenShiftClusterExternalAuth) {
					hsc.Properties.Claim.Mappings.Username.PrefixPolicy = metadataapi.UsernameClaimPrefixPolicyPrefix
				},
			),
			expectedCSExternalAuth: getBaseCSExternalAuthBuilder().Claim(arohcpv1alpha1.NewExternalAuthClaim().
				Mappings(arohcpv1alpha1.NewTokenClaimMappings().
					UserName(arohcpv1alpha1.NewUsernameClaim().
						Claim("").
						Prefix("").
						PrefixPolicy(string(metadataapi.UsernameClaimPrefixPolicyPrefix)),
					),
				).
				ValidationRules(),
			),
		},
		{
			name: "correctly parse Issuer",
			hcpExternalAuth: externalAuthResource(
				func(hsc *coreapi.HCPOpenShiftClusterExternalAuth) {
					hsc.Properties.Issuer = coreapi.TokenIssuerProfile{
						CA:        dummyCA,
						URL:       dummyURL,
						Audiences: dummyAudiences,
					}
				},
			),
			expectedCSExternalAuth: getBaseCSExternalAuthBuilder().Issuer(
				arohcpv1alpha1.NewTokenIssuer().
					CA(dummyCA).
					URL(dummyURL).
					Audiences(dummyAudiences...),
			),
		},
		{
			name: "correctly parse Claim",
			hcpExternalAuth: externalAuthResource(
				func(hsc *coreapi.HCPOpenShiftClusterExternalAuth) {
					hsc.Properties.Claim = coreapi.ExternalAuthClaimProfile{
						Mappings: coreapi.TokenClaimMappingsProfile{
							Username: coreapi.UsernameClaimProfile{
								Claim:        "a",
								Prefix:       "",
								PrefixPolicy: "None",
							},
							Groups: &coreapi.GroupClaimProfile{
								Claim:  "b",
								Prefix: "",
							},
						},
						ValidationRules: []coreapi.TokenClaimValidationRule{
							{
								Type: metadataapi.TokenValidationRuleTypeRequiredClaim,
								RequiredClaim: coreapi.TokenRequiredClaim{
									Claim:         "A",
									RequiredValue: "B",
								},
							},
							{
								Type: metadataapi.TokenValidationRuleTypeRequiredClaim,
								RequiredClaim: coreapi.TokenRequiredClaim{
									Claim:         "C",
									RequiredValue: "D",
								},
							},
						},
					}
				},
			),
			expectedCSExternalAuth: getBaseCSExternalAuthBuilder().Claim(
				arohcpv1alpha1.NewExternalAuthClaim().
					Mappings(arohcpv1alpha1.NewTokenClaimMappings().
						UserName(arohcpv1alpha1.NewUsernameClaim().
							Claim("a").
							Prefix("").
							PrefixPolicy(""),
						).
						Groups(arohcpv1alpha1.NewGroupsClaim().
							Claim("b").
							Prefix(""),
						),
					).
					ValidationRules([]*arohcpv1alpha1.TokenClaimValidationRuleBuilder{
						arohcpv1alpha1.NewTokenClaimValidationRule().
							Claim("A").
							RequiredValue("B"),
						arohcpv1alpha1.NewTokenClaimValidationRule().
							Claim("C").
							RequiredValue("D"),
					}...),
			),
		},
		{
			name: "handle multiple clients",
			hcpExternalAuth: externalAuthResource(
				func(hsc *coreapi.HCPOpenShiftClusterExternalAuth) {
					hsc.Properties.Clients = []coreapi.ExternalAuthClientProfile{
						{
							ClientID: "a",
							Type:     metadataapi.ExternalAuthClientTypeConfidential,
						},
						{
							ClientID: "b",
							Type:     metadataapi.ExternalAuthClientTypeConfidential,
						},
					}
				},
			),
			expectedCSExternalAuth: getBaseCSExternalAuthBuilder().Clients(
				[]*arohcpv1alpha1.ExternalAuthClientConfigBuilder{
					arohcpv1alpha1.NewExternalAuthClientConfig().
						ID("a").
						Component(arohcpv1alpha1.NewClientComponent().
							Name("").
							Namespace(""),
						).
						ExtraScopes().
						Type(arohcpv1alpha1.ExternalAuthClientTypeConfidential),
					arohcpv1alpha1.NewExternalAuthClientConfig().
						ID("b").
						Component(arohcpv1alpha1.NewClientComponent().
							Name("").
							Namespace(""),
						).
						ExtraScopes().
						Type(arohcpv1alpha1.ExternalAuthClientTypeConfidential),
				}...),
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			ctx := context.Background()
			expected, err := tc.expectedCSExternalAuth.Build()
			require.NoError(t, err)
			generatedCSExternalAuthBuilder, err := BuildCSExternalAuth(ctx, tc.hcpExternalAuth, false)
			require.NoError(t, err)
			generatedCSExternalAuth, err := generatedCSExternalAuthBuilder.Build()
			require.NoError(t, err)
			assert.Equalf(t, expected, generatedCSExternalAuth, "BuildCSExternalAuth(%v, %v)", resourceID, expected)
		})
	}
}

// defaultTestKMSUpdateAzureBuilder returns the Azure builder that applyToCSBuilders
// produces for an UPDATE when the test cluster has default KMS etcd encryption
// (from MinimumValidClusterTestCase). Only the key version is dispatch-managed on update.
func defaultTestKMSUpdateAzureBuilder() *arohcpv1alpha1.AzureBuilder {
	return arohcpv1alpha1.NewAzure().
		EtcdEncryption(arohcpv1alpha1.NewAzureEtcdEncryption().
			DataEncryption(arohcpv1alpha1.NewAzureEtcdDataEncryption().
				CustomerManaged(arohcpv1alpha1.NewAzureEtcdDataEncryptionCustomerManaged().
					Kms(arohcpv1alpha1.NewAzureKmsEncryption().
						ActiveKey(arohcpv1alpha1.NewAzureKmsKey().
							KeyVersion("test-version"))))))
}

func getBaseCSClusterBuilder(updating bool) *arohcpv1alpha1.ClusterBuilder {
	var builder *arohcpv1alpha1.ClusterBuilder
	clusterAPIBuilder := arohcpv1alpha1.NewClusterAPI()

	if updating {
		builder = arohcpv1alpha1.NewCluster().
			Azure(defaultTestKMSUpdateAzureBuilder())
	} else {
		builder = ocmClusterDefaults(coreapitesting.TestLocation)
		clusterAPIBuilder = clusterAPIBuilder.Listening(arohcpv1alpha1.ListeningMethodExternal)
		builder.Ingresses(arohcpv1alpha1.NewIngressList().Items(
			arohcpv1alpha1.NewIngress().Default(true).Listening(arohcpv1alpha1.ListeningMethodExternal),
		))
		builder.Azure(arohcpv1alpha1.NewAzure().
			EtcdEncryption(arohcpv1alpha1.NewAzureEtcdEncryption().
				DataEncryption(arohcpv1alpha1.NewAzureEtcdDataEncryption().
					KeyManagementMode(csKeyManagementModeCustomerManaged).
					CustomerManaged(arohcpv1alpha1.NewAzureEtcdDataEncryptionCustomerManaged().
						EncryptionType("kms").
						Kms(arohcpv1alpha1.NewAzureKmsEncryption().
							Visibility(arohcpv1alpha1.AzureKmsEncryptionVisibilityPublic).
							ActiveKey(arohcpv1alpha1.NewAzureKmsKey().
								KeyName("test-key").
								KeyVaultName("test-vault").
								KeyVersion("test-version"),
							),
						),
					))).
			ManagedResourceGroupName(coreapitesting.TestManagedResourceGroupName).
			NetworkSecurityGroupResourceID(coreapitesting.TestNetworkSecurityGroupResourceID).
			NodesOutboundConnectivity(arohcpv1alpha1.NewAzureNodesOutboundConnectivity().
				OutboundType(csOutboundType)).
			OperatorsAuthentication(arohcpv1alpha1.NewAzureOperatorsAuthentication().
				ManagedIdentities(arohcpv1alpha1.NewAzureOperatorsAuthenticationManagedIdentities().
					ControlPlaneOperatorsManagedIdentities(make(map[string]*arohcpv1alpha1.AzureControlPlaneManagedIdentityBuilder)).
					DataPlaneOperatorsManagedIdentities(make(map[string]*arohcpv1alpha1.AzureDataPlaneManagedIdentityBuilder)).
					ManagedIdentitiesDataPlaneIdentityUrl(coreapitesting.TestManagedIdentitiesDataPlaneIdentityURL))).
			ResourceGroupName(strings.ToLower(coreapitesting.TestResourceGroupName)).
			ResourceName(strings.ToLower(coreapitesting.TestClusterName)).
			SubnetResourceID(coreapitesting.TestSubnetResourceID).
			VnetIntegrationSubnetResourceID(coreapitesting.TestVnetIntegrationSubnetResourceID).
			SubscriptionID(strings.ToLower(coreapitesting.TestSubscriptionID)).
			TenantID(coreapitesting.TestTenantID))
	}

	// Add common mutable fields that BuildCSCluster always sets
	builder = builder.
		NodeDrainGracePeriod(arohcpv1alpha1.NewValue().
			Unit(csNodeDrainGracePeriodUnit).
			Value(float64(0))).
		Autoscaler(arohcpv1alpha1.NewClusterAutoscaler().
			PodPriorityThreshold(-10).
			MaxNodeProvisionTime("15m").
			MaxPodGracePeriod(600).
			ResourceLimits(arohcpv1alpha1.NewAutoscalerResourceLimits().
				MaxNodesTotal(0))).
		Properties(map[string]string{}).
		API(clusterAPIBuilder.CIDRBlockAccess(arohcpv1alpha1.NewCIDRBlockAccess().
			Allow(arohcpv1alpha1.NewCIDRBlockAllowAccess().
				Mode(CSCIDRBlockAllowAccessModeAllowAll)))).
		RegistryConfig(arohcpv1alpha1.NewClusterRegistryConfig().ImageDigestMirrors())

	return builder
}

func TestBuildCSCluster(t *testing.T) {
	testCases := []struct {
		name                     string
		hcpCluster               *coreapi.HCPOpenShiftCluster
		requiredProperties       map[string]string
		oldClusterServiceCluster *arohcpv1alpha1.Cluster
		expectedCSCluster        *arohcpv1alpha1.ClusterBuilder
		expectedError            string
	}{
		{
			name: "CREATE - sets CIDRBlockAccess with nil AuthorizedCIDRs",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				CustomerProperties: coreapi.HCPOpenShiftClusterCustomerProperties{
					API: coreapi.CustomerAPIProfile{
						AuthorizedCIDRs: nil,
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(false),
		},
		{
			name: "CREATE - rejects empty AuthorizedCIDRs",
			hcpCluster: func() *coreapi.HCPOpenShiftCluster {
				cluster := coreapitesting.MinimumValidClusterTestCase()
				cluster.CustomerProperties.API.AuthorizedCIDRs = make([]string, 0)
				return cluster
			}(),
			expectedError: "AuthorizedCIDRs cannot be an empty list",
		},
		{
			name: "CREATE - sets CIDRBlockAccess with non-empty AuthorizedCIDRs",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				CustomerProperties: coreapi.HCPOpenShiftClusterCustomerProperties{
					API: coreapi.CustomerAPIProfile{
						Visibility:      metadataapi.VisibilityPrivate,
						AuthorizedCIDRs: []string{"10.0.0.0/8", "192.168.0.0/16"},
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(false).
				API(arohcpv1alpha1.NewClusterAPI().
					Listening(arohcpv1alpha1.ListeningMethodInternal).
					CIDRBlockAccess(arohcpv1alpha1.NewCIDRBlockAccess().
						Allow(arohcpv1alpha1.NewCIDRBlockAllowAccess().
							Mode(CSCIDRBlockAllowAccessModeAllowList).
							Values("10.0.0.0/8", "192.168.0.0/16")))),
		},
		{
			name: "CREATE - sets private ingress type",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				CustomerProperties: coreapi.HCPOpenShiftClusterCustomerProperties{
					Ingress: coreapi.CustomerIngressProfile{
						Type: metadataapi.IngressTypePrivate,
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(false).
				Ingresses(arohcpv1alpha1.NewIngressList().Items(
					arohcpv1alpha1.NewIngress().Default(true).Listening(arohcpv1alpha1.ListeningMethodInternal),
				)),
		},
		{
			name: "UPDATE - sets CIDRBlockAccess with nil AuthorizedCIDRs",
			oldClusterServiceCluster: func() *arohcpv1alpha1.Cluster {
				c, err := arohcpv1alpha1.NewCluster().Build()
				if err != nil {
					panic(err)
				}
				return c
			}(),
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				CustomerProperties: coreapi.HCPOpenShiftClusterCustomerProperties{
					API: coreapi.CustomerAPIProfile{
						AuthorizedCIDRs: nil,
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(true),
		},
		{
			name: "UPDATE - rejects empty AuthorizedCIDRs",
			oldClusterServiceCluster: func() *arohcpv1alpha1.Cluster {
				c, err := arohcpv1alpha1.NewCluster().Build()
				if err != nil {
					panic(err)
				}
				return c
			}(),
			hcpCluster: func() *coreapi.HCPOpenShiftCluster {
				cluster := coreapitesting.MinimumValidClusterTestCase()
				cluster.CustomerProperties.API.AuthorizedCIDRs = make([]string, 0)
				return cluster
			}(),
			expectedError: "AuthorizedCIDRs cannot be an empty list",
		},
		{
			name: "UPDATE - sets only CIDRBlockAccess with non-empty AuthorizedCIDRs",
			oldClusterServiceCluster: func() *arohcpv1alpha1.Cluster {
				c, err := arohcpv1alpha1.NewCluster().Build()
				if err != nil {
					panic(err)
				}
				return c
			}(),
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				CustomerProperties: coreapi.HCPOpenShiftClusterCustomerProperties{
					API: coreapi.CustomerAPIProfile{
						AuthorizedCIDRs: []string{"172.16.0.0/12", "203.0.113.0/24"},
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(true).
				API(arohcpv1alpha1.NewClusterAPI().
					CIDRBlockAccess(arohcpv1alpha1.NewCIDRBlockAccess().
						Allow(arohcpv1alpha1.NewCIDRBlockAllowAccess().
							Mode(CSCIDRBlockAllowAccessModeAllowList).
							Values("172.16.0.0/12", "203.0.113.0/24")))),
		},
		{
			name: "CREATE - sets experimental feature properties to true",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				ServiceProviderProperties: coreapi.HCPOpenShiftClusterServiceProviderProperties{
					ExperimentalFeatures: coreapi.ExperimentalFeatures{
						ControlPlaneAvailability: coreapi.SingleReplicaControlPlane,
						ControlPlanePodSizing:    coreapi.MinimalControlPlanePodSizing,
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(false).
				Properties(map[string]string{
					"hosted_cluster_single_replica": "true",
					"hosted_cluster_size_override":  CSPropertyE2EMinimalControlPlaneSize,
				}),
		},
		{
			name: "CREATE - sets only single-replica",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				ServiceProviderProperties: coreapi.HCPOpenShiftClusterServiceProviderProperties{
					ExperimentalFeatures: coreapi.ExperimentalFeatures{
						ControlPlaneAvailability: coreapi.SingleReplicaControlPlane,
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(false).
				Properties(map[string]string{
					"hosted_cluster_single_replica": "true",
				}),
		},
		{
			name: "UPDATE - tag removal clears previously set properties",
			oldClusterServiceCluster: func() *arohcpv1alpha1.Cluster {
				c, err := arohcpv1alpha1.NewCluster().Properties(map[string]string{
					"hosted_cluster_single_replica": "true",
					"hosted_cluster_size_override":  "true",
				}).Build()
				if err != nil {
					panic(err)
				}
				return c
			}(),
			hcpCluster: &coreapi.HCPOpenShiftCluster{},
			expectedCSCluster: getBaseCSClusterBuilder(true).
				Properties(map[string]string{}),
		},
		{
			name: "UPDATE - partial feature disablement keeps remaining feature",
			oldClusterServiceCluster: func() *arohcpv1alpha1.Cluster {
				c, err := arohcpv1alpha1.NewCluster().Properties(map[string]string{
					"hosted_cluster_single_replica": "true",
					"hosted_cluster_size_override":  "true",
				}).Build()
				if err != nil {
					panic(err)
				}
				return c
			}(),
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				ServiceProviderProperties: coreapi.HCPOpenShiftClusterServiceProviderProperties{
					ExperimentalFeatures: coreapi.ExperimentalFeatures{
						ControlPlanePodSizing: coreapi.MinimalControlPlanePodSizing,
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(true).
				Properties(map[string]string{
					"hosted_cluster_size_override": CSPropertyE2EMinimalControlPlaneSize,
				}),
		},
		{
			name: "UPDATE - preserves non-experimental old properties",
			oldClusterServiceCluster: func() *arohcpv1alpha1.Cluster {
				c, err := arohcpv1alpha1.NewCluster().Properties(map[string]string{
					"provisioner_noop_provision":    "true",
					"provisioner_noop_deprovision":  "true",
					"hosted_cluster_single_replica": "true",
				}).Build()
				if err != nil {
					panic(err)
				}
				return c
			}(),
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				ServiceProviderProperties: coreapi.HCPOpenShiftClusterServiceProviderProperties{
					ExperimentalFeatures: coreapi.ExperimentalFeatures{
						ControlPlaneAvailability: coreapi.SingleReplicaControlPlane,
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(true).
				Properties(map[string]string{
					"provisioner_noop_provision":    "true",
					"provisioner_noop_deprovision":  "true",
					"hosted_cluster_single_replica": "true",
				}),
		},
		{
			name: "CREATE - required properties merged with experimental features",
			requiredProperties: map[string]string{
				"provision_shard_id":           "test-shard",
				"provisioner_noop_provision":   "true",
				"provisioner_noop_deprovision": "true",
			},
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				ServiceProviderProperties: coreapi.HCPOpenShiftClusterServiceProviderProperties{
					ExperimentalFeatures: coreapi.ExperimentalFeatures{
						ControlPlaneAvailability: coreapi.SingleReplicaControlPlane,
						ControlPlanePodSizing:    coreapi.MinimalControlPlanePodSizing,
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(false).
				Properties(map[string]string{
					"provision_shard_id":            "test-shard",
					"provisioner_noop_provision":    "true",
					"provisioner_noop_deprovision":  "true",
					"hosted_cluster_single_replica": "true",
					"hosted_cluster_size_override":  CSPropertyE2EMinimalControlPlaneSize,
				}),
		},
		{
			name: "CREATE - experimental features override conflicting required properties",
			requiredProperties: map[string]string{
				"hosted_cluster_single_replica": "false",
				"hosted_cluster_size_override":  "false",
			},
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				ServiceProviderProperties: coreapi.HCPOpenShiftClusterServiceProviderProperties{
					ExperimentalFeatures: coreapi.ExperimentalFeatures{
						ControlPlaneAvailability: coreapi.SingleReplicaControlPlane,
						ControlPlanePodSizing:    coreapi.MinimalControlPlanePodSizing,
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(false).
				Properties(map[string]string{
					"hosted_cluster_single_replica": "true",
					"hosted_cluster_size_override":  CSPropertyE2EMinimalControlPlaneSize,
				}),
		},
		{
			name: "CREATE - sets CPO image override",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				ServiceProviderProperties: coreapi.HCPOpenShiftClusterServiceProviderProperties{
					ExperimentalFeatures: coreapi.ExperimentalFeatures{
						ControlPlaneOperatorImage: "quay.io/openshift/cpo:test",
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(false).
				Properties(map[string]string{
					"control_plane_operator_image": "quay.io/openshift/cpo:test",
				}),
		},
		{
			name: "CREATE - sets CPO image override with other experimental features",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				ServiceProviderProperties: coreapi.HCPOpenShiftClusterServiceProviderProperties{
					ExperimentalFeatures: coreapi.ExperimentalFeatures{
						ControlPlaneAvailability:  coreapi.SingleReplicaControlPlane,
						ControlPlaneOperatorImage: "quay.io/openshift/cpo:test",
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(false).
				Properties(map[string]string{
					"hosted_cluster_single_replica": "true",
					"control_plane_operator_image":  "quay.io/openshift/cpo:test",
				}),
		},
		{
			name: "UPDATE - tag removal clears CPO image override",
			oldClusterServiceCluster: func() *arohcpv1alpha1.Cluster {
				c, err := arohcpv1alpha1.NewCluster().Properties(map[string]string{
					"control_plane_operator_image": "quay.io/openshift/cpo:old",
				}).Build()
				if err != nil {
					panic(err)
				}
				return c
			}(),
			hcpCluster: &coreapi.HCPOpenShiftCluster{},
			expectedCSCluster: getBaseCSClusterBuilder(true).
				Properties(map[string]string{}),
		},
		{
			name: "CREATE - sets some image digest mirrors",
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				CustomerProperties: coreapi.HCPOpenShiftClusterCustomerProperties{
					ImageDigestMirrors: []coreapi.ImageDigestMirror{
						{
							Source:  "sourceRegistry1",
							Mirrors: []string{"mirrorRegistry1a", "mirrorRegistry1b"},
						},
						{
							Source:  "sourceRegistry2",
							Mirrors: []string{"mirrorRegistry2a", "mirrorRegistry2b"},
						},
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(false).
				RegistryConfig(arohcpv1alpha1.NewClusterRegistryConfig().
					ImageDigestMirrors(
						arohcpv1alpha1.NewImageMirror().
							Source("sourceRegistry1").
							Mirrors("mirrorRegistry1a", "mirrorRegistry1b"),
						arohcpv1alpha1.NewImageMirror().
							Source("sourceRegistry2").
							Mirrors("mirrorRegistry2a", "mirrorRegistry2b"),
					),
				),
		},
		{
			name:       "UPDATE - clears all image digest mirrors",
			hcpCluster: &coreapi.HCPOpenShiftCluster{},
			oldClusterServiceCluster: func() *arohcpv1alpha1.Cluster {
				c, err := getBaseCSClusterBuilder(false).
					RegistryConfig(arohcpv1alpha1.NewClusterRegistryConfig().
						ImageDigestMirrors(
							arohcpv1alpha1.NewImageMirror().
								Source("sourceRegistry1").
								Mirrors("mirrorRegistry1a", "mirrorRegistry1b"),
							arohcpv1alpha1.NewImageMirror().
								Source("sourceRegistry2").
								Mirrors("mirrorRegistry2a", "mirrorRegistry2b"),
						),
					).Build()
				if err != nil {
					panic(err)
				}
				return c
			}(),
			expectedCSCluster: getBaseCSClusterBuilder(true),
		},
		{
			name: "CREATE - converts KMS encryption with Public visibility",
			hcpCluster: func() *coreapi.HCPOpenShiftCluster {
				cluster := coreapitesting.MinimumValidClusterTestCase()
				cluster.CustomerProperties.Etcd.DataEncryption.KeyManagementMode = metadataapi.EtcdDataEncryptionKeyManagementModeTypeCustomerManaged
				cluster.CustomerProperties.Etcd.DataEncryption.CustomerManaged = &coreapi.CustomerManagedEncryptionProfile{
					EncryptionType: metadataapi.CustomerManagedEncryptionTypeKMS,
					Kms: &coreapi.KmsEncryptionProfile{
						Visibility: metadataapi.KeyVaultVisibilityPublic,
						ActiveKey: coreapi.KmsKey{
							Name:      "test-key",
							VaultName: "test-vault",
							Version:   "v1",
						},
					},
				}
				return cluster
			}(),
			expectedCSCluster: ocmClusterDefaults(coreapitesting.TestLocation).
				NodeDrainGracePeriod(arohcpv1alpha1.NewValue().
					Unit(csNodeDrainGracePeriodUnit).
					Value(float64(0))).
				Autoscaler(arohcpv1alpha1.NewClusterAutoscaler().
					PodPriorityThreshold(-10).
					MaxNodeProvisionTime("15m").
					MaxPodGracePeriod(600).
					ResourceLimits(arohcpv1alpha1.NewAutoscalerResourceLimits().
						MaxNodesTotal(0))).
				Properties(map[string]string{}).
				API(arohcpv1alpha1.NewClusterAPI().
					Listening(arohcpv1alpha1.ListeningMethodExternal).
					CIDRBlockAccess(arohcpv1alpha1.NewCIDRBlockAccess().
						Allow(arohcpv1alpha1.NewCIDRBlockAllowAccess().
							Mode(CSCIDRBlockAllowAccessModeAllowAll)))).
				Ingresses(arohcpv1alpha1.NewIngressList().Items(
					arohcpv1alpha1.NewIngress().Default(true).Listening(arohcpv1alpha1.ListeningMethodExternal),
				)).
				Azure(arohcpv1alpha1.NewAzure().
					EtcdEncryption(arohcpv1alpha1.NewAzureEtcdEncryption().
						DataEncryption(arohcpv1alpha1.NewAzureEtcdDataEncryption().
							KeyManagementMode(csKeyManagementModeCustomerManaged).
							CustomerManaged(arohcpv1alpha1.NewAzureEtcdDataEncryptionCustomerManaged().
								EncryptionType("kms").
								Kms(arohcpv1alpha1.NewAzureKmsEncryption().
									Visibility(arohcpv1alpha1.AzureKmsEncryptionVisibilityPublic).
									ActiveKey(arohcpv1alpha1.NewAzureKmsKey().
										KeyName("test-key").
										KeyVaultName("test-vault").
										KeyVersion("v1"),
									),
								),
							),
						)).
					ManagedResourceGroupName(coreapitesting.TestManagedResourceGroupName).
					NetworkSecurityGroupResourceID(coreapitesting.TestNetworkSecurityGroupResourceID).
					NodesOutboundConnectivity(arohcpv1alpha1.NewAzureNodesOutboundConnectivity().
						OutboundType(csOutboundType)).
					OperatorsAuthentication(arohcpv1alpha1.NewAzureOperatorsAuthentication().
						ManagedIdentities(arohcpv1alpha1.NewAzureOperatorsAuthenticationManagedIdentities().
							ControlPlaneOperatorsManagedIdentities(make(map[string]*arohcpv1alpha1.AzureControlPlaneManagedIdentityBuilder)).
							DataPlaneOperatorsManagedIdentities(make(map[string]*arohcpv1alpha1.AzureDataPlaneManagedIdentityBuilder)).
							ManagedIdentitiesDataPlaneIdentityUrl(coreapitesting.TestManagedIdentitiesDataPlaneIdentityURL))).
					ResourceGroupName(strings.ToLower(coreapitesting.TestResourceGroupName)).
					ResourceName(strings.ToLower(coreapitesting.TestClusterName)).
					SubnetResourceID(coreapitesting.TestSubnetResourceID).
					VnetIntegrationSubnetResourceID(coreapitesting.TestVnetIntegrationSubnetResourceID).
					SubscriptionID(strings.ToLower(coreapitesting.TestSubscriptionID)).
					TenantID(coreapitesting.TestTenantID),
				),
		},
		{
			name: "CREATE - converts KMS encryption with Private visibility",
			hcpCluster: func() *coreapi.HCPOpenShiftCluster {
				cluster := coreapitesting.MinimumValidClusterTestCase()
				cluster.CustomerProperties.Etcd.DataEncryption.KeyManagementMode = metadataapi.EtcdDataEncryptionKeyManagementModeTypeCustomerManaged
				cluster.CustomerProperties.Etcd.DataEncryption.CustomerManaged = &coreapi.CustomerManagedEncryptionProfile{
					EncryptionType: metadataapi.CustomerManagedEncryptionTypeKMS,
					Kms: &coreapi.KmsEncryptionProfile{
						Visibility: metadataapi.KeyVaultVisibilityPrivate,
						ActiveKey: coreapi.KmsKey{
							Name:      "test-key",
							VaultName: "test-vault",
							Version:   "v1",
						},
					},
				}
				return cluster
			}(),
			expectedCSCluster: ocmClusterDefaults(coreapitesting.TestLocation).
				NodeDrainGracePeriod(arohcpv1alpha1.NewValue().
					Unit(csNodeDrainGracePeriodUnit).
					Value(float64(0))).
				Autoscaler(arohcpv1alpha1.NewClusterAutoscaler().
					PodPriorityThreshold(-10).
					MaxNodeProvisionTime("15m").
					MaxPodGracePeriod(600).
					ResourceLimits(arohcpv1alpha1.NewAutoscalerResourceLimits().
						MaxNodesTotal(0))).
				Properties(map[string]string{}).
				API(arohcpv1alpha1.NewClusterAPI().
					Listening(arohcpv1alpha1.ListeningMethodExternal).
					CIDRBlockAccess(arohcpv1alpha1.NewCIDRBlockAccess().
						Allow(arohcpv1alpha1.NewCIDRBlockAllowAccess().
							Mode(CSCIDRBlockAllowAccessModeAllowAll)))).
				Ingresses(arohcpv1alpha1.NewIngressList().Items(
					arohcpv1alpha1.NewIngress().Default(true).Listening(arohcpv1alpha1.ListeningMethodExternal),
				)).
				Azure(arohcpv1alpha1.NewAzure().
					EtcdEncryption(arohcpv1alpha1.NewAzureEtcdEncryption().
						DataEncryption(arohcpv1alpha1.NewAzureEtcdDataEncryption().
							KeyManagementMode(csKeyManagementModeCustomerManaged).
							CustomerManaged(arohcpv1alpha1.NewAzureEtcdDataEncryptionCustomerManaged().
								EncryptionType("kms").
								Kms(arohcpv1alpha1.NewAzureKmsEncryption().
									Visibility(arohcpv1alpha1.AzureKmsEncryptionVisibilityPrivate).
									ActiveKey(arohcpv1alpha1.NewAzureKmsKey().
										KeyName("test-key").
										KeyVaultName("test-vault").
										KeyVersion("v1"),
									),
								),
							),
						)).
					ManagedResourceGroupName(coreapitesting.TestManagedResourceGroupName).
					NetworkSecurityGroupResourceID(coreapitesting.TestNetworkSecurityGroupResourceID).
					NodesOutboundConnectivity(arohcpv1alpha1.NewAzureNodesOutboundConnectivity().
						OutboundType(csOutboundType)).
					OperatorsAuthentication(arohcpv1alpha1.NewAzureOperatorsAuthentication().
						ManagedIdentities(arohcpv1alpha1.NewAzureOperatorsAuthenticationManagedIdentities().
							ControlPlaneOperatorsManagedIdentities(make(map[string]*arohcpv1alpha1.AzureControlPlaneManagedIdentityBuilder)).
							DataPlaneOperatorsManagedIdentities(make(map[string]*arohcpv1alpha1.AzureDataPlaneManagedIdentityBuilder)).
							ManagedIdentitiesDataPlaneIdentityUrl(coreapitesting.TestManagedIdentitiesDataPlaneIdentityURL))).
					ResourceGroupName(strings.ToLower(coreapitesting.TestResourceGroupName)).
					ResourceName(strings.ToLower(coreapitesting.TestClusterName)).
					SubnetResourceID(coreapitesting.TestSubnetResourceID).
					VnetIntegrationSubnetResourceID(coreapitesting.TestVnetIntegrationSubnetResourceID).
					SubscriptionID(strings.ToLower(coreapitesting.TestSubscriptionID)).
					TenantID(coreapitesting.TestTenantID),
				),
		},
		{
			name: "UPDATE - sets new KMS key version",
			oldClusterServiceCluster: func() *arohcpv1alpha1.Cluster {
				c, err := arohcpv1alpha1.NewCluster().Build()
				if err != nil {
					panic(err)
				}
				return c
			}(),
			hcpCluster: &coreapi.HCPOpenShiftCluster{
				CustomerProperties: coreapi.HCPOpenShiftClusterCustomerProperties{
					Etcd: coreapi.EtcdProfile{
						DataEncryption: coreapi.EtcdDataEncryptionProfile{
							KeyManagementMode: metadataapi.EtcdDataEncryptionKeyManagementModeTypeCustomerManaged,
							CustomerManaged: &coreapi.CustomerManagedEncryptionProfile{
								EncryptionType: metadataapi.CustomerManagedEncryptionTypeKMS,
								Kms: &coreapi.KmsEncryptionProfile{
									Visibility: metadataapi.KeyVaultVisibilityPublic,
									ActiveKey: coreapi.KmsKey{
										Name:      "test-key",
										VaultName: "test-vault",
										Version:   "v2",
									},
								},
							},
						},
					},
				},
			},
			expectedCSCluster: getBaseCSClusterBuilder(true).
				Azure(arohcpv1alpha1.NewAzure().
					EtcdEncryption(arohcpv1alpha1.NewAzureEtcdEncryption().
						DataEncryption(arohcpv1alpha1.NewAzureEtcdDataEncryption().
							CustomerManaged(arohcpv1alpha1.NewAzureEtcdDataEncryptionCustomerManaged().
								Kms(arohcpv1alpha1.NewAzureKmsEncryption().
									ActiveKey(arohcpv1alpha1.NewAzureKmsKey().
										KeyVersion("v2"),
									),
								),
							),
						))),
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Create a complete minimal cluster for testing
			// For error test cases with expected errors, use the cluster as-is to preserve empty slices
			var hcpCluster *coreapi.HCPOpenShiftCluster
			if tc.expectedError != "" {
				hcpCluster = tc.hcpCluster
			} else {
				hcpCluster = coreapitesting.ClusterTestCase(t, tc.hcpCluster)
			}

			hcpCluster.ServiceProviderProperties.ManagedIdentitiesDataPlaneIdentityURL = coreapitesting.TestManagedIdentitiesDataPlaneIdentityURL

			resourceID, err := azcorearm.ParseResourceID(coreapitesting.TestClusterResourceID)
			require.NoError(t, err)

			var serviceProviderCluster *coreapi.ServiceProviderCluster
			if tc.oldClusterServiceCluster == nil {
				serviceProviderCluster = spcWithDesiredVersion("4.20.25")
			}

			// Build actual CS cluster
			actualClusterBuilder, err := BuildCSCluster(resourceID, coreapitesting.TestTenantID, hcpCluster, tc.requiredProperties, tc.oldClusterServiceCluster, serviceProviderCluster)

			if tc.expectedError != "" {
				require.Error(t, err)
				assert.Contains(t, err.Error(), tc.expectedError)
				return
			}

			require.NoError(t, err)

			// Build expected CS cluster
			expected, err := tc.expectedCSCluster.Build()
			require.NoError(t, err)

			actual, err := actualClusterBuilder.Build()
			require.NoError(t, err)

			// Compare
			assert.Equal(t, expected, actual)
		})
	}
}

func TestClusterCSVersionID(t *testing.T) {
	hcpCluster := func(versionID string) *coreapi.HCPOpenShiftCluster {
		c := &coreapi.HCPOpenShiftCluster{}
		c.CustomerProperties.Version.ID = versionID
		c.CustomerProperties.Version.ChannelGroup = "stable"
		return c
	}
	spcWithDesired := func(v string) *coreapi.ServiceProviderCluster {
		return &coreapi.ServiceProviderCluster{
			Spec: coreapi.ServiceProviderClusterSpec{
				ControlPlaneVersion: coreapi.ServiceProviderClusterSpecVersion{
					DesiredVersion: ptr.To(semver.MustParse(v)),
				},
			},
		}
	}

	tests := []struct {
		name       string
		spc        *coreapi.ServiceProviderCluster
		hcpCluster *coreapi.HCPOpenShiftCluster
		want       string
		wantError  string
	}{
		{
			name:       "error when SPC is nil",
			spc:        nil,
			hcpCluster: hcpCluster("4.20"),
			wantError:  "control plane desired version is not set on the ServiceProviderCluster",
		},
		{
			name:       "error when SPC has no DesiredVersion",
			spc:        &coreapi.ServiceProviderCluster{},
			hcpCluster: hcpCluster("4.20"),
			wantError:  "control plane desired version is not set on the ServiceProviderCluster",
		},
		{
			name:       "uses SPC DesiredVersion",
			spc:        spcWithDesired("4.20.8"),
			hcpCluster: hcpCluster("4.20"),
			want:       "openshift-v4.20.8",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got, err := clusterCSVersionID(tc.spc, tc.hcpCluster)
			if tc.wantError != "" {
				require.Error(t, err)
				assert.Contains(t, err.Error(), tc.wantError)
				return
			}
			require.NoError(t, err)
			assert.Equal(t, tc.want, got)
		})
	}
}

func TestConvertHostedClusterSizeOverrideToCS(t *testing.T) {
	largeStr := "Large"

	tests := []struct {
		name                         string
		clusterControlPlanePodSizing coreapi.ControlPlanePodSizing
		spcControlPlaneSize          *string
		expectValue                  string
		expectActive                 bool
	}{
		{
			name:                         "SPC size wins over experimental feature",
			clusterControlPlanePodSizing: coreapi.MinimalControlPlanePodSizing,
			spcControlPlaneSize:          &largeStr,
			expectValue:                  "large",
			expectActive:                 true,
		},
		{
			name:                         "SPC size set, no experimental feature",
			clusterControlPlanePodSizing: "",
			spcControlPlaneSize:          &largeStr,
			expectValue:                  "large",
			expectActive:                 true,
		},
		{
			name:                         "SPC nil, experimental feature active returns legacy sentinel",
			clusterControlPlanePodSizing: coreapi.MinimalControlPlanePodSizing,
			spcControlPlaneSize:          nil,
			expectValue:                  CSPropertyE2EMinimalControlPlaneSize,
			expectActive:                 true,
		},
		{
			name:                         "neither input set returns property absent",
			clusterControlPlanePodSizing: "",
			spcControlPlaneSize:          nil,
			expectActive:                 false,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			value, active := ConvertHostedClusterSizeOverrideToCS(tc.clusterControlPlanePodSizing, tc.spcControlPlaneSize)
			assert.Equal(t, tc.expectActive, active)
			if tc.expectActive {
				assert.Equal(t, tc.expectValue, value)
			}
		})
	}
}

// validProvisionShardBuilder returns a builder pre-populated with all required fields
// for a valid management cluster conversion. Tests can override individual fields.
func validProvisionShardBuilder(t *testing.T) *arohcpv1alpha1.ProvisionShardBuilder {
	t.Helper()
	return arohcpv1alpha1.NewProvisionShard().
		ID("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee").
		HREF("/api/aro_hcp/v1alpha1/provision_shards/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee").
		Status(CSProvisionShardStatusActive).
		Topology(CSProvisionShardTopologyShared).
		AzureShard(arohcpv1alpha1.NewAzureShard().
			AksManagementClusterResourceId("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.ContainerService/managedClusters/test-westus3-mgmt-1").
			PublicDnsZoneResourceId("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/dns-rg/providers/Microsoft.Network/dnszones/test.example.com").
			CxSecretsKeyVaultUrl("https://cx-kv.vault.azure.net/").
			CxManagedIdentitiesKeyVaultUrl("https://mi-kv.vault.azure.net/").
			CxSecretsKeyVaultManagedIdentityClientId("c2bde1aa-d904-48cd-a728-9de33e3ddca9"),
		).
		MaestroConfig(
			arohcpv1alpha1.NewProvisionShardMaestroConfig().
				ConsumerName("test-consumer").
				RestApiConfig(arohcpv1alpha1.NewProvisionShardMaestroRestApiConfig().
					Url("http://maestro.maestro.svc.cluster.local:8000")).
				GrpcApiConfig(arohcpv1alpha1.NewProvisionShardMaestroGrpcApiConfig().
					Url("maestro-grpc.maestro.svc.cluster.local:8090")),
		)
}

func TestConvertCSManagementClusterToInternal(t *testing.T) {
	tests := []struct {
		name                string
		build               func(t *testing.T) *arohcpv1alpha1.ProvisionShard
		expectedErrorSubstr string
		validate            func(t *testing.T, mc *fleetapi.ManagementCluster)
	}{
		{
			name: "nil shard",
			build: func(t *testing.T) *arohcpv1alpha1.ProvisionShard {
				return nil
			},
			expectedErrorSubstr: "provision shard is nil",
		},
		{
			name: "empty shard HREF",
			build: func(t *testing.T) *arohcpv1alpha1.ProvisionShard {
				shard, err := arohcpv1alpha1.NewProvisionShard().Build()
				require.NoError(t, err)
				return shard
			},
			expectedErrorSubstr: "provision shard has empty HREF",
		},
		{
			name: "invalid AKS resource ID",
			build: func(t *testing.T) *arohcpv1alpha1.ProvisionShard {
				shard, err := arohcpv1alpha1.NewProvisionShard().
					ID("11111111-2222-3333-4444-555555555555").
					HREF("/api/aro_hcp/v1alpha1/provision_shards/11111111-2222-3333-4444-555555555555").
					AzureShard(arohcpv1alpha1.NewAzureShard().
						AksManagementClusterResourceId("not-a-valid-resource-id")).
					Build()
				require.NoError(t, err)
				return shard
			},
			expectedErrorSubstr: "failed to parse management cluster AKS resource ID",
		},
		{
			name: "invalid public DNS zone resource ID",
			build: func(t *testing.T) *arohcpv1alpha1.ProvisionShard {
				shard, err := arohcpv1alpha1.NewProvisionShard().
					ID("11111111-2222-3333-4444-555555555555").
					HREF("/api/aro_hcp/v1alpha1/provision_shards/11111111-2222-3333-4444-555555555555").
					AzureShard(arohcpv1alpha1.NewAzureShard().
						AksManagementClusterResourceId("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.ContainerService/managedClusters/test-westus3-mgmt-1").
						PublicDnsZoneResourceId("not-valid")).
					Build()
				require.NoError(t, err)
				return shard
			},
			expectedErrorSubstr: "failed to parse public DNS zone resource ID",
		},
		{
			name: "missing maestro config",
			build: func(t *testing.T) *arohcpv1alpha1.ProvisionShard {
				shard, err := arohcpv1alpha1.NewProvisionShard().
					ID("11111111-2222-3333-4444-555555555555").
					HREF("/api/aro_hcp/v1alpha1/provision_shards/11111111-2222-3333-4444-555555555555").
					AzureShard(arohcpv1alpha1.NewAzureShard().
						AksManagementClusterResourceId("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.ContainerService/managedClusters/test-westus3-mgmt-1").
						PublicDnsZoneResourceId("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/dns-rg/providers/Microsoft.Network/dnszones/test.example.com").
						CxSecretsKeyVaultUrl("https://cx-kv.vault.azure.net/").
						CxManagedIdentitiesKeyVaultUrl("https://mi-kv.vault.azure.net/").
						CxSecretsKeyVaultManagedIdentityClientId("c2bde1aa-d904-48cd-a728-9de33e3ddca9"),
					).
					Build()
				require.NoError(t, err)
				return shard
			},
			expectedErrorSubstr: "no maestro config",
		},
		{
			name: "successful conversion populates all fields",
			build: func(t *testing.T) *arohcpv1alpha1.ProvisionShard {
				shard, err := validProvisionShardBuilder(t).Build()
				require.NoError(t, err)
				return shard
			},
			validate: func(t *testing.T, mc *fleetapi.ManagementCluster) {
				// ResourceID
				expectedResourceID := metadataapi.Must(fleetapi.ToManagementClusterResourceID("1"))
				require.NotNil(t, mc.ResourceID)
				assert.Equal(t, expectedResourceID.String(), mc.ResourceID.String())
				assert.Equal(t, mc.ResourceID, mc.CosmosMetadata.ResourceID)

				assert.Equal(t, "1", mc.GetStampIdentifier(), "stamp identifier should be suffix after last '-' in AKS cluster name")
				assert.Equal(t, fleetapi.ManagementClusterSchedulingPolicySchedulable, mc.Spec.SchedulingPolicy, "active shard should be schedulable")

				// Status
				require.NotNil(t, mc.Status.AKSResourceID)
				assert.Equal(t, "test-westus3-mgmt-1", mc.Status.AKSResourceID.Name)
				require.NotNil(t, mc.Status.PublicDNSZoneResourceID)
				assert.Equal(t, "https://cx-kv.vault.azure.net/", mc.Status.HostedClustersSecretsKeyVaultURL)
				assert.Equal(t, "https://mi-kv.vault.azure.net/", mc.Status.HostedClustersManagedIdentitiesKeyVaultURL)
				assert.Equal(t, "c2bde1aa-d904-48cd-a728-9de33e3ddca9", mc.Status.HostedClustersSecretsKeyVaultManagedIdentityClientID)
				require.NotNil(t, mc.Status.ClusterServiceProvisionShardID)
				assert.Equal(t, metadataapi.Must(metadataapi.NewInternalID("/api/aro_hcp/v1alpha1/provision_shards/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")), *mc.Status.ClusterServiceProvisionShardID)

				// Kube-applier
				assert.Equal(t, "Manifests-MC-1", mc.Status.KubeApplierCosmosContainerName)

				// Maestro config
				assert.Equal(t, "test-consumer", mc.Status.MaestroConsumerName)
				assert.Equal(t, "http://maestro.maestro.svc.cluster.local:8000", mc.Status.MaestroRESTAPIURL)
				assert.Equal(t, "maestro-grpc.maestro.svc.cluster.local:8090", mc.Status.MaestroGRPCTarget)
			},
		},
		{
			name: "maintenance shard is unschedulable",
			build: func(t *testing.T) *arohcpv1alpha1.ProvisionShard {
				shard, err := validProvisionShardBuilder(t).Status("maintenance").Build()
				require.NoError(t, err)
				return shard
			},
			validate: func(t *testing.T, mc *fleetapi.ManagementCluster) {
				assert.Equal(t, fleetapi.ManagementClusterSchedulingPolicyUnschedulable, mc.Spec.SchedulingPolicy, "maintenance shard should be unschedulable")
				require.Len(t, mc.Status.Conditions, 1)
				assert.Equal(t, string(fleetapi.ManagementClusterConditionReady), mc.Status.Conditions[0].Type)
				assert.Equal(t, metav1.ConditionFalse, mc.Status.Conditions[0].Status)
				assert.Equal(t, string(fleetapi.ManagementClusterConditionReasonProvisionShardMaintenance), mc.Status.Conditions[0].Reason)
				assert.Contains(t, mc.Status.Conditions[0].Message, "maintenance")
			},
		},
		{
			name: "offline shard is unschedulable",
			build: func(t *testing.T) *arohcpv1alpha1.ProvisionShard {
				shard, err := validProvisionShardBuilder(t).Status("offline").Build()
				require.NoError(t, err)
				return shard
			},
			validate: func(t *testing.T, mc *fleetapi.ManagementCluster) {
				assert.Equal(t, fleetapi.ManagementClusterSchedulingPolicyUnschedulable, mc.Spec.SchedulingPolicy, "offline shard should be unschedulable")
				require.Len(t, mc.Status.Conditions, 1)
				assert.Equal(t, string(fleetapi.ManagementClusterConditionReady), mc.Status.Conditions[0].Type)
				assert.Equal(t, metav1.ConditionFalse, mc.Status.Conditions[0].Status)
				assert.Equal(t, string(fleetapi.ManagementClusterConditionReasonProvisionShardOffline), mc.Status.Conditions[0].Reason)
				assert.Contains(t, mc.Status.Conditions[0].Message, "offline")
			},
		},
		{
			name: "unknown shard status produces ConditionUnknown",
			build: func(t *testing.T) *arohcpv1alpha1.ProvisionShard {
				shard, err := validProvisionShardBuilder(t).Status("some-new-status").Build()
				require.NoError(t, err)
				return shard
			},
			validate: func(t *testing.T, mc *fleetapi.ManagementCluster) {
				assert.Equal(t, fleetapi.ManagementClusterSchedulingPolicyUnschedulable, mc.Spec.SchedulingPolicy, "unknown status shard should be unschedulable")
				require.Len(t, mc.Status.Conditions, 1)
				assert.Equal(t, string(fleetapi.ManagementClusterConditionReady), mc.Status.Conditions[0].Type)
				assert.Equal(t, metav1.ConditionUnknown, mc.Status.Conditions[0].Status)
				assert.Equal(t, string(fleetapi.ManagementClusterConditionReasonProvisionShardStatusUnknown), mc.Status.Conditions[0].Reason)
				assert.Contains(t, mc.Status.Conditions[0].Message, "some-new-status")
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			shard := tt.build(t)
			mc, err := ConvertCSManagementClusterToInternal(shard)
			if len(tt.expectedErrorSubstr) > 0 {
				require.Error(t, err)
				assert.Contains(t, err.Error(), tt.expectedErrorSubstr)
				assert.Nil(t, mc)
			} else {
				require.NoError(t, err)
				require.NotNil(t, mc)
				if tt.validate != nil {
					tt.validate(t, mc)
				}
			}
		})
	}
}

func TestCSErrorToCloudError(t *testing.T) {
	t.Parallel()

	resourceID, err := azcorearm.ParseResourceID(
		"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRG/providers/Microsoft.RedHatOpenShift/hcpOpenShiftClusters/myCluster",
	)
	require.NoError(t, err)

	tests := []struct {
		name               string
		err                error
		resourceID         *azcorearm.ResourceID
		expectedStatusCode int
		expectedCode       string
		expectedMessage    string
	}{
		{
			name: "CS 404 with shard-not-found returns 503",
			err: func() error {
				e, _ := ocmerrors.NewError().
					Status(http.StatusNotFound).
					Reason("Unable to find shard for cluster '123' in region 'westus3'").
					Build()
				return e
			}(),
			resourceID:         resourceID,
			expectedStatusCode: http.StatusServiceUnavailable,
			expectedCode:       coreapi.CloudErrorCodeServiceUnavailable,
			expectedMessage:    "Capacity is currently restricted, please try again later",
		},
		{
			name: "CS 404 with shard-not-found returns 503 even without resourceID",
			err: func() error {
				e, _ := ocmerrors.NewError().
					Status(http.StatusNotFound).
					Reason("Unable to find shard for cluster '456' in region 'eastus'").
					Build()
				return e
			}(),
			resourceID:         nil,
			expectedStatusCode: http.StatusServiceUnavailable,
			expectedCode:       coreapi.CloudErrorCodeServiceUnavailable,
			expectedMessage:    "Capacity is currently restricted, please try again later",
		},
		{
			name: "CS 404 without shard reason returns ResourceNotFound when resourceID present",
			err: func() error {
				e, _ := ocmerrors.NewError().
					Status(http.StatusNotFound).
					Reason("Cluster not found").
					Build()
				return e
			}(),
			resourceID:         resourceID,
			expectedStatusCode: http.StatusNotFound,
			expectedCode:       coreapi.CloudErrorCodeResourceNotFound,
		},
		{
			name: "CS 404 without shard reason returns NotFound when resourceID is nil",
			err: func() error {
				e, _ := ocmerrors.NewError().
					Status(http.StatusNotFound).
					Reason("Cluster not found").
					Build()
				return e
			}(),
			resourceID:         nil,
			expectedStatusCode: http.StatusNotFound,
			expectedCode:       coreapi.CloudErrorCodeNotFound,
			expectedMessage:    "Cluster not found",
		},
		{
			name: "CS 400 returns InvalidRequestContent",
			err: func() error {
				e, _ := ocmerrors.NewError().
					Status(http.StatusBadRequest).
					Reason("Validation failed").
					Build()
				return e
			}(),
			resourceID:         resourceID,
			expectedStatusCode: http.StatusBadRequest,
			expectedCode:       coreapi.CloudErrorCodeInvalidRequestContent,
			expectedMessage:    "Validation failed",
		},
		{
			name: "CS 409 returns Conflict",
			err: func() error {
				e, _ := ocmerrors.NewError().
					Status(http.StatusConflict).
					Reason("Resource already exists").
					Build()
				return e
			}(),
			resourceID:         resourceID,
			expectedStatusCode: http.StatusConflict,
			expectedCode:       coreapi.CloudErrorCodeConflict,
			expectedMessage:    "Resource already exists",
		},
		{
			name: "CS 503 returns ServiceUnavailable",
			err: func() error {
				e, _ := ocmerrors.NewError().
					Status(http.StatusServiceUnavailable).
					Reason("Cluster limit reached").
					Build()
				return e
			}(),
			resourceID:         resourceID,
			expectedStatusCode: http.StatusServiceUnavailable,
			expectedCode:       coreapi.CloudErrorCodeServiceUnavailable,
			expectedMessage:    "Cluster limit reached",
		},
		{
			name:               "non-OCM error returns InternalServerError",
			err:                errors.New("something went wrong"),
			resourceID:         resourceID,
			expectedStatusCode: http.StatusInternalServerError,
			expectedCode:       coreapi.CloudErrorCodeInternalServerError,
		},
		{
			name: "unhandled OCM status returns InternalServerError",
			err: func() error {
				e, _ := ocmerrors.NewError().
					Status(http.StatusForbidden).
					Reason("Forbidden").
					Build()
				return e
			}(),
			resourceID:         resourceID,
			expectedStatusCode: http.StatusInternalServerError,
			expectedCode:       coreapi.CloudErrorCodeInternalServerError,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			cloudErr := CSErrorToCloudError(tt.err, tt.resourceID)

			require.NotNil(t, cloudErr)
			assert.Equal(t, tt.expectedStatusCode, cloudErr.StatusCode)
			assert.Equal(t, tt.expectedCode, cloudErr.Code)
			if tt.expectedMessage != "" {
				assert.Contains(t, cloudErr.Message, tt.expectedMessage)
			}
		})
	}
}
