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

import { Logger } from "../logger/Logger.js";
import { UrlString } from "../url/UrlString.js";
import { AuthorityMetadataSource } from "../utils/Constants.js";
import { StaticAuthorityOptions } from "./AuthorityOptions.js";
import { CloudDiscoveryMetadata } from "./CloudDiscoveryMetadata.js";
import { CloudInstanceDiscoveryResponse } from "./CloudInstanceDiscoveryResponse.js";
import { OpenIdConfigResponse } from "./OpenIdConfigResponse.js";

type RawMetadata = {
    endpointMetadata: { [key: string]: OpenIdConfigResponse };
    instanceDiscoveryMetadata: CloudInstanceDiscoveryResponse;
};

// Build endpoint metadata dynamically to avoid string duplication
const endpointHosts: Array<{ host: string; issuerHost?: string }> = [
    { host: "login.microsoftonline.com" },
    {
        host: "login.chinacloudapi.cn",
        issuerHost: "login.partner.microsoftonline.cn", // Issuer differs
    },
    { host: "login.microsoftonline.us" },
    { host: "login.sovcloud-identity.fr" },
    { host: "login.sovcloud-identity.de" },
    { host: "login.sovcloud-identity.sg" },
];

function buildOpenIdConfig(
    host: string,
    issuerHost: string
): OpenIdConfigResponse {
    return {
        token_endpoint: `https://${host}/{tenantid}/oauth2/v2.0/token`,
        jwks_uri: `https://${host}/{tenantid}/discovery/v2.0/keys`,
        issuer: `https://${issuerHost}/{tenantid}/v2.0`,
        authorization_endpoint: `https://${host}/{tenantid}/oauth2/v2.0/authorize`,
        end_session_endpoint: `https://${host}/{tenantid}/oauth2/v2.0/logout`,
    };
}

const dynamicEndpointMetadata: RawMetadata["endpointMetadata"] =
    endpointHosts.reduce((acc, { host, issuerHost }) => {
        acc[host] = buildOpenIdConfig(host, issuerHost || host);
        return acc;
    }, {} as Record<string, OpenIdConfigResponse>);

export const rawMetdataJSON: RawMetadata = {
    endpointMetadata: dynamicEndpointMetadata,
    instanceDiscoveryMetadata: {
        tenant_discovery_endpoint:
            "https://{canonicalAuthority}/v2.0/.well-known/openid-configuration",
        metadata: [
            {
                preferred_network: "login.microsoftonline.com",
                preferred_cache: "login.windows.net",
                aliases: [
                    "login.microsoftonline.com",
                    "login.windows.net",
                    "login.microsoft.com",
                    "sts.windows.net",
                ],
            },
            {
                preferred_network: "login.partner.microsoftonline.cn",
                preferred_cache: "login.partner.microsoftonline.cn",
                aliases: [
                    "login.partner.microsoftonline.cn",
                    "login.chinacloudapi.cn",
                ],
            },
            {
                preferred_network: "login.microsoftonline.de",
                preferred_cache: "login.microsoftonline.de",
                aliases: ["login.microsoftonline.de"],
            },
            {
                preferred_network: "login.microsoftonline.us",
                preferred_cache: "login.microsoftonline.us",
                aliases: [
                    "login.microsoftonline.us",
                    "login.usgovcloudapi.net",
                ],
            },
            {
                preferred_network: "login-us.microsoftonline.com",
                preferred_cache: "login-us.microsoftonline.com",
                aliases: ["login-us.microsoftonline.com"],
            },
            {
                preferred_network: "login.sovcloud-identity.fr",
                preferred_cache: "login.sovcloud-identity.fr",
                aliases: ["login.sovcloud-identity.fr"],
            },
            {
                preferred_network: "login.sovcloud-identity.de",
                preferred_cache: "login.sovcloud-identity.de",
                aliases: ["login.sovcloud-identity.de"],
            },
            {
                preferred_network: "login.sovcloud-identity.sg",
                preferred_cache: "login.sovcloud-identity.sg",
                aliases: ["login.sovcloud-identity.sg"],
            },
            {
                preferred_network: "login.windows-ppe.net",
                preferred_cache: "login.windows-ppe.net",
                aliases: [
                    "login.windows-ppe.net",
                    "sts.windows-ppe.net",
                    "login.microsoft-ppe.com",
                ],
            },
        ],
    },
};

export const EndpointMetadata = rawMetdataJSON.endpointMetadata;
export const InstanceDiscoveryMetadata =
    rawMetdataJSON.instanceDiscoveryMetadata;

export const InstanceDiscoveryMetadataAliases: Set<String> = new Set();
InstanceDiscoveryMetadata.metadata.forEach(
    (metadataEntry: CloudDiscoveryMetadata) => {
        metadataEntry.aliases.forEach((alias: string) => {
            InstanceDiscoveryMetadataAliases.add(alias);
        });
    }
);

/**
 * Attempts to get an aliases array from the static authority metadata sources based on the canonical authority host
 * @param staticAuthorityOptions
 * @param logger
 * @returns
 */
export function getAliasesFromStaticSources(
    staticAuthorityOptions: StaticAuthorityOptions,
    logger: Logger,
    correlationId: string
): string[] {
    let staticAliases: string[] | undefined;
    const canonicalAuthority = staticAuthorityOptions.canonicalAuthority;
    if (canonicalAuthority) {
        const authorityHost = new UrlString(
            canonicalAuthority,
            correlationId
        ).getUrlComponents().HostNameAndPort;
        staticAliases =
            getAliasesFromMetadata(
                logger,
                correlationId,
                authorityHost,
                staticAuthorityOptions.cloudDiscoveryMetadata?.metadata,
                AuthorityMetadataSource.CONFIG
            ) ||
            getAliasesFromMetadata(
                logger,
                correlationId,
                authorityHost,
                InstanceDiscoveryMetadata.metadata,
                AuthorityMetadataSource.HARDCODED_VALUES
            ) ||
            staticAuthorityOptions.knownAuthorities;
    }

    return staticAliases || [];
}

/**
 * Returns aliases for from the raw cloud discovery metadata passed in
 * @param authorityHost
 * @param rawCloudDiscoveryMetadata
 * @returns
 */
export function getAliasesFromMetadata(
    logger: Logger,
    correlationId: string,
    authorityHost?: string,
    cloudDiscoveryMetadata?: CloudDiscoveryMetadata[],
    source?: AuthorityMetadataSource
): string[] | null {
    logger.trace(
        `getAliasesFromMetadata called with source: '${source}'`,
        correlationId
    );
    if (authorityHost && cloudDiscoveryMetadata) {
        const metadata = getCloudDiscoveryMetadataFromNetworkResponse(
            cloudDiscoveryMetadata,
            authorityHost
        );

        if (metadata) {
            logger.trace(
                `getAliasesFromMetadata: found cloud discovery metadata in '${source}', returning aliases`,
                correlationId
            );
            return metadata.aliases;
        } else {
            logger.trace(
                `getAliasesFromMetadata: did not find cloud discovery metadata in '${source}'`,
                correlationId
            );
        }
    }

    return null;
}

/**
 * Get cloud discovery metadata for common authorities
 */
export function getCloudDiscoveryMetadataFromHardcodedValues(
    authorityHost: string
): CloudDiscoveryMetadata | null {
    const metadata = getCloudDiscoveryMetadataFromNetworkResponse(
        InstanceDiscoveryMetadata.metadata,
        authorityHost
    );
    return metadata;
}

/**
 * Searches instance discovery network response for the entry that contains the host in the aliases list
 * @param response
 * @param authority
 */
export function getCloudDiscoveryMetadataFromNetworkResponse(
    response: CloudDiscoveryMetadata[],
    authorityHost: string
): CloudDiscoveryMetadata | null {
    for (let i = 0; i < response.length; i++) {
        const metadata = response[i];
        if (metadata.aliases.includes(authorityHost)) {
            return metadata;
        }
    }

    return null;
}
