import { AuthenticationResult } from '@azure/msal-node';
import jwt from 'jsonwebtoken';

type HttpErrorCodes = 400 | 401 | 403 | 500;
interface ResultErr {
    readonly message: string;
    readonly description: string;
    readonly statusCode: HttpErrorCodes;
}
type Result<T, E = ResultErr> = T extends object ? ({
    readonly [K in keyof T]: T[K];
} & {
    readonly success: true;
    readonly error?: undefined;
}) | ({
    readonly [K in keyof T]?: undefined;
} & {
    readonly success: false;
    readonly error: E;
}) : {
    readonly success: true;
    readonly result: T;
    readonly error?: undefined;
} | {
    readonly success: false;
    readonly error: E;
    readonly result?: undefined;
};
/**
 * Custom error class for handling OAuth-related errors.
 *
 * This error is designed for use in OAuth authentication flows,
 * providing an HTTP status code, a message, and an optional description.
 *
 * @extends {Error}
 */
declare class OAuthError extends Error {
    readonly statusCode: HttpErrorCodes;
    readonly description: string;
    constructor(err: ResultErr);
    constructor(err: Result<never, ResultErr>);
    constructor(err: {
        msg: string;
        desc: string;
        status?: HttpErrorCodes;
    });
}

declare const ACCESS_TOKEN_NAME: "at";
declare const REFRESH_TOKEN_NAME: "rt";

type LoginPrompt = 'email' | 'select-account' | 'sso';
type TimeUnit = 'ms' | 'sec';
type CryptoType = 'node' | 'web-api';
type JwtPayload = jwt.JwtPayload;
type LooseString<T extends string> = T | (string & {});
type NonEmptyArray<T> = [T, ...T[]];
type OneOrMore<T> = T | [T, ...T[]];
type BaseWithExtended<TBase extends object, TExtended extends object> = {
    [KBase in keyof TBase]: TBase[KBase];
} | ({
    [KBase in keyof TBase]: TBase[KBase];
} & {
    [KExtended in keyof TExtended]: TExtended[KExtended];
});
/**
 * Configuration object for initializing the OAuthProvider.
 */
interface OAuthConfig {
    azure: OneOrMore<{
        clientId: string;
        tenantId: LooseString<'common'>;
        scopes: NonEmptyArray<string>;
        clientSecret: string;
        downstreamServices?: NonEmptyArray<{
            serviceName: string;
            scope: string;
            serviceUrl: OneOrMore<string>;
            encryptionKey: string;
            cryptoType?: CryptoType;
            accessTokenExpiry?: number;
        }>;
        b2bApps?: NonEmptyArray<{
            appName: string;
            scope: string;
        }>;
    }>;
    frontendUrl: OneOrMore<string>;
    serverCallbackUrl: string;
    encryptionKey: string;
    advanced?: {
        loginPrompt?: LoginPrompt;
        acceptB2BRequests?: boolean;
        cryptoType?: CryptoType;
        disableCompression?: boolean;
        cookies?: {
            timeUnit?: TimeUnit;
            disableSecure?: boolean;
            disableSameSite?: boolean;
            accessTokenExpiry?: number;
            refreshTokenExpiry?: number;
        };
    };
}
type LiteConfig = BaseWithExtended<{
    clientId: string;
    tenantId: LooseString<'common'>;
}, {
    clientSecret: string;
    b2bApps: NonEmptyArray<{
        appName: string;
        scope: string;
    }>;
}>;
/** Parsed and resolved configuration used internally by the OAuthProvider */
interface OAuthSettings {
    readonly loginPrompt: LoginPrompt;
    readonly acceptB2BRequests: boolean;
    readonly b2bApps: NonEmptyArray<{
        azureId: string;
        names: NonEmptyArray<string>;
    }> | undefined;
    readonly downstreamServices: NonEmptyArray<{
        azureId: string;
        names: NonEmptyArray<string>;
    }> | undefined;
    readonly disableCompression: boolean;
    readonly cryptoType: CryptoType;
    readonly azures: NonEmptyArray<{
        azureId: string;
        tenantId: string;
    }>;
    readonly cookies: {
        readonly timeUnit: TimeUnit;
        readonly isSecure: boolean;
        readonly isSameSite: boolean;
        readonly accessTokenExpiry: number;
        readonly accessTokenName: AccessTokenName;
        readonly refreshTokenExpiry: number;
        readonly refreshTokenName: RefreshTokenName;
        readonly cookieNames: NonEmptyArray<{
            azureId: string;
            accessTokenName: AccessTokenName;
            refreshTokenName: RefreshTokenName;
        }>;
        readonly deleteOptions: CookieOptions;
    };
}
type MsalResponse = AuthenticationResult;
type AccessTokenName = `${typeof ACCESS_TOKEN_NAME}-${string}` | `__Host-${typeof ACCESS_TOKEN_NAME}-${string}`;
type RefreshTokenName = `${typeof REFRESH_TOKEN_NAME}-${string}` | `__Host-${typeof REFRESH_TOKEN_NAME}-${string}`;
interface CookieOptions {
    readonly maxAge: number;
    readonly httpOnly: true;
    readonly secure: boolean;
    readonly path: '/';
    readonly sameSite: 'strict' | 'none' | undefined;
}
interface Cookies {
    AccessToken: {
        readonly name: AccessTokenName;
        readonly value: string;
        readonly options: CookieOptions;
    };
    RefreshToken: {
        readonly name: RefreshTokenName;
        readonly value: string;
        readonly options: CookieOptions;
    };
    DeleteAccessToken: {
        readonly name: AccessTokenName;
        readonly value: string;
        readonly options: CookieOptions;
    };
    DeleteRefreshToken: {
        readonly name: RefreshTokenName;
        readonly value: string;
        readonly options: CookieOptions;
    };
}
interface B2BResult {
    appName: string;
    appId: string;
    clientId: string;
    token: string;
    isCached: boolean;
    msalResponse: MsalResponse;
    expiresAt: number;
}
interface OboResult {
    serviceName: string;
    serviceId: string;
    clientId: string;
    accessToken: Cookies['AccessToken'];
    msalResponse: MsalResponse;
}
type Metadata = {
    audience: string | undefined;
    issuer: string | undefined;
    subject: string | undefined;
    issuedAt: number | undefined;
    expiration: number | undefined;
    uniqueId: string | undefined;
    azureId: string | undefined;
    tenantId: string | undefined;
    roles: string[] | undefined;
    uniqueTokenId: string | undefined;
} & ({
    isApp: true;
    appId: string | undefined;
    name?: undefined;
    email?: undefined;
} | {
    isApp: false;
    appId?: undefined;
    name: string | undefined;
    email: string | undefined;
});

/**
 * Core OAuth2/PKCE provider for Microsoft Entra ID (Azure AD).
 *
 * Responsibilities:
 *  - PKCE authorization URL generation
 *  - Authorization‐code and refresh‐token exchanges
 *  - Secure encryption/decryption of state & cookies
 *  - JWT validation via JWKS
 *  - B2B client‐credentials flow
 *  - On‐Behalf-Of (OBO) flow for downstream services
 *
 * Designed to be framework-agnostic (Express, NestJS, etc.)
 */
declare class OAuthProvider {
    private readonly azures;
    private readonly frontendUrls;
    private readonly frontendWhitelist;
    private readonly serverCallbackUrl;
    private readonly baseCookieOptions;
    private readonly encryptionKeys;
    private readonly msalCryptoProvider;
    private readonly jwksClient;
    readonly settings: OAuthSettings;
    /**
     * @param configuration The OAuth configuration object:
     * - `azure`: clientId, tenantId, scopes, clientSecret, B2B apps, and downstream services. Can be an array of Azure configurations.
     * - `frontendUrl`: allowed redirect URIs
     * - `serverCallbackUrl`: your server’s Azure callback endpoint
     * - `encryptionKey`: 32 characters base encryption secret
     * - `advanced`: optional behaviors
     * @throws {OAuthError} if the config fails validation or has duplicate service names
     */
    constructor(configuration: OAuthConfig);
    /**
     * Generate an OAuth2 authorization URL for user login (PKCE-backed).
     *
     * @param params (optional) - Parameters to customize the auth URL:
     * - `loginPrompt` (optional) - Override the default prompt (`sso`|`email`|`select-account`)
     * - `email` (optional) - Email address to pre-fill the login form
     * - `frontendUrl` (optional) - Frontend URL override to redirect the user after authentication
     * - `azureId` (optional) - Azure configuration ID to use, relevant if multiple Azure configurations (Defaults to the first one)
     * @returns A result containing the authorization URL and a ticket (which is used for bearer flow only)
     * @throws {OAuthError} if something goes wrong.
     */
    getAuthUrl(params?: {
        loginPrompt?: LoginPrompt;
        email?: string;
        frontendUrl?: string;
        azureId?: string;
    }): Promise<{
        authUrl: string;
        ticket: string;
    }>;
    /**
     * Exchange an authorization code for encrypted tokens and metadata.
     *
     * @param params - The parameters containing the authorization code and state.
     * - `code` - The authorization code received from the OAuth flow.
     * - `state` -  The state parameter received from Microsoft.
     * @returns A result containing the access token, refresh token (if available), frontend URL, and MSAL response.
     * @throws {OAuthError} if something goes wrong.
     */
    getTokenByCode(params: {
        code: string;
        state: string;
    }): Promise<{
        accessToken: Cookies['AccessToken'];
        refreshToken: Cookies['RefreshToken'] | null;
        frontendUrl: string;
        ticketId: string;
        msalResponse: MsalResponse;
    }>;
    /**
     * Build a logout URL and cookie-deletion instructions.
     *
     * @param params (optional) - Parameters to customize the logout URL:
     * - `frontendUrl` (optional) - Frontend URL override to redirect the user after log out
     * - `azureId` (optional) - Azure configuration ID to use, relevant if multiple Azure configurations (Defaults to the first one)
     * @returns A result containing the logout URL and cookie deletion instructions.
     * @throws {OAuthError} if something goes wrong.
     */
    getLogoutUrl(params?: {
        frontendUrl?: string;
        azureId?: string;
    }): Promise<{
        logoutUrl: string;
        deleteAccessToken: Cookies['DeleteAccessToken'];
        deleteRefreshToken: Cookies['DeleteRefreshToken'];
    }>;
    /**
     * Verify the access token (either encrypted or in JWT format) and extract its payload.
     * Make sure that user access tokens are encrypted and app tokens aren't
     *
     * @param accessToken - The access token string either encrypted or in JWT format
     * @returns A result containing the raw access token, its payload, any injected data, and whether it is an app token.
     * @template T - Type of any injected data in the encrypted token
     */
    verifyAccessToken<T extends object = Record<string, any>>(accessToken: string | undefined): Promise<Result<{
        payload: JwtPayload;
        meta: Metadata;
        rawJwt: string;
        injectedData: T | undefined;
        hasInjectedData: boolean;
    }>>;
    /**
     * Verifies and uses the refresh token to get new set of access and refresh tokens.
     *
     * @param refreshToken - Encrypted refresh-token value
     * @returns A result containing the new access token, optional new refresh token, the raw access token, its payload, and the MSAL response.
     */
    tryRefreshTokens(refreshToken: string | undefined): Promise<Result<{
        newAccessToken: Cookies['AccessToken'];
        newRefreshToken: Cookies['RefreshToken'] | null;
        payload: JwtPayload;
        meta: Metadata;
        rawJwt: string;
        msalResponse: MsalResponse;
    }>>;
    /**
     * Inject non-sensitive metadata into the access token.
     *
     * @param params - The parameters containing the access token and data to inject.
     * - `accessToken` - The encrypted access token to inject data into.
     * - `data` - The data to inject into the access token.
     * @returns A result containing the new encrypted access token with injected data and the injected data.
     * @template T - Type of the data to inject into the access token.
     */
    tryInjectData<T extends object = Record<string, any>>(params: {
        accessToken: string;
        data: T;
    }): Promise<Result<{
        newAccessToken: Cookies['AccessToken'];
        injectedData: T;
    }>>;
    /**
     * Decrypts a ticket and returns the ticket ID.
     * Useful for bearer flow.
     *
     * @param ticket - The encrypted ticket string to decrypt (generated by getAuthUrl).
     * @returns A result containing the ticket ID (returned by getTokenByCode).
     */
    tryDecryptTicket(ticket: string): Promise<Result<{
        ticketId: string;
    }>>;
    /**
     * Acquire client-credential tokens for one or multiple B2B apps.
     * Caches tokens for better performance.
     *
     * @overload
     * @param params.appName - The name of the B2B app to get the token for.
     * @param params.azureId (optional) - Azure configuration ID to use, relevant if multiple Azure configurations (Defaults to the first one)
     * @returns A result containing the B2B app token and metadata.
     *
     * @overload
     * @param params.appsNames - An array of B2B app names to get tokens for.
     * @param params.azureId (optional) - Azure configuration ID to use, relevant if multiple Azure configurations (Defaults to the first one)
     * @returns Results containing an array of B2B app tokens and metadata.
     */
    tryGetB2BToken(params: {
        app: string;
        azureId?: string;
    }): Promise<Result<{
        result: B2BResult;
    }>>;
    tryGetB2BToken(params: {
        apps: string[];
        azureId?: string;
    }): Promise<Result<{
        results: NonEmptyArray<B2BResult>;
    }>>;
    /**
     * Acquire On-Behalf-Of tokens for downstream services.
     *
     * @overload
     * @param params.accessToken - The encrypted access token to use for OBO.
     * @param params.serviceName - The name of the service to get the token for.
     * @param params.azureId (optional) - Azure configuration ID to use, relevant if multiple Azure configurations (Defaults to the first one)
     * @returns A result containing the OBO token and metadata for the specified service.
     * @throws {OAuthError} if something goes wrong.
     *
     * @overload
     * @param params.accessToken - The encrypted access token to use for OBO.
     * @param params.serviceNames - An array of service names to get tokens for.
     * @param params.azureId (optional) - Azure configuration ID to use, relevant if multiple Azure configurations (Defaults to the first one)
     * @returns Results containing an array of OBO tokens and metadata for the specified services.
     * @throws {OAuthError} if something goes wrong.
     */
    getTokenOnBehalfOf(params: {
        accessToken: string;
        service: string;
        azureId?: string;
    }): Promise<{
        result: OboResult;
    }>;
    getTokenOnBehalfOf(params: {
        accessToken: string;
        services: string[];
        azureId?: string;
    }): Promise<{
        results: NonEmptyArray<OboResult>;
    }>;
    private $getAzure;
    /** Extracts and encrypts both tokens */
    private $extractTokens;
    /** Extracts the refresh token from the cache that msal created, and removes the account from the cache. */
    private $obtainRefreshToken;
    /** Updates the secret key for a specific token type if it is a string. */
    private $updateSecretKey;
    private $encryptToken;
    private $decryptToken;
}

/** Supported server frameworks for binding the OAuthProvider */
type ServerType = 'express' | 'nestjs';
/**
 * Represents either an end-user or a service principal.
 *
 * @template T  Type of any injected metadata for a user.
 */
type UserInfo<T extends object = Record<string, any>> = {
    readonly azureId: string;
    readonly tenantId: string;
    readonly uniqueId: string;
    readonly roles: string[];
} & ({
    readonly isApp: false;
    readonly name: string;
    readonly email: string;
    readonly injectedData?: T;
    readonly appId?: undefined;
} | {
    readonly isApp: true;
    readonly appId: string;
    readonly name?: undefined;
    readonly email?: undefined;
    readonly injectedData?: undefined;
});
/**
 * Adds metadata into an existing access token.
 *
 * @template T  Shape of the object to inject.
 * @param data  Arbitrary JSON to embed.
 * @returns A `Result<{ injectedData: T }>` containing the injected data.
 */
type InjectDataFunction<T extends object = Record<string, any>> = (data: T) => Promise<Result<{
    injectedData: T;
}>>;
/**
 * Optional callback invoked once a request is authenticated.
 *
 * @param params.userInfo - Information about the authenticated user or service principal.
 * @param params.tryInjectData - Function to inject additional data into the access token.
 */
type CallbackFunction = (() => Promise<void> | void) | ((params: {
    userInfo: UserInfo;
    tryInjectData: InjectDataFunction;
}) => Promise<void> | void);
declare global {
    namespace Express {
        interface Request {
            /** Bound OAuthProvider instance. */
            oauthProvider: OAuthProvider;
            /** Which server adapter is in use. */
            serverType: ServerType;
            /** Raw JWT and decoded payload, if present. */
            accessTokenInfo?: {
                readonly jwt: string;
                readonly payload: JwtPayload;
                readonly meta: Metadata;
            };
            /** Information about the authenticated user or service principal. */
            userInfo?: UserInfo;
        }
    }
}

export { type B2BResult as B, type CallbackFunction as C, type HttpErrorCodes as H, type JwtPayload as J, type LiteConfig as L, type Metadata as M, OAuthError as O, type Result as R, type UserInfo as U, type ResultErr as a, type MsalResponse as b, type OAuthConfig as c, OAuthProvider as d };
