// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services).
import type { Req, Res, Next } from '../types/http';
('use strict');

/**
 * OIDC / authentik authentication routes.
 *
 * GET /api/auth/oidc/login    — initiates authorization-code + PKCE flow
 * GET /api/auth/oidc/callback — handles redirect from the identity provider
 *
 * Returns 501 if OIDC is not configured or not enabled by admin.
 * Local auth is unaffected. Tokens are never logged or forwarded to the frontend.
 */

const express = require('express');
const router = express.Router();

const {
  createSession,
  cookieOpts,
  COOKIE_NAME,
  recordLogin,
} = require('../services/authService.cts');
const {
  getOidcConfig,
  isOidcLoginActive,
  createLoginState,
  consumeLoginState,
  buildAuthorizationUrl,
  exchangeAndVerifyTokens,
  findOrProvisionUser,
} = require('../services/oidcService.cts');

const NOT_ACTIVE = { error: 'OIDC authentication is not configured or not enabled on this server' };

// ── GET /api/auth/oidc/login ──────────────────────────────────────────────────
// Validates that OIDC is active, generates PKCE + nonce + state, stores in DB,
// then redirects the user to the identity provider's authorization endpoint.
router.get('/login', async (req: Req, res: Res) => {
  if (!isOidcLoginActive()) return res.status(501).json(NOT_ACTIVE);
  const config = getOidcConfig();
  if (!config) return res.status(501).json(NOT_ACTIVE);

  try {
    const redirectTo = typeof req.query.redirect_to === 'string' ? req.query.redirect_to : null;
    const state = createLoginState(redirectTo);
    const authUrl = await buildAuthorizationUrl(config, state);
    res.redirect(authUrl);
  } catch (err) {
    const msg = err.message || String(err);
    console.error('[oidc] Login initiation error:', msg || '(no message)');
    if (err.errors) {
      err.errors.forEach((e, i) => console.error(`  [${i}]`, e.message || String(e)));
    }
    if (err.cause) console.error('  cause:', err.cause.message || String(err.cause));
    res.status(502).json({ error: 'Failed to reach the identity provider. Please try again.' });
  }
});

// ── GET /api/auth/oidc/callback ───────────────────────────────────────────────
// Receives code + state from the identity provider after user authentication.
//
// Full validation via openid-client@5:
//   • State matches saved state  (replay prevention)
//   • PKCE code_verifier / code_challenge
//   • JWT signature via JWKS (cryptographic verification)
//   • Issuer, audience, expiry, nonce claims
//
// On success: creates a local session, sets cookie, redirects to frontend.
// On any failure: redirects with a safe oidc_error query parameter.
// Tokens are never logged or forwarded.
router.get('/callback', async (req: Req, res: Res) => {
  if (!isOidcLoginActive()) return res.redirect('/?oidc_error=not_configured');
  const config = getOidcConfig();
  if (!config) return res.redirect('/?oidc_error=not_configured');

  const { code, state: stateId, error } = req.query;

  // Provider signalled an authorization error — log code only, not description (may have PII)
  if (error) {
    console.error('[oidc] Provider error on callback:', String(error).slice(0, 40));
    return res.redirect('/?oidc_error=authorization_failed');
  }

  if (!code || !stateId || typeof code !== 'string' || typeof stateId !== 'string') {
    return res.redirect('/?oidc_error=invalid_callback');
  }

  // Consume the one-time state — validates expiry and prevents replay
  const savedState = consumeLoginState(stateId);
  if (!savedState) {
    return res.redirect('/?oidc_error=invalid_or_expired_state');
  }

  try {
    // Full token exchange + cryptographic ID token verification via openid-client@5.
    // Verifies: state, PKCE, JWT signature (JWKS), issuer, audience, expiry, nonce.
    // Throws on any validation failure. Tokens are never logged.
    const claims = await exchangeAndVerifyTokens(config, code, stateId, savedState);

    // Map verified claims to a local user account
    const user = await findOrProvisionUser(claims, config);

    // Create a local session — same mechanism as local login
    const session = await createSession(user.id);
    if (!session) throw new Error('Failed to create local session after OIDC login');

    recordLogin(user.id, req.ip, req.get('user-agent'), session.sessionId);
    res.cookie(COOKIE_NAME, session.sessionId, cookieOpts(req));
    res.redirect(savedState.redirect_to || '/');
  } catch (err) {
    // Log message only — never log tokens, codes, or ID token contents
    const msg = err.message || String(err);
    console.error('[oidc] Callback error:', msg || '(no message)');
    if (err.errors) {
      err.errors.forEach((e, i) => console.error(`  [${i}]`, e.message || String(e)));
    }
    if (err.cause) console.error('  cause:', err.cause.message || String(err.cause));
    const errCode = err.status === 403 ? 'access_denied' : 'authentication_failed';
    res.redirect(`/?oidc_error=${errCode}`);
  }
});

module.exports = router;
