const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.js');
const require_logger_index = require('../../logger/index.js');
const require_common_app = require('../app.js');
const require_common_debug = require('../debug.js');
const require_common_providers_https = require('./https.js');
let firebase_admin_auth = require("firebase-admin/auth");
firebase_admin_auth = require_rolldown_runtime.__toESM(firebase_admin_auth);

//#region src/common/providers/identity.ts
const DISALLOWED_CUSTOM_CLAIMS = [
	"acr",
	"amr",
	"at_hash",
	"aud",
	"auth_time",
	"azp",
	"cnf",
	"c_hash",
	"exp",
	"iat",
	"iss",
	"jti",
	"nbf",
	"nonce",
	"firebase"
];
const CLAIMS_MAX_PAYLOAD_SIZE = 1e3;
const EVENT_MAPPING = {
	beforeCreate: "providers/cloud.auth/eventTypes/user.beforeCreate",
	beforeSignIn: "providers/cloud.auth/eventTypes/user.beforeSignIn",
	beforeSendEmail: "providers/cloud.auth/eventTypes/user.beforeSendEmail",
	beforeSendSms: "providers/cloud.auth/eventTypes/user.beforeSendSms"
};
/**
* Helper class to create the user metadata in a `UserRecord` object.
*/
var UserRecordMetadata = class {
	constructor(creationTime, lastSignInTime) {
		this.creationTime = creationTime;
		this.lastSignInTime = lastSignInTime;
	}
	/** Returns a plain JavaScript object with the properties of UserRecordMetadata. */
	toJSON() {
		return {
			creationTime: this.creationTime,
			lastSignInTime: this.lastSignInTime
		};
	}
};
/**
* Helper function that creates a `UserRecord` class from data sent over the wire.
* @param wireData data sent over the wire
* @returns an instance of `UserRecord` with correct toJSON functions
*/
function userRecordConstructor(wireData) {
	const falseyValues = {
		email: null,
		emailVerified: false,
		displayName: null,
		photoURL: null,
		phoneNumber: null,
		disabled: false,
		providerData: [],
		customClaims: {},
		passwordSalt: null,
		passwordHash: null,
		tokensValidAfterTime: null
	};
	const record = {
		...falseyValues,
		...wireData
	};
	const meta = record.metadata;
	if (meta) {
		record.metadata = new UserRecordMetadata(meta.createdAt || meta.creationTime, meta.lastSignedInAt || meta.lastSignInTime);
	} else {
		record.metadata = new UserRecordMetadata(null, null);
	}
	record.toJSON = () => {
		const { uid, email, emailVerified, displayName, photoURL, phoneNumber, disabled, passwordHash, passwordSalt, tokensValidAfterTime } = record;
		const json = {
			uid,
			email,
			emailVerified,
			displayName,
			photoURL,
			phoneNumber,
			disabled,
			passwordHash,
			passwordSalt,
			tokensValidAfterTime
		};
		json.metadata = record.metadata.toJSON();
		json.customClaims = JSON.parse(JSON.stringify(record.customClaims));
		json.providerData = record.providerData.map((entry) => {
			const newEntry = { ...entry };
			newEntry.toJSON = () => entry;
			return newEntry;
		});
		return json;
	};
	return record;
}
/**
* Checks for a valid identity platform web request, otherwise throws an HttpsError.
* @internal
*/
function isValidRequest(req) {
	if (req.method !== "POST") {
		require_logger_index.warn(`Request has invalid method "${req.method}".`);
		return false;
	}
	const contentType = (req.header("Content-Type") || "").toLowerCase();
	if (!contentType.includes("application/json")) {
		require_logger_index.warn("Request has invalid header Content-Type.");
		return false;
	}
	if (!req.body?.data?.jwt) {
		require_logger_index.warn("Request has an invalid body.");
		return false;
	}
	return true;
}
/**
* Decode, but not verify, an Auth Blocking token.
*
* Do not use in production. Token should always be verified using the Admin SDK.
*
* This is exposed only for testing.
*/
function unsafeDecodeAuthBlockingToken(token) {
	const decoded = require_common_providers_https.unsafeDecodeToken(token);
	decoded.uid = decoded.sub;
	return decoded;
}
/**
* Helper function to parse the decoded metadata object into a `UserMetaData` object
* @internal
*/
function parseMetadata(metadata) {
	const creationTime = metadata?.creation_time ? new Date(metadata.creation_time).toUTCString() : null;
	const lastSignInTime = metadata?.last_sign_in_time ? new Date(metadata.last_sign_in_time).toUTCString() : null;
	return {
		creationTime,
		lastSignInTime
	};
}
/**
* Helper function to parse the decoded user info array into an `AuthUserInfo` array.
* @internal
*/
function parseProviderData(providerData) {
	const providers = [];
	for (const provider of providerData) {
		providers.push({
			uid: provider.uid,
			displayName: provider.display_name,
			email: provider.email,
			photoURL: provider.photo_url,
			providerId: provider.provider_id,
			phoneNumber: provider.phone_number
		});
	}
	return providers;
}
/**
* Helper function to parse the date into a UTC string.
* @internal
*/
function parseDate(tokensValidAfterTime) {
	if (!tokensValidAfterTime) {
		return null;
	}
	tokensValidAfterTime = tokensValidAfterTime * 1e3;
	try {
		const date = new Date(tokensValidAfterTime);
		if (!isNaN(date.getTime())) {
			return date.toUTCString();
		}
	} catch {}
	return null;
}
/**
* Helper function to parse the decoded enrolled factors into a valid MultiFactorSettings
* @internal
*/
function parseMultiFactor(multiFactor) {
	if (!multiFactor) {
		return null;
	}
	const parsedEnrolledFactors = [];
	for (const factor of multiFactor.enrolled_factors || []) {
		if (!factor.uid) {
			throw new require_common_providers_https.HttpsError("internal", "INTERNAL ASSERT FAILED: Invalid multi-factor info response");
		}
		const enrollmentTime = factor.enrollment_time ? new Date(factor.enrollment_time).toUTCString() : null;
		parsedEnrolledFactors.push({
			uid: factor.uid,
			factorId: factor.phone_number ? factor.factor_id || "phone" : factor.factor_id,
			displayName: factor.display_name,
			enrollmentTime,
			phoneNumber: factor.phone_number
		});
	}
	if (parsedEnrolledFactors.length > 0) {
		return { enrolledFactors: parsedEnrolledFactors };
	}
	return null;
}
/**
* Parses the decoded user record into a valid UserRecord for use in the handler
* @internal
*/
function parseAuthUserRecord(decodedJWTUserRecord) {
	if (!decodedJWTUserRecord.uid) {
		throw new require_common_providers_https.HttpsError("internal", "INTERNAL ASSERT FAILED: Invalid user response");
	}
	const disabled = decodedJWTUserRecord.disabled || false;
	const metadata = parseMetadata(decodedJWTUserRecord.metadata);
	const providerData = parseProviderData(decodedJWTUserRecord.provider_data);
	const tokensValidAfterTime = parseDate(decodedJWTUserRecord.tokens_valid_after_time);
	const multiFactor = parseMultiFactor(decodedJWTUserRecord.multi_factor);
	return {
		uid: decodedJWTUserRecord.uid,
		email: decodedJWTUserRecord.email,
		emailVerified: decodedJWTUserRecord.email_verified,
		displayName: decodedJWTUserRecord.display_name,
		photoURL: decodedJWTUserRecord.photo_url,
		phoneNumber: decodedJWTUserRecord.phone_number,
		disabled,
		metadata,
		providerData,
		passwordHash: decodedJWTUserRecord.password_hash,
		passwordSalt: decodedJWTUserRecord.password_salt,
		customClaims: decodedJWTUserRecord.custom_claims,
		tenantId: decodedJWTUserRecord.tenant_id,
		tokensValidAfterTime,
		multiFactor
	};
}
/** Helper to get the `AdditionalUserInfo` from the decoded JWT */
function parseAdditionalUserInfo(decodedJWT) {
	let profile;
	let username;
	if (decodedJWT.raw_user_info) {
		try {
			profile = JSON.parse(decodedJWT.raw_user_info);
		} catch (err) {
			require_logger_index.debug(`Parse Error: ${err.message}`);
		}
	}
	if (profile) {
		if (decodedJWT.sign_in_method === "github.com") {
			username = profile.login;
		}
		if (decodedJWT.sign_in_method === "twitter.com") {
			username = profile.screen_name;
		}
	}
	return {
		providerId: decodedJWT.sign_in_method === "emailLink" ? "password" : decodedJWT.sign_in_method,
		profile,
		username,
		isNewUser: decodedJWT.event_type === "beforeCreate" ? true : false,
		recaptchaScore: decodedJWT.recaptcha_score,
		email: decodedJWT.email,
		phoneNumber: decodedJWT.phone_number
	};
}
/**
* Helper to generate a response from the blocking function to the Firebase Auth backend.
* @internal
*/
function generateResponsePayload(authResponse) {
	if (!authResponse) {
		return {};
	}
	const { recaptchaActionOverride,...formattedAuthResponse } = authResponse;
	const result = {};
	const updateMask = getUpdateMask(formattedAuthResponse);
	if (updateMask.length !== 0) {
		result.userRecord = {
			...formattedAuthResponse,
			updateMask
		};
	}
	if (recaptchaActionOverride !== undefined) {
		result.recaptchaActionOverride = recaptchaActionOverride;
	}
	return result;
}
/** Helper to get the Credential from the decoded JWT */
function parseAuthCredential(decodedJWT, time) {
	if (!decodedJWT.sign_in_attributes && !decodedJWT.oauth_id_token && !decodedJWT.oauth_access_token && !decodedJWT.oauth_refresh_token) {
		return null;
	}
	return {
		claims: decodedJWT.sign_in_attributes,
		idToken: decodedJWT.oauth_id_token,
		accessToken: decodedJWT.oauth_access_token,
		refreshToken: decodedJWT.oauth_refresh_token,
		expirationTime: decodedJWT.oauth_expires_in ? new Date(time + decodedJWT.oauth_expires_in * 1e3).toUTCString() : undefined,
		secret: decodedJWT.oauth_token_secret,
		providerId: decodedJWT.sign_in_method === "emailLink" ? "password" : decodedJWT.sign_in_method,
		signInMethod: decodedJWT.sign_in_method
	};
}
/**
* Parses the decoded jwt into a valid AuthEventContext for use in the handler
* @internal
*/
function parseAuthEventContext(decodedJWT, projectId, time = new Date().getTime()) {
	const eventType = (EVENT_MAPPING[decodedJWT.event_type] || decodedJWT.event_type) + (decodedJWT.sign_in_method ? `:${decodedJWT.sign_in_method}` : "");
	return {
		locale: decodedJWT.locale,
		ipAddress: decodedJWT.ip_address,
		userAgent: decodedJWT.user_agent,
		eventId: decodedJWT.event_id,
		eventType,
		authType: decodedJWT.user_record ? "USER" : "UNAUTHENTICATED",
		resource: {
			service: "identitytoolkit.googleapis.com",
			name: decodedJWT.tenant_id ? `projects/${projectId}/tenants/${decodedJWT.tenant_id}` : `projects/${projectId}`
		},
		timestamp: new Date(decodedJWT.iat * 1e3).toUTCString(),
		additionalUserInfo: parseAdditionalUserInfo(decodedJWT),
		credential: parseAuthCredential(decodedJWT, time),
		emailType: decodedJWT.email_type,
		smsType: decodedJWT.sms_type,
		params: {}
	};
}
/**
* Checks the handler response for invalid customClaims & sessionClaims objects
* @internal
*/
function validateAuthResponse(eventType, authRequest) {
	if (!authRequest) {
		authRequest = {};
	}
	if (authRequest.customClaims) {
		const invalidClaims = DISALLOWED_CUSTOM_CLAIMS.filter((claim) => authRequest.customClaims.hasOwnProperty(claim));
		if (invalidClaims.length > 0) {
			throw new require_common_providers_https.HttpsError("invalid-argument", `The customClaims claims "${invalidClaims.join(",")}" are reserved and cannot be specified.`);
		}
		if (JSON.stringify(authRequest.customClaims).length > CLAIMS_MAX_PAYLOAD_SIZE) {
			throw new require_common_providers_https.HttpsError("invalid-argument", `The customClaims payload should not exceed ${CLAIMS_MAX_PAYLOAD_SIZE} characters.`);
		}
	}
	if (eventType === "beforeSignIn" && authRequest.sessionClaims) {
		const invalidClaims = DISALLOWED_CUSTOM_CLAIMS.filter((claim) => authRequest.sessionClaims.hasOwnProperty(claim));
		if (invalidClaims.length > 0) {
			throw new require_common_providers_https.HttpsError("invalid-argument", `The sessionClaims claims "${invalidClaims.join(",")}" are reserved and cannot be specified.`);
		}
		if (JSON.stringify(authRequest.sessionClaims).length > CLAIMS_MAX_PAYLOAD_SIZE) {
			throw new require_common_providers_https.HttpsError("invalid-argument", `The sessionClaims payload should not exceed ${CLAIMS_MAX_PAYLOAD_SIZE} characters.`);
		}
		const combinedClaims = {
			...authRequest.customClaims,
			...authRequest.sessionClaims
		};
		if (JSON.stringify(combinedClaims).length > CLAIMS_MAX_PAYLOAD_SIZE) {
			throw new require_common_providers_https.HttpsError("invalid-argument", `The customClaims and sessionClaims payloads should not exceed ${CLAIMS_MAX_PAYLOAD_SIZE} characters combined.`);
		}
	}
}
/**
* Helper function to generate the update mask for the identity platform changed values
* @internal
*/
function getUpdateMask(authResponse) {
	if (!authResponse) {
		return "";
	}
	const updateMask = [];
	for (const key in authResponse) {
		if (authResponse.hasOwnProperty(key) && typeof authResponse[key] !== "undefined") {
			updateMask.push(key);
		}
	}
	return updateMask.join(",");
}
/** @internal */
function wrapHandler(eventType, handler) {
	return async (req, res) => {
		try {
			const projectId = process.env.GCLOUD_PROJECT;
			if (!isValidRequest(req)) {
				require_logger_index.error("Invalid request, unable to process");
				throw new require_common_providers_https.HttpsError("invalid-argument", "Bad Request");
			}
			if (!firebase_admin_auth.getAuth(require_common_app.getApp())._verifyAuthBlockingToken) {
				throw new Error("Cannot validate Auth Blocking token. Please update Firebase Admin SDK to >= v10.1.0");
			}
			let decodedPayload;
			if (require_common_debug.isDebugFeatureEnabled("skipTokenVerification")) {
				decodedPayload = unsafeDecodeAuthBlockingToken(req.body.data.jwt);
			} else {
				const unverified = unsafeDecodeAuthBlockingToken(req.body.data.jwt);
				if (handler.platform === "gcfv2" && unverified.aud && unverified.aud.includes("run.app")) {
					decodedPayload = await firebase_admin_auth.getAuth(require_common_app.getApp())._verifyAuthBlockingToken(req.body.data.jwt, "run.app");
				} else {
					decodedPayload = await firebase_admin_auth.getAuth(require_common_app.getApp())._verifyAuthBlockingToken(req.body.data.jwt);
				}
			}
			let authUserRecord;
			if (decodedPayload.event_type === "beforeCreate" || decodedPayload.event_type === "beforeSignIn") {
				authUserRecord = parseAuthUserRecord(decodedPayload.user_record);
			}
			const authEventContext = parseAuthEventContext(decodedPayload, projectId);
			let authResponse;
			if (handler.platform === "gcfv1") {
				authResponse = authUserRecord ? await handler(authUserRecord, authEventContext) || undefined : await handler(authEventContext) || undefined;
			} else {
				authResponse = await handler({
					...authEventContext,
					data: authUserRecord
				}) || undefined;
			}
			validateAuthResponse(eventType, authResponse);
			const result = generateResponsePayload(authResponse);
			res.status(200);
			res.setHeader("Content-Type", "application/json");
			res.send(JSON.stringify(result));
		} catch (err) {
			let httpErr = err;
			if (!(httpErr instanceof require_common_providers_https.HttpsError)) {
				require_logger_index.error("Unhandled error", err);
				httpErr = new require_common_providers_https.HttpsError("internal", "An unexpected error occurred.");
			}
			const { status } = httpErr.httpErrorCode;
			const body = { error: httpErr.toJSON() };
			res.setHeader("Content-Type", "application/json");
			res.status(status).send(body);
		}
	};
}

//#endregion
exports.HttpsError = require_common_providers_https.HttpsError;
exports.UserRecordMetadata = UserRecordMetadata;
exports.generateResponsePayload = generateResponsePayload;
exports.getUpdateMask = getUpdateMask;
exports.isValidRequest = isValidRequest;
exports.parseAuthEventContext = parseAuthEventContext;
exports.parseAuthUserRecord = parseAuthUserRecord;
exports.parseDate = parseDate;
exports.parseMetadata = parseMetadata;
exports.parseMultiFactor = parseMultiFactor;
exports.parseProviderData = parseProviderData;
exports.userRecordConstructor = userRecordConstructor;
exports.validateAuthResponse = validateAuthResponse;
exports.wrapHandler = wrapHandler;