package cmd

// 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.

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"path/filepath"

	"github.com/go-logr/logr"
	"github.com/spf13/cobra"
	"go.opentelemetry.io/otel"
	semconv "go.opentelemetry.io/otel/semconv/v1.17.0"

	"k8s.io/component-base/metrics/legacyregistry"
	"k8s.io/klog/v2"

	"github.com/Azure/ARO-HCP/backend/pkg/app"
	azureclient "github.com/Azure/ARO-HCP/backend/pkg/azure/client"
	internalazure "github.com/Azure/ARO-HCP/internal/azure"
	"github.com/Azure/ARO-HCP/internal/database/cosmosstorage/kubeappliercosmosstorage"
	"github.com/Azure/ARO-HCP/internal/signal"
	"github.com/Azure/ARO-HCP/internal/tracing"
	"github.com/Azure/ARO-HCP/internal/utils"
	"github.com/Azure/ARO-HCP/internal/version"
)

type BackendRootCmdFlags struct {
	Kubeconfig                                                                                    string
	K8sNamespace                                                                                  string
	AzureLocation                                                                                 string
	AzureCosmosDBName                                                                             string
	AzureCosmosDBURL                                                                              string
	ClustersServiceURL                                                                            string
	ClustersServiceTLSInsecure                                                                    bool
	MetricsServerListenAddress                                                                    string
	HealthzServerListenAddress                                                                    string
	AzureRuntimeConfigPath                                                                        string
	AzureFirstPartyApplicationCertificateBundlePath                                               string
	AzureFirstPartyApplicationClientID                                                            string
	InsecureAzureARMPermissionsManagerIdentityCertificateBundlePath                               string
	InsecureAzureARMPermissionsManagerIdentityClientID                                            string
	InsecureAzureARMPermissionsManagerIdentityTenantID                                            string
	LogVerbosity                                                                                  int
	MaestroSourceEnvironmentIdentifier                                                            string
	InsecureAzureManagedIdentityMockCertificateBundlePath                                         string
	InsecureAzureManagedIdentityMockClientID                                                      string
	InsecureAzureManagedIdentityMockServicePrincipalID                                            string
	InsecureAzureManagedIdentityMockTenantID                                                      string
	InsecureIgnoreUserAzureManagedIdentitiesThatNeedManagedIdentitiesDataplaneAvailableAndUseMock bool
	ExitOnPanic                                                                                   bool
	AzureClusterScopedIdentitiesRoleSetName                                                       string
}

func (f *BackendRootCmdFlags) AddFlags(cmd *cobra.Command) {
	cmd.Flags().StringVar(&f.Kubeconfig, "kubeconfig", f.Kubeconfig, "Absolute path to the kubeconfig file")
	cmd.Flags().StringVar(&f.K8sNamespace, "namespace", f.K8sNamespace, "Kubernetes namespace")
	cmd.Flags().StringVar(&f.AzureLocation, "location", f.AzureLocation, "Azure location")
	cmd.Flags().StringVar(&f.AzureCosmosDBName, "cosmos-name", f.AzureCosmosDBName, "Cosmos database name")
	cmd.Flags().StringVar(&f.AzureCosmosDBURL, "cosmos-url", f.AzureCosmosDBURL, "Cosmos database URL")
	cmd.Flags().StringVar(&f.ClustersServiceURL, "clusters-service-url", f.ClustersServiceURL, "URL of the OCM API gateway")
	cmd.Flags().BoolVar(&f.ClustersServiceTLSInsecure, "insecure", f.ClustersServiceTLSInsecure, "Skip validating TLS for clusters-service")
	cmd.Flags().StringVar(&f.MetricsServerListenAddress, "metrics-listen-address", f.MetricsServerListenAddress, "Address on which to expose metrics")
	cmd.Flags().StringVar(&f.HealthzServerListenAddress, "healthz-listen-address", f.HealthzServerListenAddress, "Address on which Healthz endpoint will be supported")
	cmd.Flags().StringVar(
		&f.AzureRuntimeConfigPath, "azure-runtime-config-path", f.AzureRuntimeConfigPath,
		"Path to a file containing the Azure runtime configuration in JSON or YAML format following the schema defined "+
			"in backend/api/azure/v1/AzureRuntimeConfig",
	)
	cmd.Flags().StringVar(
		&f.AzureFirstPartyApplicationCertificateBundlePath,
		"azure-first-party-application-certificate-bundle-path", f.AzureFirstPartyApplicationCertificateBundlePath,
		"Path to a file containing an X.509 Certificate based client certificate, consisting of a private key and "+
			"certificate chain, in a PEM or PKCS#12 format for authenticating clients with a first party application identity",
	)
	cmd.Flags().StringVar(
		&f.AzureFirstPartyApplicationClientID,
		"azure-first-party-application-client-id",
		f.AzureFirstPartyApplicationClientID,
		"The client id of the first party application identity",
	)
	cmd.Flags().IntVar(&f.LogVerbosity, "log-verbosity", f.LogVerbosity, "Log verbosity. 0 is the default verbosity level, equivalent to INFO. It must be a value >= 0, where a higher value means more verbose output.")

	cmd.Flags().StringVar(&f.MaestroSourceEnvironmentIdentifier, "maestro-source-environment-identifier", f.MaestroSourceEnvironmentIdentifier,
		"The environment name part used when generating Maestro Source IDs using the backend/pkg/maestro.GenerateMaestroSourceID function. "+
			"It must be between 1 and 10 characters and can contain only lowercase letters. Example value: arohcpdev."+
			"Changing the value causes the Maestro Source IDs generated to change which impacts visibility of previously existing resources. It is "+
			"therefore a must to first understand and plan the impact changing the value would have, including any potential migration plan before changing it.",
	)

	cmd.Flags().BoolVar(
		&f.InsecureIgnoreUserAzureManagedIdentitiesThatNeedManagedIdentitiesDataplaneAvailableAndUseMock,
		"insecure-ignore-user-azure-managed-identities-that-need-managed-identities-dataplane-available-and-use-mock",
		f.InsecureIgnoreUserAzureManagedIdentitiesThatNeedManagedIdentitiesDataplaneAvailableAndUseMock,
		"If set, the cluster-scoped user-provided Managed Identities that need the Managed Identities Dataplane service to be available are not used "+
			"and the managed identity mock identity (MI Mock) will be used instead. The identities that need the Managed Identities Dataplane service are the "+
			"Cluster's Control Plane Operators identities and the Cluster's Service Managed Identity. Even though when this is set there's no authentication "+
			"as them against Azure, the backend still leverages them to perform some permissions validation checks. Additionally, when it is set, the backend will also "+
			"use the ARM Permissions Manager identity to perform some additional permissions management and validations. When set, the --insecure-azure-managed-identity-mock-certificate-bundle-path, "+
			"--insecure-azure-managed-identity-mock-client-id, --insecure-azure-managed-identity-mock-principal-id, --insecure-azure-managed-identity-mock-tenant-id, "+
			"--insecure-azure-arm-permissions-manager-identity-certificate-bundle-path, --insecure-azure-arm-permissions-manager-identity-client-id, "+
			"--insecure-azure-arm-permissions-manager-identity-tenant-id flags must also be set.",
	)

	cmd.Flags().StringVar(
		&f.InsecureAzureManagedIdentityMockCertificateBundlePath,
		"insecure-azure-managed-identity-mock-certificate-bundle-path",
		"",
		"Path to a file containing an X.509 Certificate based client certificate, consisting of a private key and "+
			"certificate chain, in a PEM or PKCS#12 format for authenticating clients with the msi mock identity, which is "+
			"a common Azure Service Principal identity. This flag should only be set in environments where "+
			"Microsoft's MI Dataplane service is not available. "+
			"When set, it must be set in combination with the '--insecure-azure-managed-identity-mock-client-id' and "+
			"'--insecure-azure-managed-identity-mock-principal-id' and '--insecure-azure-managed-identity-mock-tenant-id' flags.",
	)

	cmd.Flags().StringVar(
		&f.InsecureAzureManagedIdentityMockClientID,
		"insecure-azure-managed-identity-mock-client-id",
		"",
		"The client id of the ARO-HCP Clusters Managed Identities (MI) mock identity, which is a common Azure Service Principal identity. "+
			"This flag should only be set in environments where Microsoft's MI Dataplane service is not available. "+
			"When set, it must be set in combination with the '--insecure-azure-managed-identity-mock-certificate-bundle-path' and "+
			"'--insecure-azure-managed-identity-mock-principal-id' and '--insecure-azure-managed-identity-mock-tenant-id' flags.",
	)

	cmd.Flags().StringVar(
		&f.InsecureAzureManagedIdentityMockServicePrincipalID,
		"insecure-azure-managed-identity-mock-principal-id",
		"",
		"The principal id of the ARO-HCP Clusters Managed Identities (MI) mock identity, which is a common Azure Service Principal identity. "+
			"This flag should only be set in environments where Microsoft's MI Dataplane service is not available. "+
			"When set, it must be set in combination with the '--insecure-azure-managed-identity-mock-certificate-bundle-path' and "+
			"'--azure-mi-mock-principal-client-id' and '--insecure-azure-managed-identity-mock-tenant-id' flags.",
	)

	cmd.Flags().StringVar(
		&f.InsecureAzureManagedIdentityMockTenantID,
		"insecure-azure-managed-identity-mock-tenant-id",
		"",
		"The tenant id of the ARO-HCP Clusters Managed Identities (MI) mock identity, which is a common Azure Service Principal identity. "+
			"This flag should only be set in environments where Microsoft's MI Dataplane service is not available. "+
			"When set, it must be set in combination with the '--insecure-azure-managed-identity-mock-certificate-bundle-path', "+
			"'--insecure-azure-managed-identity-mock-client-id' and '--insecure-azure-managed-identity-mock-principal-id' flags.",
	)

	cmd.Flags().BoolVar(&f.ExitOnPanic, "exit-on-panic", f.ExitOnPanic,
		"If set, backend will exit the process if a panic occurs. As of now it only controls the setting of k8s.io/apimachinery/pkg/util/runtime.ReallyCrash",
	)

	cmd.Flags().StringVar(
		&f.InsecureAzureARMPermissionsManagerIdentityCertificateBundlePath,
		"insecure-azure-arm-permissions-manager-identity-certificate-bundle-path",
		f.InsecureAzureARMPermissionsManagerIdentityCertificateBundlePath,
		"Path to a file containing an X.509 Certificate based client certificate, consisting of a private key and "+
			"certificate chain, in a PEM or PKCS#12 format for authenticating clients with the ARM Permissions Manager identity. "+
			"When set, it must be set in combination with the '--insecure-azure-arm-permissions-manager-identity-client-id' and "+
			"'--insecure-azure-arm-permissions-manager-identity-tenant-id' flags.",
	)

	cmd.Flags().StringVar(
		&f.InsecureAzureARMPermissionsManagerIdentityClientID,
		"insecure-azure-arm-permissions-manager-identity-client-id",
		f.InsecureAzureARMPermissionsManagerIdentityClientID,
		"The client id of the ARM Permissions Manager identity. When set, it must be set in combination with the '--insecure-azure-arm-permissions-manager-identity-certificate-bundle-path' and "+
			"'--insecure-azure-arm-permissions-manager-identity-tenant-id' flags.",
	)

	cmd.Flags().StringVar(
		&f.InsecureAzureARMPermissionsManagerIdentityTenantID,
		"insecure-azure-arm-permissions-manager-identity-tenant-id",
		f.InsecureAzureARMPermissionsManagerIdentityTenantID,
		"The tenant id of the ARM Permissions Manager identity. When set, it must be set in combination with the '--insecure-azure-arm-permissions-manager-identity-certificate-bundle-path' and "+
			"'--insecure-azure-arm-permissions-manager-identity-client-id' flags.",
	)

	cmd.Flags().StringVar(
		&f.AzureClusterScopedIdentitiesRoleSetName,
		"azure-cluster-scoped-identities-role-set-name",
		f.AzureClusterScopedIdentitiesRoleSetName,
		"The name of the cluster scoped identities role set to use. It is used to select the appropriate set of operator role definitions associated to the cluster scoped identities. Accepted values: [dev, public].",
	)

	cmd.MarkFlagsRequiredTogether("cosmos-name", "cosmos-url")
}

func (f *BackendRootCmdFlags) validate() error {
	if len(f.AzureLocation) == 0 {
		return utils.TrackError(fmt.Errorf("--location is required"))
	}

	if len(f.ClustersServiceURL) == 0 {
		return utils.TrackError(fmt.Errorf("--clusters-service-url is required"))
	}

	if len(f.AzureCosmosDBName) == 0 {
		return utils.TrackError(fmt.Errorf("--cosmos-name is required"))
	}

	if len(f.AzureCosmosDBURL) == 0 {
		return utils.TrackError(fmt.Errorf("--cosmos-url is required"))
	}

	if len(f.K8sNamespace) == 0 {
		return utils.TrackError(fmt.Errorf("--namespace is required"))
	}

	if f.LogVerbosity < 0 {
		return utils.TrackError(fmt.Errorf("--log-verbosity must be a value >= 0"))
	}

	if len(f.MaestroSourceEnvironmentIdentifier) == 0 {
		return utils.TrackError(fmt.Errorf("--maestro-source-environment-identifier is required"))
	}
	if len(f.MaestroSourceEnvironmentIdentifier) > 10 {
		return utils.TrackError(fmt.Errorf("--maestro-source-environment-identifier must be less than 10 characters"))
	}

	// If InsecureIgnoreUserAzureManagedIdentitiesThatNeedManagedIdentitiesDataplaneAvailableAndUseMock is set,
	// we need to ensure that all the azure managed identity mock identity related flags and all of the ARM Permissions Manager identity related flags are set.
	if f.InsecureIgnoreUserAzureManagedIdentitiesThatNeedManagedIdentitiesDataplaneAvailableAndUseMock {
		if len(f.InsecureAzureManagedIdentityMockCertificateBundlePath) == 0 {
			return utils.TrackError(fmt.Errorf("--insecure-azure-managed-identity-mock-certificate-bundle-path must be set"))
		}
		if len(f.InsecureAzureManagedIdentityMockClientID) == 0 {
			return utils.TrackError(fmt.Errorf("--insecure-azure-managed-identity-mock-client-id must be set"))
		}
		if len(f.InsecureAzureManagedIdentityMockServicePrincipalID) == 0 {
			return utils.TrackError(fmt.Errorf("--insecure-azure-managed-identity-mock-principal-id must be set"))
		}
		if len(f.InsecureAzureManagedIdentityMockTenantID) == 0 {
			return utils.TrackError(fmt.Errorf("--insecure-azure-managed-identity-mock-tenant-id must be set"))
		}
		if len(f.InsecureAzureARMPermissionsManagerIdentityCertificateBundlePath) == 0 {
			return utils.TrackError(fmt.Errorf("--insecure-azure-arm-permissions-manager-identity-certificate-bundle-path must be set"))
		}
		if len(f.InsecureAzureARMPermissionsManagerIdentityClientID) == 0 {
			return utils.TrackError(fmt.Errorf("--insecure-azure-arm-permissions-manager-identity-client-id must be set"))
		}
		if len(f.InsecureAzureARMPermissionsManagerIdentityTenantID) == 0 {
			return utils.TrackError(fmt.Errorf("--insecure-azure-arm-permissions-manager-identity-tenant-id must be set"))
		}
	} else { // Otherwise we also validate that none of the azure managed identity mock identity nor the ARM Permissions Manager identity related flags are set in that case.
		if len(f.InsecureAzureManagedIdentityMockCertificateBundlePath) != 0 {
			return utils.TrackError(fmt.Errorf("--insecure-azure-managed-identity-mock-certificate-bundle-path must not be set"))
		}
		if len(f.InsecureAzureManagedIdentityMockClientID) != 0 {
			return utils.TrackError(fmt.Errorf("--insecure-azure-managed-identity-mock-client-id must not be set"))
		}
		if len(f.InsecureAzureManagedIdentityMockServicePrincipalID) != 0 {
			return utils.TrackError(fmt.Errorf("--insecure-azure-managed-identity-mock-principal-id must not be set"))
		}
		if len(f.InsecureAzureManagedIdentityMockTenantID) != 0 {
			return utils.TrackError(fmt.Errorf("--insecure-azure-managed-identity-mock-tenant-id must not be set"))
		}
		if len(f.InsecureAzureARMPermissionsManagerIdentityCertificateBundlePath) != 0 {
			return utils.TrackError(fmt.Errorf("--insecure-azure-arm-permissions-manager-identity-certificate-bundle-path must not be set"))
		}
		if len(f.InsecureAzureARMPermissionsManagerIdentityClientID) != 0 {
			return utils.TrackError(fmt.Errorf("--insecure-azure-arm-permissions-manager-identity-client-id must not be set"))
		}
		if len(f.InsecureAzureARMPermissionsManagerIdentityTenantID) != 0 {
			return utils.TrackError(fmt.Errorf("--insecure-azure-arm-permissions-manager-identity-tenant-id must not be set"))
		}
	}

	if f.AzureClusterScopedIdentitiesRoleSetName != string(internalazure.RoleDefinitionConfigSetNameDev) && f.AzureClusterScopedIdentitiesRoleSetName != string(internalazure.RoleDefinitionConfigSetNamePublic) {
		return utils.TrackError(fmt.Errorf("--azure-cluster-scoped-identities-role-set-name must be either '%s' or '%s'", internalazure.RoleDefinitionConfigSetNameDev, internalazure.RoleDefinitionConfigSetNamePublic))
	}

	return nil
}

func (f *BackendRootCmdFlags) ToBackendOptions(ctx context.Context, cmd *cobra.Command) (*app.BackendOptions, error) {
	logger := utils.LoggerFromContext(ctx)

	kubeconfig, err := app.NewKubeconfig(f.Kubeconfig)
	if err != nil {
		return nil, utils.TrackError(fmt.Errorf("failed to create Kubernetes configuration: %w", err))
	}
	// Use pod name as the lock identity.
	hostname, err := os.Hostname()
	if err != nil {
		return nil, fmt.Errorf("failed to get hostname: %w", err)
	}
	leaderElectionLock, err := app.NewLeaderElectionLock(hostname, kubeconfig, f.K8sNamespace)
	if err != nil {
		return nil, utils.TrackError(fmt.Errorf("failed to create leader election lock: %w", err))
	}

	// Initialize the global OpenTelemetry tracer.
	otelShutdown, err := tracing.ConfigureOpenTelemetryTracer(
		ctx,
		logger,
		semconv.CloudRegion(f.AzureLocation),
		semconv.ServiceNameKey.String(cmd.Short),
		semconv.ServiceVersionKey.String(cmd.Version),
	)
	if err != nil {
		return nil, utils.TrackError(fmt.Errorf("could not initialize opentelemetry sdk: %w", err))
	}

	otelTracerProvider := otel.GetTracerProvider()
	azureConfig, err := app.NewAzureConfig(ctx, f.AzureRuntimeConfigPath, otelTracerProvider)
	if err != nil {
		return nil, utils.TrackError(fmt.Errorf("failed to create Azure configuration: %w", err))
	}

	fpaTokenCredRetriever, err := app.NewFirstPartyApplicationTokenCredentialRetriever(ctx, f.AzureFirstPartyApplicationCertificateBundlePath, f.AzureFirstPartyApplicationClientID, azureConfig)
	if err != nil {
		return nil, utils.TrackError(fmt.Errorf("failed to create FPA token credential retriever: %w", err))
	}

	fpaClientBuilder, err := app.NewFirstPartyApplicationClientBuilder(fpaTokenCredRetriever, azureConfig)
	if err != nil {
		return nil, utils.TrackError(fmt.Errorf("failed to create FPA client builder: %w", err))
	}

	backendIdentityAzureClients, err := app.NewBackendIdentityAzureClients(ctx, azureConfig)
	if err != nil {
		return nil, utils.TrackError(fmt.Errorf("failed to create backend identity azure clients: %w", err))
	}

	backendIdentityAzureCachedReaders, err := app.NewBackendIdentityAzureCachedReaders(ctx, backendIdentityAzureClients)
	if err != nil {
		return nil, utils.TrackError(fmt.Errorf("failed to create backend identity azure cached readers: %w", err))
	}

	var fpaMIDataplaneClientBuilder azureclient.FPAMIDataplaneClientBuilder
	var checkAccessV2ClientBuilder azureclient.CheckAccessV2ClientBuilder
	if !f.InsecureIgnoreUserAzureManagedIdentitiesThatNeedManagedIdentitiesDataplaneAvailableAndUseMock {
		// In ARO-HCP environments where we have a real FPA, we use the FPA identity to create the FPA MI dataplane client builder
		fpaMIDataplaneClientBuilder = azureclient.NewFPAMIDataplaneClientBuilder(
			azureConfig.AzureRuntimeConfig.ServiceTenantID,
			fpaTokenCredRetriever,
			azureConfig.AzureRuntimeConfig.ManagedIdentitiesDataPlaneAudienceResource,
			azureConfig.CloudEnvironment.AZCoreClientOptions(),
		)

		// In ARO-HCP environments where we have a real FPA, we use the FPA identity to create the Check Access V2 client
		checkAccessV2ClientBuilder = azureclient.NewRealFPAIdentityCheckAccessV2ClientBuilder(
			fpaTokenCredRetriever, azureConfig.CloudEnvironment.CheckAccessV2Endpoint(f.AzureLocation),
			azureConfig.CloudEnvironment.CheckAccessV2Scope(), azureConfig.CloudEnvironment.AZCoreClientOptions(),
		)
	} else {
		// In ARO-HCP environments where we don't have a real FPA, we use the Azure Permissions Manager identity to create the Check Access V2 client
		azureARMPermissionsManagerIdentityTokenCredentialRetriever, err := newInsecureAzurePermissionsManagerIdentityTokenCredentialRetriever(
			ctx, f.InsecureAzureARMPermissionsManagerIdentityTenantID, f.InsecureAzureARMPermissionsManagerIdentityClientID,
			f.InsecureAzureARMPermissionsManagerIdentityCertificateBundlePath, azureConfig,
		)
		if err != nil {
			return nil, utils.TrackError(fmt.Errorf("failed to create Azure Permissions Manager identity token credential retriever: %w", err))
		}
		checkAccessV2ClientBuilder = azureclient.NewInsecureARMPermissionsManagerIdentityCheckAccessV2ClientBuilder(
			azureARMPermissionsManagerIdentityTokenCredentialRetriever,
			azureConfig.CloudEnvironment.CheckAccessV2Endpoint(f.AzureLocation),
			azureConfig.CloudEnvironment.CheckAccessV2Scope(), azureConfig.CloudEnvironment.AZCoreClientOptions(),
		)

		// In ARO-HCP environments where we don't have a real FPA, we use the HardcodedIdentityFPAMIDataplaneClientBuilder to create the FPA MI dataplane client builder
		fpaMIDataplaneClientBuilder, err = newHardcodedIdentityFPAMIDataplaneClientBuilder(
			f.InsecureAzureManagedIdentityMockCertificateBundlePath, f.InsecureAzureManagedIdentityMockClientID, f.InsecureAzureManagedIdentityMockServicePrincipalID, f.InsecureAzureManagedIdentityMockTenantID,
			azureConfig,
		)
		if err != nil {
			return nil, utils.TrackError(fmt.Errorf("error getting hardcoded identity FPA MI dataplane client builder: %w", err))
		}
	}

	smiClientBuilder := app.NewServiceManagedIdentityClientBuilder(fpaMIDataplaneClientBuilder, azureConfig)

	azCoreClientOptions := *azureConfig.CloudEnvironment.AZCoreClientOptions()

	cosmosDatabaseClient, err := app.NewCosmosDatabaseClient(
		f.AzureCosmosDBURL,
		f.AzureCosmosDBName,
		azCoreClientOptions,
	)
	if err != nil {
		return nil, utils.TrackError(err)
	}

	resourcesCosmosDBClient, billingDBClient, err := app.NewCosmosDBClients(cosmosDatabaseClient)
	if err != nil {
		return nil, utils.TrackError(err)
	}

	fleetDBClient, err := app.NewFleetDBClient(cosmosDatabaseClient)
	if err != nil {
		return nil, utils.TrackError(fmt.Errorf("failed to create fleet db client: %w", err))
	}

	// In the per-management-cluster container model the backend resolves each MC's
	// kube-applier container by walking the fleet's ManagementCluster lister. We
	// back the lister with the FleetDBClient directly so the registry is usable
	// before informers start; the per-MC azcosmos client construction is cached
	// inside the registry, but the MC list itself is re-read each call so fleet
	// additions/removals become visible without restarting the backend.
	kubeApplierDBClients := app.NewKubeApplierDBClients(
		cosmosDatabaseClient,
		kubeappliercosmosstorage.NewDBBackedManagementClusterLister(fleetDBClient),
	)

	clustersServiceClient, err := app.NewClustersServiceClient(ctx, f.ClustersServiceURL, f.ClustersServiceTLSInsecure)
	if err != nil {
		return nil, utils.TrackError(fmt.Errorf("failed to create clusters service client: %w", err))
	}

	clusterScopedIdentitiesConfig := internalazure.NewClusterScopedIdentitiesConfig(internalazure.RoleDefinitionConfigSetName(f.AzureClusterScopedIdentitiesRoleSetName))

	backendOptions := &app.BackendOptions{
		AppShortDescriptionName:            cmd.Short,
		AppVersion:                         cmd.Version,
		AzureLocation:                      f.AzureLocation,
		LeaderElectionLock:                 leaderElectionLock,
		ResourcesDBClient:                  resourcesCosmosDBClient,
		BillingDBClient:                    billingDBClient,
		FleetDBClient:                      fleetDBClient,
		KubeApplierDBClients:               kubeApplierDBClients,
		ClustersServiceClient:              clustersServiceClient,
		MetricsServerListenAddress:         f.MetricsServerListenAddress,
		HealthzServerListenAddress:         f.HealthzServerListenAddress,
		TracerProviderShutdownFunc:         otelShutdown,
		MaestroSourceEnvironmentIdentifier: f.MaestroSourceEnvironmentIdentifier,
		FPAClientBuilder:                   fpaClientBuilder,
		BackendIdentityAzureClients:        backendIdentityAzureClients,
		BackendIdentityAzureCachedReaders:  backendIdentityAzureCachedReaders,
		ExitOnPanic:                        f.ExitOnPanic,
		FPAMIDataplaneClientBuilder:        fpaMIDataplaneClientBuilder,
		SMIClientBuilder:                   smiClientBuilder,
		CheckAccessV2ClientBuilder:         checkAccessV2ClientBuilder,
		ClusterScopedIdentitiesConfig:      clusterScopedIdentitiesConfig,
		MetricsRegisterer:                  legacyregistry.Registerer(),
		MetricsGatherer:                    legacyregistry.DefaultGatherer,
	}

	return backendOptions, nil
}

func NewBackendRootCmdFlags() *BackendRootCmdFlags {
	flags := &BackendRootCmdFlags{
		Kubeconfig:                 "",
		K8sNamespace:               os.Getenv("NAMESPACE"),
		AzureLocation:              os.Getenv("LOCATION"),
		AzureCosmosDBName:          os.Getenv("DB_NAME"),
		AzureCosmosDBURL:           os.Getenv("DB_URL"),
		ClustersServiceTLSInsecure: false,
		MetricsServerListenAddress: ":8081",
		HealthzServerListenAddress: ":8083",
		AzureRuntimeConfigPath:     "",
		AzureFirstPartyApplicationCertificateBundlePath: "",
		AzureFirstPartyApplicationClientID:              "",
		LogVerbosity:                                    0,
		MaestroSourceEnvironmentIdentifier:              "",
		ExitOnPanic:                                     true,
	}

	return flags
}

func NewCmdRoot() *cobra.Command {
	processName := filepath.Base(os.Args[0])

	flags := NewBackendRootCmdFlags()

	cmd := &cobra.Command{
		Use:   processName,
		Args:  cobra.NoArgs,
		Short: "ARO HCP Backend",
		Long: fmt.Sprintf(`ARO HCP Backend

	The command runs the ARO HCP Backend. It executes background processing that
	communicates with Clusters Service and CosmosDB.

	# Run ARO HCP Backend locally to connect to a local Clusters Service at http://localhost:8000
	%s --cosmos-name ${DB_NAME} --cosmos-url ${DB_URL} --location ${LOCATION} \
		--clusters-service-url "http://localhost:8000"
`, processName),
		RunE: func(cmd *cobra.Command, args []string) error {
			err := RunRootCmd(cmd, flags)
			if err != nil {
				return utils.TrackError(fmt.Errorf("failed to run: %w", err))
			}
			return nil
		},
		SilenceErrors: true, // errors are printed after Execute
	}

	cmd.SetErrPrefix(cmd.Short + " error:")

	cmd.Version = version.CommitSHA

	flags.AddFlags(cmd)

	return cmd
}

func RunRootCmd(cmd *cobra.Command, flags *BackendRootCmdFlags) error {
	err := flags.validate()
	if err != nil {
		return utils.TrackError(fmt.Errorf("flags validation failed: %w", err))
	}

	// Setup signal context allowing for both graceful and forceful shutdown
	// through linux signals (SIGINT and SIGTERM).
	ctx := signal.SetupSignalContext()

	// Create a logr.Logger and add it to context for use throughout the application.
	// We use slog.Level(flags.LogVerbosity * -1) to convert the verbosity level to a slog.Level.
	// A value of 0 is equivalent to INFO. Higher values mean more verbose output.
	handlerOptions := &slog.HandlerOptions{Level: slog.Level(flags.LogVerbosity * -1), AddSource: true}
	slogJSONHandler := slog.NewJSONHandler(os.Stdout, handlerOptions)
	logger := logr.FromSlogHandler(slogJSONHandler)
	ctx = utils.ContextWithLogger(ctx, logger)

	// We set our logger to be used on klog log calls
	klog.SetLogger(logger)

	backendOptions, err := flags.ToBackendOptions(ctx, cmd)
	if err != nil {
		return utils.TrackError(fmt.Errorf("failed to convert flags to backend options: %w", err))
	}

	err = backendOptions.RunBackend(ctx)
	if err != nil {
		return utils.TrackError(fmt.Errorf("failed to run backend: %w", err))
	}

	return nil
}
