// Copyright 2026 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 validationutils

import (
	"context"
	"fmt"
	"strings"

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

	azureclient "github.com/Azure/ARO-HCP/backend/pkg/azure/client"
	"github.com/Azure/ARO-HCP/internal/api/coreapi"
	"github.com/Azure/ARO-HCP/internal/utils"
)

// AzureClusterManagedIdentitiesExistenceValidation validates the existence of all managed identities defined in the cluster.
// It assumes all identities present are for recognized operators.
type AzureClusterManagedIdentitiesExistenceValidation struct {
	smiClientBuilder azureclient.ServiceManagedIdentityClientBuilder
}

func NewAzureClusterManagedIdentitiesExistenceValidation(
	smiClientBuilder azureclient.ServiceManagedIdentityClientBuilder,
) *AzureClusterManagedIdentitiesExistenceValidation {
	return &AzureClusterManagedIdentitiesExistenceValidation{
		smiClientBuilder: smiClientBuilder,
	}
}

func (v *AzureClusterManagedIdentitiesExistenceValidation) Name() string {
	return "AzureClusterManagedIdentitiesExistenceValidation"
}

func (v *AzureClusterManagedIdentitiesExistenceValidation) Validate(ctx context.Context, clusterSubscription *coreapi.Subscription, cluster *coreapi.HCPOpenShiftCluster) error {
	smiResourceID := cluster.CustomerProperties.Platform.OperatorsAuthentication.UserAssignedIdentities.ServiceManagedIdentity
	clusterIdentityURL := cluster.ServiceProviderProperties.ManagedIdentitiesDataPlaneIdentityURL
	// We check the existence of the Cluster's Service Managed Identity by
	// attempting to retrieve the user assigned identities client using the
	// service managed identity's identity credentials, which we obtain by
	// requesting them via the Managed Identities Data Plane Service. If the
	// service managed identity does not exist the request will fail.
	uaisClient, err := v.smiClientBuilder.UserAssignedIdentitiesClient(ctx, clusterIdentityURL, smiResourceID, cluster.ID.SubscriptionID)
	if err != nil {
		return utils.TrackError(fmt.Errorf("failed to get user assigned identities client: %w", err))
	}

	clusterUAIsProfile := &cluster.CustomerProperties.Platform.OperatorsAuthentication.UserAssignedIdentities
	clusterOperatorsMIsResourceIDs := v.clusterOperatorsManagedIdentities(clusterUAIsProfile)

	var notFoundMIsStrs []string
	for _, resourceID := range clusterOperatorsMIsResourceIDs {
		_, err := uaisClient.Get(ctx, resourceID.ResourceGroupName, resourceID.Name, nil)
		if azureclient.IsResourceNotFoundErr(err) {
			notFoundMIsStrs = append(notFoundMIsStrs, resourceID.String())
		}
		if err != nil {
			// TODO is it ok to error when one of them fails to get when the error is not a resource not found error?
			return utils.TrackError(fmt.Errorf("failed to get managed identity '%s': %w", resourceID, err))
		}
	}

	if len(notFoundMIsStrs) > 0 {
		return utils.TrackError(fmt.Errorf("managed identities not found: %s", strings.Join(notFoundMIsStrs, ", ")))
	}

	return nil
}

// clusterOperatorsManagedIdentities returns a list of the control and data plane identities defined in the cluster.
func (v *AzureClusterManagedIdentitiesExistenceValidation) clusterOperatorsManagedIdentities(
	clusterUAIsProfile *coreapi.UserAssignedIdentitiesProfile) []*azcorearm.ResourceID {
	var resourceIDs []*azcorearm.ResourceID

	for _, miResourceID := range clusterUAIsProfile.ControlPlaneOperators {
		resourceIDs = append(resourceIDs, miResourceID)
	}
	for _, miResourceID := range clusterUAIsProfile.DataPlaneOperators {
		resourceIDs = append(resourceIDs, miResourceID)
	}

	return resourceIDs
}
