import { getLogger } from "../utils/logger.js";
import { querySsoProviderById } from "../data-access/runtime/auth.js";
import { APIError } from "better-auth/api";
import { DiscoveryError, discoverOIDCConfig } from "@better-auth/sso";

//#region src/auth/sso-issuer-discovery.ts
const logger = getLogger("sso-issuer-discovery");
const UPDATE_PROVIDER_PATH = "/sso/update-provider";
function normalizeIssuer(issuer) {
	return issuer.endsWith("/") ? issuer.slice(0, -1) : issuer;
}
function sameOrigin(url, issuer) {
	try {
		return new URL(url).origin === new URL(issuer).origin;
	} catch {
		return false;
	}
}
/**
* Re-runs OIDC discovery server-side when an SSO provider's issuer is being
* changed via `/sso/update-provider`, returning a Better Auth context patch
* that overwrites the request body's `oidcConfig` with endpoints freshly
* derived from the new issuer.
*
* Why this exists (do not remove without replacing the behavior):
* `@better-auth/sso`'s update path merges every cached endpoint with
* `updates.X ?? current.X` and never recomputes them from a changed issuer
* (the register path does), so the stored discovery/authorization/token/jwks/
* userinfo endpoints keep pointing at the old IdP and sign-in keeps hitting it.
* The client cannot fix this: the update schema validates each endpoint as
* `z.string().url()` (so empty strings to clear stale values are rejected) and
* a browser fetch of the new issuer's discovery document is subject to CORS.
* Running discovery here — server-side, at the single update chokepoint —
* sidesteps both and corrects the stored config eagerly.
*
* Returns `undefined` (no-op) for any non-update path, a missing
* provider/issuer, an unchanged issuer, or a provider that can't be read.
*
* @throws APIError(BAD_REQUEST) when discovery against the new issuer fails,
* mirroring the register path's refusal to persist an unreachable issuer.
*/
async function maybeRediscoverSsoIssuer(ctx, dbClient) {
	if (ctx.path !== UPDATE_PROVIDER_PATH) return void 0;
	const body = ctx.body;
	const providerId = typeof body?.providerId === "string" ? body.providerId : void 0;
	const newIssuer = typeof body?.issuer === "string" ? body.issuer : void 0;
	if (!providerId || !newIssuer) return void 0;
	let existing;
	try {
		existing = await querySsoProviderById(dbClient)(providerId);
	} catch (err) {
		logger.warn({
			err,
			providerId
		}, "SSO issuer rediscovery: provider lookup failed; skipping");
		return;
	}
	if (!existing || normalizeIssuer(existing.issuer) === normalizeIssuer(newIssuer)) return;
	const isTrustedOrigin = (url) => ctx.context.isTrustedOrigin(url) || sameOrigin(url, newIssuer);
	let hydrated;
	try {
		hydrated = await discoverOIDCConfig({
			issuer: newIssuer,
			isTrustedOrigin
		});
	} catch (error) {
		logger.warn({
			err: error,
			providerId,
			issuer: newIssuer
		}, "SSO issuer rediscovery failed");
		throw new APIError("BAD_REQUEST", { message: `OIDC discovery failed for the new issuer "${newIssuer}"${error instanceof DiscoveryError ? `: ${error.message}` : ""}` });
	}
	return { context: { body: { oidcConfig: {
		discoveryEndpoint: hydrated.discoveryEndpoint,
		authorizationEndpoint: hydrated.authorizationEndpoint,
		tokenEndpoint: hydrated.tokenEndpoint,
		jwksEndpoint: hydrated.jwksEndpoint,
		...hydrated.userInfoEndpoint && { userInfoEndpoint: hydrated.userInfoEndpoint },
		...hydrated.tokenEndpointAuthentication && { tokenEndpointAuthentication: hydrated.tokenEndpointAuthentication }
	} } } };
}

//#endregion
export { maybeRediscoverSsoIssuer };