/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */

import {
    Authority,
    INetworkModule,
    Logger,
    AuthenticationResult,
} from "@azure/msal-common/node";
import { AppService } from "./ManagedIdentitySources/AppService.js";
import { AzureArc } from "./ManagedIdentitySources/AzureArc.js";
import { CloudShell } from "./ManagedIdentitySources/CloudShell.js";
import { Imds } from "./ManagedIdentitySources/Imds.js";
import { ServiceFabric } from "./ManagedIdentitySources/ServiceFabric.js";
import { CryptoProvider } from "../crypto/CryptoProvider.js";
import {
    ManagedIdentityErrorCodes,
    createManagedIdentityError,
} from "../error/ManagedIdentityError.js";
import { ManagedIdentityRequest } from "../request/ManagedIdentityRequest.js";
import { ManagedIdentityId } from "../config/ManagedIdentityId.js";
import { NodeStorage } from "../cache/NodeStorage.js";
import { BaseManagedIdentitySource } from "./ManagedIdentitySources/BaseManagedIdentitySource.js";
import { ManagedIdentitySourceNames } from "../utils/Constants.js";

/*
 * Class to initialize a managed identity and identify the service.
 * Original source of code: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/src/ManagedIdentityClient.cs
 */
export class ManagedIdentityClient {
    private logger: Logger;
    private nodeStorage: NodeStorage;
    private networkClient: INetworkModule;
    private cryptoProvider: CryptoProvider;

    private static identitySource?: BaseManagedIdentitySource;
    public static sourceName?: ManagedIdentitySourceNames;

    constructor(
        logger: Logger,
        nodeStorage: NodeStorage,
        networkClient: INetworkModule,
        cryptoProvider: CryptoProvider
    ) {
        this.logger = logger;
        this.nodeStorage = nodeStorage;
        this.networkClient = networkClient;
        this.cryptoProvider = cryptoProvider;
    }

    public async sendManagedIdentityTokenRequest(
        managedIdentityRequest: ManagedIdentityRequest,
        managedIdentityId: ManagedIdentityId,
        fakeAuthority: Authority,
        refreshAccessToken?: boolean
    ): Promise<AuthenticationResult> {
        if (!ManagedIdentityClient.identitySource) {
            ManagedIdentityClient.identitySource =
                this.selectManagedIdentitySource(
                    this.logger,
                    this.nodeStorage,
                    this.networkClient,
                    this.cryptoProvider,
                    managedIdentityId
                );
        }

        return ManagedIdentityClient.identitySource.acquireTokenWithManagedIdentity(
            managedIdentityRequest,
            managedIdentityId,
            fakeAuthority,
            refreshAccessToken
        );
    }

    private allEnvironmentVariablesAreDefined(
        environmentVariables: Array<string | undefined>
    ): boolean {
        return Object.values(environmentVariables).every(
            (environmentVariable) => {
                return environmentVariable !== undefined;
            }
        );
    }

    /**
     * Determine the Managed Identity Source based on available environment variables. This API is consumed by ManagedIdentityApplication's getManagedIdentitySource.
     * @returns ManagedIdentitySourceNames - The Managed Identity source's name
     */
    public getManagedIdentitySource(): ManagedIdentitySourceNames {
        ManagedIdentityClient.sourceName =
            this.allEnvironmentVariablesAreDefined(
                ServiceFabric.getEnvironmentVariables()
            )
                ? ManagedIdentitySourceNames.SERVICE_FABRIC
                : this.allEnvironmentVariablesAreDefined(
                      AppService.getEnvironmentVariables()
                  )
                ? ManagedIdentitySourceNames.APP_SERVICE
                : this.allEnvironmentVariablesAreDefined(
                      CloudShell.getEnvironmentVariables()
                  )
                ? ManagedIdentitySourceNames.CLOUD_SHELL
                : this.allEnvironmentVariablesAreDefined(
                      AzureArc.getEnvironmentVariables()
                  )
                ? ManagedIdentitySourceNames.AZURE_ARC
                : ManagedIdentitySourceNames.DEFAULT_TO_IMDS;

        return ManagedIdentityClient.sourceName;
    }

    /**
     * Tries to create a managed identity source for all sources
     * @returns the managed identity Source
     */
    private selectManagedIdentitySource(
        logger: Logger,
        nodeStorage: NodeStorage,
        networkClient: INetworkModule,
        cryptoProvider: CryptoProvider,
        managedIdentityId: ManagedIdentityId
    ): BaseManagedIdentitySource {
        const source =
            ServiceFabric.tryCreate(
                logger,
                nodeStorage,
                networkClient,
                cryptoProvider,
                managedIdentityId
            ) ||
            AppService.tryCreate(
                logger,
                nodeStorage,
                networkClient,
                cryptoProvider
            ) ||
            CloudShell.tryCreate(
                logger,
                nodeStorage,
                networkClient,
                cryptoProvider,
                managedIdentityId
            ) ||
            AzureArc.tryCreate(
                logger,
                nodeStorage,
                networkClient,
                cryptoProvider,
                managedIdentityId
            ) ||
            Imds.tryCreate(logger, nodeStorage, networkClient, cryptoProvider);
        if (!source) {
            throw createManagedIdentityError(
                ManagedIdentityErrorCodes.unableToCreateSource
            );
        }
        return source;
    }
}
