const cookies = require('cookie');
const { isEnabled, clearCloudFrontCookies } = require('@librechat/api');
const { logger } = require('@librechat/data-schemas');
const { logoutUser } = require('~/server/services/AuthService');
const { getOpenIdConfig } = require('~/strategies');

/** Parses and validates OPENID_MAX_LOGOUT_URL_LENGTH, returning defaultValue on invalid input */
function parseMaxLogoutUrlLength(defaultValue = 2000) {
  const raw = process.env.OPENID_MAX_LOGOUT_URL_LENGTH;
  const trimmed = raw == null ? '' : raw.trim();
  if (trimmed === '') {
    return defaultValue;
  }
  const parsed = /^\d+$/.test(trimmed) ? Number(trimmed) : NaN;
  if (!Number.isFinite(parsed) || parsed <= 0) {
    logger.warn(
      `[logoutController] Invalid OPENID_MAX_LOGOUT_URL_LENGTH value "${raw}", using default ${defaultValue}`,
    );
    return defaultValue;
  }
  return parsed;
}

const logoutController = async (req, res) => {
  const parsedCookies = req.headers.cookie ? cookies.parse(req.headers.cookie) : {};
  const isOpenIdUser = req.user?.openidId != null && req.user?.provider === 'openid';

  let refreshToken;
  let idToken;
  if (isOpenIdUser && req.session?.openidTokens) {
    refreshToken = req.session.openidTokens.refreshToken;
    idToken = req.session.openidTokens.idToken;
    delete req.session.openidTokens;
  }
  refreshToken = refreshToken || parsedCookies.refreshToken;
  idToken = idToken || parsedCookies.openid_id_token;

  try {
    const logout = await logoutUser(req, refreshToken);
    const { status, message } = logout;

    res.clearCookie('refreshToken');
    res.clearCookie('openid_access_token');
    res.clearCookie('openid_id_token');
    res.clearCookie('openid_user_id');
    res.clearCookie('token_provider');
    clearCloudFrontCookies(res, {
      userId: req.user?.id ?? req.user?._id?.toString?.(),
      tenantId: req.user?.tenantId,
    });
    const response = { message };
    if (
      isOpenIdUser &&
      isEnabled(process.env.OPENID_USE_END_SESSION_ENDPOINT) &&
      process.env.OPENID_ISSUER
    ) {
      let openIdConfig;
      try {
        openIdConfig = getOpenIdConfig();
      } catch (err) {
        logger.warn('[logoutController] OpenID config not available:', err.message);
      }
      if (openIdConfig) {
        const endSessionEndpoint = openIdConfig.serverMetadata().end_session_endpoint;
        if (endSessionEndpoint) {
          const endSessionUrl = new URL(endSessionEndpoint);
          const postLogoutRedirectUri =
            process.env.OPENID_POST_LOGOUT_REDIRECT_URI || `${process.env.DOMAIN_CLIENT}/login`;
          endSessionUrl.searchParams.set('post_logout_redirect_uri', postLogoutRedirectUri);

          /**
           * OIDC RP-Initiated Logout cascading strategy:
           * 1. id_token_hint (most secure, identifies exact session)
           * 2. logout_hint + client_id (when URL would exceed safe length)
           * 3. client_id only (when no token available)
           *
           * JWT tokens from spec-compliant OIDC providers use base64url
           * encoding (RFC 7515), whose characters are all URL-safe, so
           * token length equals URL-encoded length for projection.
           * Non-compliant issuers using standard base64 (+/=) will cause
           * underestimation; increase OPENID_MAX_LOGOUT_URL_LENGTH if the
           * fallback does not trigger as expected.
           */
          const maxLogoutUrlLength = parseMaxLogoutUrlLength();
          let strategy = 'no_token';
          if (idToken) {
            const baseLength = endSessionUrl.toString().length;
            const projectedLength = baseLength + '&id_token_hint='.length + idToken.length;
            if (projectedLength > maxLogoutUrlLength) {
              strategy = 'too_long';
              logger.debug(
                `[logoutController] Logout URL too long (${projectedLength} chars, max ${maxLogoutUrlLength}), ` +
                  'switching to logout_hint strategy',
              );
            } else {
              strategy = 'use_token';
            }
          }

          if (strategy === 'use_token') {
            endSessionUrl.searchParams.set('id_token_hint', idToken);
          } else {
            if (strategy === 'too_long') {
              const logoutHint = req.user?.email || req.user?.username || req.user?.openidId;
              if (logoutHint) {
                endSessionUrl.searchParams.set('logout_hint', logoutHint);
              }
            }

            if (process.env.OPENID_CLIENT_ID) {
              endSessionUrl.searchParams.set('client_id', process.env.OPENID_CLIENT_ID);
            } else if (strategy === 'too_long') {
              logger.warn(
                '[logoutController] Logout URL exceeds max length and OPENID_CLIENT_ID is not set. ' +
                  'The OIDC end-session request may be rejected. ' +
                  'Consider setting OPENID_CLIENT_ID or increasing OPENID_MAX_LOGOUT_URL_LENGTH.',
              );
            } else {
              logger.warn(
                '[logoutController] Neither id_token_hint nor OPENID_CLIENT_ID is available. ' +
                  'To enable id_token_hint, set OPENID_REUSE_TOKENS=true. ' +
                  'The OIDC end-session request may be rejected by the identity provider.',
              );
            }
          }

          response.redirect = endSessionUrl.toString();
        } else {
          logger.warn(
            '[logoutController] end_session_endpoint not found in OpenID issuer metadata. Please verify that the issuer is correct.',
          );
        }
      }
    }
    return res.status(status).send(response);
  } catch (err) {
    logger.error('[logoutController]', err);
    return res.status(500).json({ message: err.message });
  }
};

module.exports = {
  logoutController,
};
