/**
 * Sanity-check the Entra ID app-registration env vars early in
 * bootstrap so a missing or malformed value fails fast with a clear,
 * actionable message instead of a deep `@azure/msal-node` error
 * thrown on the first auth request.
 *
 * Wired in `main.ts` alongside `assertDatabaseUrl()` — same family
 * of "fail before any request lands" guard recommended by ADR-0018
 * §"BFF env-var loading".
 *
 * The four `*_INSTANCE_URL` / `*_TENANT_ID` / `*_CLIENT_ID` /
 * `*_CLIENT_SECRET` keys are mandatory; the two redirect URIs are
 * mandatory once the OIDC routes ship (next PR). Until then the
 * validator returns the parsed config but does not consume it; the
 * `AuthModule` exposes it via DI so the future MSAL client factory
 * picks it up by constructor injection.
 */

export interface EntraConfig {
  /**
   * Microsoft login endpoint, e.g. `https://login.microsoftonline.com/`.
   * Combined with `tenantId` to build the MSAL `authority`.
   */
  readonly instanceUrl: string;
  /** Directory (tenant) UUID. */
  readonly tenantId: string;
  /** App registration UUID. */
  readonly clientId: string;
  /** Confidential client secret. */
  readonly clientSecret: string;
  /** Where Entra sends the user after authentication — `/api/auth/callback`. */
  readonly redirectUri: string;
  /** Where Entra sends the user after RP-initiated logout. */
  readonly postLogoutRedirectUri: string;
  /**
   * Admin-surface callback URL — `/api/admin/auth/callback`. Distinct
   * from {@link redirectUri} per ADR-0020 §"Sessions — distinct from
   * `portal-shell`" so Entra knows which callback handler (and
   * therefore which session) the auth flow is establishing.
   * Both URIs must be registered on the same Entra app registration.
   */
  readonly adminRedirectUri: string;
  /**
   * Where Entra sends the admin user after RP-initiated logout from
   * the admin surface. Typically the admin SPA's landing page — kept
   * distinct from {@link postLogoutRedirectUri} so an operator can
   * route admin sign-outs to a different page than user sign-outs.
   */
  readonly adminPostLogoutRedirectUri: string;
  /**
   * Convenience: the full authority URL passed to MSAL Node
   * (`${instanceUrl}${tenantId}`). Computed once so the rest of the
   * codebase does not re-derive it on every call.
   */
  readonly authority: string;
}

const REQUIRED_KEYS = [
  'ENTRA_INSTANCE_URL',
  'ENTRA_TENANT_ID',
  'ENTRA_CLIENT_ID',
  'ENTRA_CLIENT_SECRET',
  'ENTRA_REDIRECT_URI',
  'ENTRA_POST_LOGOUT_REDIRECT_URI',
  'ENTRA_ADMIN_REDIRECT_URI',
  'ENTRA_ADMIN_POST_LOGOUT_REDIRECT_URI',
] as const;

const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const PLACEHOLDER_TENANT_OR_CLIENT = '00000000-0000-0000-0000-000000000000';
const PLACEHOLDER_SECRET = 'replace_with_real_value';

export function assertEntraConfig(): EntraConfig {
  const missing = REQUIRED_KEYS.filter((k) => !process.env[k] || process.env[k] === '');
  if (missing.length > 0) {
    throw new Error(
      `Missing Entra config env vars: ${missing.join(', ')}. ` +
        `Copy apps/portal-bff/.env.example to apps/portal-bff/.env ` +
        `and fill in the values from the Entra app registration.`,
    );
  }

  // After the `missing` check, every required key is present.
  const instanceUrl = process.env['ENTRA_INSTANCE_URL'] as string;
  const tenantId = process.env['ENTRA_TENANT_ID'] as string;
  const clientId = process.env['ENTRA_CLIENT_ID'] as string;
  const clientSecret = process.env['ENTRA_CLIENT_SECRET'] as string;
  const redirectUri = process.env['ENTRA_REDIRECT_URI'] as string;
  const postLogoutRedirectUri = process.env['ENTRA_POST_LOGOUT_REDIRECT_URI'] as string;
  const adminRedirectUri = process.env['ENTRA_ADMIN_REDIRECT_URI'] as string;
  const adminPostLogoutRedirectUri = process.env['ENTRA_ADMIN_POST_LOGOUT_REDIRECT_URI'] as string;

  assertUrl('ENTRA_INSTANCE_URL', instanceUrl, ['https:']);
  if (!instanceUrl.endsWith('/')) {
    throw new Error(
      `ENTRA_INSTANCE_URL must end with a trailing "/" so the authority ` +
        `(${instanceUrl}<tenant-id>) concatenates correctly. Got: ${instanceUrl}`,
    );
  }

  assertUuid('ENTRA_TENANT_ID', tenantId);
  assertUuid('ENTRA_CLIENT_ID', clientId);
  assertNotPlaceholder('ENTRA_TENANT_ID', tenantId, PLACEHOLDER_TENANT_OR_CLIENT);
  assertNotPlaceholder('ENTRA_CLIENT_ID', clientId, PLACEHOLDER_TENANT_OR_CLIENT);
  assertNotPlaceholder('ENTRA_CLIENT_SECRET', clientSecret, PLACEHOLDER_SECRET);

  assertUrl('ENTRA_REDIRECT_URI', redirectUri, ['http:', 'https:']);
  assertUrl('ENTRA_POST_LOGOUT_REDIRECT_URI', postLogoutRedirectUri, ['http:', 'https:']);
  assertUrl('ENTRA_ADMIN_REDIRECT_URI', adminRedirectUri, ['http:', 'https:']);
  assertUrl('ENTRA_ADMIN_POST_LOGOUT_REDIRECT_URI', adminPostLogoutRedirectUri, [
    'http:',
    'https:',
  ]);

  // Admin and user redirect URIs must differ — they're the only
  // way Entra (and therefore the BFF) tells the two flows apart.
  // A misconfiguration that points both to the same handler would
  // silently collapse the two surfaces into one session.
  if (adminRedirectUri === redirectUri) {
    throw new Error(
      `ENTRA_ADMIN_REDIRECT_URI must differ from ENTRA_REDIRECT_URI ` +
        `so Entra can route the callback to the matching session ` +
        `(see ADR-0020 §"Sessions — distinct from portal-shell"). ` +
        `Both are: ${redirectUri}`,
    );
  }

  return {
    instanceUrl,
    tenantId,
    clientId,
    clientSecret,
    redirectUri,
    postLogoutRedirectUri,
    adminRedirectUri,
    adminPostLogoutRedirectUri,
    authority: `${instanceUrl}${tenantId}`,
  };
}

function assertUrl(key: string, value: string, allowedProtocols: readonly string[]): void {
  let parsed: URL;
  try {
    parsed = new URL(value);
  } catch {
    throw new Error(`${key} is not a valid URL. Got: ${value}`);
  }
  if (!allowedProtocols.includes(parsed.protocol)) {
    throw new Error(
      `${key} must use one of [${allowedProtocols.join(', ')}]; got "${parsed.protocol}".`,
    );
  }
}

function assertUuid(key: string, value: string): void {
  if (!UUID_RE.test(value)) {
    throw new Error(
      `${key} must be a UUID (e.g. "0123abcd-0123-4567-89ab-cdef01234567"). Got: ${value}`,
    );
  }
}

function assertNotPlaceholder(key: string, value: string, placeholder: string): void {
  if (value === placeholder) {
    throw new Error(
      `${key} is still set to the .env.example placeholder ("${placeholder}"). ` +
        `Replace with the real value from the Entra app registration.`,
    );
  }
}
