// @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';
const express = require('express');
const router = express.Router();

let _appVersion;
function getAppVersion() {
  if (!_appVersion) {
    try {
      _appVersion = require('../package.json').version;
    } catch {
      _appVersion = '0.0.0';
    }
  }
  return _appVersion;
}

const { getDb, getSetting, setSetting } = require('../db/database.cts');
const {
  login,
  logout,
  hashPassword,
  cookieOpts,
  COOKIE_NAME,
  SINGLE_COOKIE_NAME,
  rotateSessionId,
  invalidateOtherSessions,
  recordLogin,
  recordFailedLogin,
} = require('../services/authService.cts');
const { decryptSecret } = require('../services/encryptionService.cts');
const { getCsrfToken } = require('../middleware/csrf.cts');
const { requireAuth, requireAdmin } = require('../middleware/requireAuth.cts');
const { getPublicOidcInfo } = require('../services/oidcService.cts');
const { ValidationError, formatError } = require('../utils/apiError.cts');
const { standardizeError } = require('../middleware/errorFormatter.cts');
const { passwordLimiter } = require('../middleware/rateLimiter.cts');
const { logAudit } = require('../services/auditService.cts');

// ─────────────────────────────────────────
// PUBLIC AUTH ROUTES
// ─────────────────────────────────────────

// POST /api/auth/login
router.post(
  '/login',
  (req: Req, res: Res, next: Next) => {
    // Exempt login from CSRF - no session exists yet to hijack
    // CSRF validation happens on all other authenticated routes
    req.csrfSkip = true;
    next();
  },
  async (req: Req, res: Res) => {
    // Respect admin-configured login method toggle
    if (getSetting('local_login_enabled') === 'false') {
      return res
        .status(403)
        .json(
          standardizeError(
            'Local username/password login is not enabled on this server.',
            'FORBIDDEN',
          ),
        );
    }

    const { username, password } = req.body;
    if (!username || !password) {
      return res
        .status(400)
        .json(
          standardizeError(
            'Username and password are required',
            'VALIDATION_ERROR',
            !username ? 'username' : 'password',
          ),
        );
    }

    try {
      const result = await login(username, password);
      if (!result || result.error) {
        logAudit({
          user_id: null,
          action: 'login.failure',
          details: { username },
          ip_address: req.ip,
          user_agent: req.get('user-agent'),
        });
        // Track failed attempt against known accounts (wrong password only — not unknown usernames)
        if (result?.error === 'bad_password') {
          recordFailedLogin(result.userId, req.ip, req.get('user-agent'));
        }
        return res.status(401).json(standardizeError('Invalid username or password', 'AUTH_ERROR'));
      }

      // TOTP required — don't create a session yet
      if (result.requires_totp) {
        return res.json({ requires_totp: true, challenge_token: result.challenge_token });
      }

      logAudit({
        user_id: result.user.id,
        action: 'login.success',
        ip_address: req.ip,
        user_agent: req.get('user-agent'),
      });
      recordLogin(result.user.id, req.ip, req.get('user-agent'), result.sessionId);

      res.cookie(COOKIE_NAME, result.sessionId, cookieOpts(req));
      res.json({ user: result.user });
    } catch (err) {
      console.error('Login error:', err);
      res.status(500).json(standardizeError('Login failed', 'SERVER_ERROR'));
    }
  },
);

// GET /api/auth/csrf-token
// Public — returns the CSRF token from the httpOnly cookie so the SPA can
// store it in memory and send it in the x-csrf-token header for mutations.
// Cross-origin access is prevented by the same-origin fetch policy (no CORS).
router.get('/csrf-token', (req: Req, res: Res) => {
  const token = getCsrfToken(req, res);
  res.json({ token });
});

// POST /api/auth/logout
router.post('/logout', requireAuth, (req: Req, res: Res) => {
  logout(req.cookies?.[COOKIE_NAME]);
  logAudit({
    user_id: req.user.id,
    action: 'logout',
    ip_address: req.ip,
    user_agent: req.get('user-agent'),
  });
  res.clearCookie(COOKIE_NAME, { path: '/', ...cookieOpts(req), maxAge: undefined });
  res.json({ success: true });
});

// POST /api/auth/logout-all
router.post('/logout-all', requireAuth, (req: Req, res: Res) => {
  // Delete ALL sessions for this user
  invalidateOtherSessions(req.user.id, null); // null means delete all sessions

  // Also clear the current session
  logout(req.cookies?.[COOKIE_NAME]);

  logAudit({
    user_id: req.user.id,
    action: 'logout.all',
    ip_address: req.ip,
    user_agent: req.get('user-agent'),
  });

  res.clearCookie(COOKIE_NAME, { path: '/', ...cookieOpts(req), maxAge: undefined });
  res.json({ success: true });
});

// POST /api/auth/logout-others — sign out every OTHER device, keep this one signed in.
router.post('/logout-others', requireAuth, (req: Req, res: Res) => {
  const result = invalidateOtherSessions(req.user.id, req.cookies?.[COOKIE_NAME]);
  logAudit({
    user_id: req.user.id,
    action: 'logout.others',
    ip_address: req.ip,
    user_agent: req.get('user-agent'),
  });
  res.json({ success: true, count: result?.changes || 0 });
});

// GET /api/auth/me
router.get('/me', requireAuth, (req: Req, res: Res) => {
  const currentVersion = getAppVersion();
  res.json({
    user: req.user,
    single_user_mode: !!req.singleUserMode,
    current_version: currentVersion,
    release_notes_version: currentVersion,
    has_new_version: req.user.last_seen_version !== currentVersion,
  });
});

// GET /api/auth/login-history — last 10 logins for the authenticated user (encrypted at rest, decrypted here)
router.get('/login-history', requireAuth, (req: Req, res: Res) => {
  const db = getDb();
  const rows = db
    .prepare(
      `
    SELECT id, logged_in_at, ip_address, user_agent,
           browser, os, device_type, device_fingerprint, session_fingerprint,
           success, location_city, location_country, location_region, location_isp
    FROM user_login_history
    WHERE user_id = ?
    ORDER BY logged_in_at DESC
    LIMIT 10
  `,
    )
    .all(req.user.id);

  const safeDecrypt = (v) => {
    if (!v) return null;
    try {
      return decryptSecret(v);
    } catch {
      return null;
    }
  };

  // Compute fingerprint of the current session cookie to mark "this session".
  // Single-user mode has no COOKIE_NAME — use the presence cookie instead.
  const currentCookie = req.singleUserMode
    ? req.cookies?.[SINGLE_COOKIE_NAME]
    : req.cookies?.[COOKIE_NAME];
  const currentFingerprint = currentCookie
    ? require('crypto').createHash('sha256').update(currentCookie).digest('hex').slice(0, 32)
    : null;

  const history = rows.map((r) => ({
    id: r.id,
    logged_in_at: r.logged_in_at,
    ip_address: safeDecrypt(r.ip_address),
    user_agent: safeDecrypt(r.user_agent),
    browser: r.browser,
    os: r.os,
    device_type: r.device_type,
    device_fingerprint: r.device_fingerprint,
    success: r.success !== 0,
    is_current_session: !!(currentFingerprint && r.session_fingerprint === currentFingerprint),
    location_city: safeDecrypt(r.location_city),
    location_country: safeDecrypt(r.location_country),
    location_region: safeDecrypt(r.location_region),
    location_isp: safeDecrypt(r.location_isp),
  }));

  res.json({ history });
});

// ── TOTP / Authenticator App ─────────────────────────────────────────────────

const {
  generateSecret,
  generateQrCode,
  verifyToken,
  verifyTokenRaw,
  generateRecoveryCodes,
  hashRecoveryCode,
  consumeRecoveryCode,
  createChallenge,
  consumeChallenge,
} = require('../services/totpService.cts');
const { encryptSecret: encTotpSecret } = require('../services/encryptionService.cts');

// POST /api/auth/totp/challenge — second step of login when TOTP is enabled.
// Takes challenge_token (from first login step) + totp_code, creates a session.
router.post('/totp/challenge', async (req: Req, res: Res) => {
  req.csrfSkip = true;
  const { challenge_token, code, recovery_code } = req.body || {};
  if (!challenge_token)
    return res
      .status(400)
      .json(standardizeError('challenge_token is required', 'VALIDATION_ERROR'));

  const db = getDb();
  const userId = consumeChallenge(db, challenge_token);
  if (!userId)
    return res
      .status(401)
      .json(standardizeError('Challenge expired or invalid. Please sign in again.', 'AUTH_ERROR'));

  const user = db.prepare('SELECT * FROM users WHERE id = ? AND active = 1').get(userId);
  if (!user) return res.status(401).json(standardizeError('User not found.', 'AUTH_ERROR'));

  let verified = false;
  if (recovery_code) {
    const result = consumeRecoveryCode(db, userId, recovery_code);
    verified = result.used;
    if (verified && result.remaining === 0) {
      // Warn but don't block — they're in, but should regenerate codes
    }
  } else if (code) {
    verified = verifyToken(user.totp_secret, code);
  }

  if (!verified) {
    logAudit({
      user_id: userId,
      action: 'totp.failure',
      ip_address: req.ip,
      user_agent: req.get('user-agent'),
    });
    return res.status(401).json(standardizeError('Invalid authenticator code.', 'AUTH_ERROR'));
  }

  try {
    const { createSession } = require('../services/authService.cts');
    const session = await createSession(userId);
    if (!session)
      return res.status(500).json(standardizeError('Failed to create session', 'SERVER_ERROR'));

    logAudit({
      user_id: userId,
      action: 'login.success',
      ip_address: req.ip,
      user_agent: req.get('user-agent'),
    });
    recordLogin(userId, req.ip, req.get('user-agent'), session.sessionId);

    res.cookie(COOKIE_NAME, session.sessionId, cookieOpts(req));
    res.json({ user: session.user });
  } catch (err) {
    console.error('[totp/challenge]', err);
    res.status(500).json(standardizeError('Login failed', 'SERVER_ERROR'));
  }
});

// GET /api/auth/totp/setup — generate a new pending secret + QR code for the authenticated user.
// The secret is NOT saved yet; the user must confirm a valid code via /totp/enable.
router.get('/totp/setup', requireAuth, async (req: Req, res: Res) => {
  if (req.singleUserMode)
    return res
      .status(400)
      .json(standardizeError('TOTP is not available in single-user mode.', 'VALIDATION_ERROR'));
  try {
    const secret = generateSecret();
    const user = getDb().prepare('SELECT username FROM users WHERE id = ?').get(req.user.id);
    const { uri, qr_data_url } = await generateQrCode(secret, user.username);
    res.json({ secret, uri, qr_data_url });
  } catch (err) {
    console.error('[totp/setup]', err);
    res.status(500).json(standardizeError('Failed to generate setup data', 'SERVER_ERROR'));
  }
});

// POST /api/auth/totp/enable — verify a code against the submitted secret, then enable TOTP.
router.post('/totp/enable', requireAuth, (req: Req, res: Res) => {
  if (req.singleUserMode)
    return res
      .status(400)
      .json(standardizeError('TOTP is not available in single-user mode.', 'VALIDATION_ERROR'));
  const { secret, code } = req.body || {};
  if (!secret || !code)
    return res
      .status(400)
      .json(standardizeError('secret and code are required', 'VALIDATION_ERROR'));
  if (!verifyTokenRaw(secret, code))
    return res
      .status(400)
      .json(
        standardizeError(
          'Invalid authenticator code. Check your app and try again.',
          'VALIDATION_ERROR',
          'code',
        ),
      );

  const plainCodes = generateRecoveryCodes();
  const hashedCodes = plainCodes.map(hashRecoveryCode);
  const db = getDb();
  db.prepare(
    `
    UPDATE users SET totp_enabled=1, totp_secret=?, totp_recovery_codes=?, updated_at=datetime('now')
    WHERE id=?
  `,
  ).run(encTotpSecret(secret), encTotpSecret(JSON.stringify(hashedCodes)), req.user.id);

  logAudit({
    user_id: req.user.id,
    action: 'totp.enabled',
    ip_address: req.ip,
    user_agent: req.get('user-agent'),
  });
  res.json({ enabled: true, recovery_codes: plainCodes });
});

// POST /api/auth/totp/disable — disable TOTP. Requires a valid TOTP code or recovery code.
router.post('/totp/disable', requireAuth, (req: Req, res: Res) => {
  const { code, recovery_code } = req.body || {};
  const db = getDb();
  const user = db.prepare('SELECT * FROM users WHERE id = ?').get(req.user.id);
  if (!user?.totp_enabled)
    return res.status(400).json(standardizeError('TOTP is not enabled.', 'VALIDATION_ERROR'));

  let verified = false;
  if (recovery_code) {
    verified = consumeRecoveryCode(db, req.user.id, recovery_code).used;
  } else if (code) {
    verified = verifyToken(user.totp_secret, code);
  }
  if (!verified)
    return res
      .status(401)
      .json(standardizeError('Invalid authenticator code.', 'AUTH_ERROR', 'code'));

  db.prepare(
    `UPDATE users SET totp_enabled=0, totp_secret=NULL, totp_recovery_codes=NULL, updated_at=datetime('now') WHERE id=?`,
  ).run(req.user.id);
  logAudit({
    user_id: req.user.id,
    action: 'totp.disabled',
    ip_address: req.ip,
    user_agent: req.get('user-agent'),
  });
  res.json({ enabled: false });
});

// GET /api/auth/totp/status — is TOTP enabled for the current user?
router.get('/totp/status', requireAuth, (req: Req, res: Res) => {
  const user = getDb().prepare('SELECT totp_enabled FROM users WHERE id = ?').get(req.user.id);
  res.json({ enabled: !!user?.totp_enabled });
});

// POST /api/auth/totp/acknowledge-version — user has seen the release notes
router.post('/acknowledge-version', requireAuth, (req: Req, res: Res) => {
  const currentVersion = getAppVersion();
  getDb()
    .prepare("UPDATE users SET last_seen_version = ?, updated_at = datetime('now') WHERE id = ?")
    .run(currentVersion, req.user.id);
  res.json({
    success: true,
    last_seen_version: currentVersion,
    release_notes_version: currentVersion,
  });
});

// GET /api/auth/mode
// Public — tells the login page which options are available.
// Never returns secrets. local_enabled/oidc_enabled reflect admin settings.
router.get('/mode', (req: Req, res: Res) => {
  const oidcInfo = getPublicOidcInfo();
  const localEnabled = getSetting('local_login_enabled') !== 'false';
  res.json({
    auth_mode: getSetting('auth_mode') || 'multi',
    local_enabled: localEnabled,
    ...oidcInfo,
  });
});

// POST /api/auth/restore-multi-user-mode
// Recovery path for single-user mode. In single-user mode requireAuth attaches
// the configured default user, so this lets that Settings page restore normal
// login without needing access to Admin routes.
router.post('/restore-multi-user-mode', requireAuth, (req: Req, res: Res) => {
  if (!req.singleUserMode && getSetting('auth_mode') !== 'single') {
    return res
      .status(400)
      .json(standardizeError('Single-user mode is not enabled.', 'VALIDATION_ERROR', 'auth_mode'));
  }

  setSetting('auth_mode', 'multi');
  setSetting('default_user_id', '');

  res.json({ success: true, auth_mode: 'multi' });
});

// POST /api/auth/acknowledge-privacy
router.post('/acknowledge-privacy', requireAuth, (req: Req, res: Res) => {
  getDb()
    .prepare("UPDATE users SET first_login = 0, updated_at = datetime('now') WHERE id = ?")
    .run(req.user.id);

  res.json({ success: true });
});

// POST /api/auth/change-password
// Password change endpoint with dedicated rate limiter
// CSRF protected via csrfMiddleware on /api/auth mount
router.post('/change-password', passwordLimiter, requireAuth, async (req: Req, res: Res) => {
  const { current_password, new_password } = req.body;

  if (!new_password || new_password.length < 8) {
    return res
      .status(400)
      .json(
        standardizeError(
          'New password must be at least 8 characters',
          'VALIDATION_ERROR',
          'new_password',
        ),
      );
  }

  const db = getDb();
  const user = db.prepare('SELECT * FROM users WHERE id = ?').get(req.user.id);

  try {
    if (!user.must_change_password) {
      const bcrypt = require('bcryptjs');
      const valid = await bcrypt.compare(current_password || '', user.password_hash);
      if (!valid)
        return res
          .status(401)
          .json(
            standardizeError('Current password is incorrect', 'AUTH_ERROR', 'current_password'),
          );
    }

    const hash = await hashPassword(new_password);

    db.prepare(
      "UPDATE users SET password_hash = ?, must_change_password = 0, last_password_change_at = datetime('now'), updated_at = datetime('now') WHERE id = ?",
    ).run(hash, req.user.id);

    // Invalidate all other sessions for this user
    const currentSessionId = req.cookies?.[COOKIE_NAME];
    if (currentSessionId) {
      invalidateOtherSessions(req.user.id, currentSessionId);

      // Rotate the current session ID for security
      const newSessionId = rotateSessionId(currentSessionId, req.user.id);
      if (newSessionId) {
        res.cookie(COOKIE_NAME, newSessionId, cookieOpts(req));
      }
    }

    logAudit({
      user_id: req.user.id,
      action: 'password.change',
      ip_address: req.ip,
      user_agent: req.get('user-agent'),
    });

    res.json({ success: true });
  } catch (err) {
    console.error('[auth] change-password error:', err.message);
    res.status(500).json(standardizeError('Password change failed', 'SERVER_ERROR'));
  }
});

// ─────────────────────────────────────────
// ADMIN ROUTES (MOUNTED AT /api/admin)
// ─────────────────────────────────────────

// GET /api/admin/has-users
router.get('/has-users', (req: Req, res: Res) => {
  const count = getDb().prepare("SELECT COUNT(*) AS n FROM users WHERE role = 'user'").get().n;

  res.json({ has_users: count > 0 });
});

// GET /api/admin/users
router.get('/users', requireAuth, requireAdmin, (req: Req, res: Res) => {
  const users = getDb()
    .prepare(
      'SELECT id, username, role, must_change_password, first_login, created_at FROM users ORDER BY role DESC, username ASC',
    )
    .all();

  res.json(users);
});

// POST /api/admin/users
router.post('/users', requireAuth, requireAdmin, async (req: Req, res: Res) => {
  const { username, password } = req.body;

  if (!username || username.length < 3) {
    return res
      .status(400)
      .json(
        standardizeError('Username must be at least 3 characters', 'VALIDATION_ERROR', 'username'),
      );
  }

  if (!password || password.length < 8) {
    return res
      .status(400)
      .json(
        standardizeError('Password must be at least 8 characters', 'VALIDATION_ERROR', 'password'),
      );
  }

  const db = getDb();

  const existing = db.prepare('SELECT id FROM users WHERE username = ?').get(username);
  if (existing)
    return res.status(409).json(standardizeError('Username already taken', 'CONFLICT', 'username'));

  try {
    const hash = await hashPassword(password);

    const result = db
      .prepare(
        "INSERT INTO users (username, password_hash, role, first_login, last_seen_version) VALUES (?, ?, 'user', 1, ?)",
      )
      .run(username, hash, getAppVersion());

    const created = db
      .prepare(
        'SELECT id, username, role, must_change_password, first_login, created_at FROM users WHERE id = ?',
      )
      .get(result.lastInsertRowid);

    res.status(201).json(created);
  } catch (err) {
    console.error('[auth] create-user error:', err.message);
    res.status(500).json(standardizeError('Failed to create user', 'SERVER_ERROR'));
  }
});

// ── WebAuthn / FIDO2 security key ─────────────────────────────────────────────

const {
  createRegistrationChallenge,
  verifyRegistration,
  createAuthenticationChallenge,
  verifyAuthentication,
  consumeLoginChallenge,
  getCredentials,
  deleteCredential,
} = require('../services/webauthnService.cts');

// GET /api/auth/webauthn/status
router.get('/webauthn/status', requireAuth, (req: Req, res: Res) => {
  if (req.singleUserMode) return res.json({ enabled: false, credential_count: 0 });
  const db = getDb();
  const user = db.prepare('SELECT webauthn_enabled FROM users WHERE id = ?').get(req.user.id);
  const { n } = db
    .prepare('SELECT COUNT(*) AS n FROM webauthn_credentials WHERE user_id = ?')
    .get(req.user.id);
  res.json({ enabled: !!user?.webauthn_enabled, credential_count: n });
});

// GET /api/auth/webauthn/credentials
router.get('/webauthn/credentials', requireAuth, (req: Req, res: Res) => {
  if (req.singleUserMode) return res.json({ credentials: [] });
  res.json({ credentials: getCredentials(getDb(), req.user.id) });
});

// GET /api/auth/webauthn/setup — begin registration
router.get('/webauthn/setup', requireAuth, async (req: Req, res: Res) => {
  if (req.singleUserMode)
    return res
      .status(400)
      .json(standardizeError('WebAuthn is not available in single-user mode.', 'VALIDATION_ERROR'));
  try {
    const { options, challengeId } = await createRegistrationChallenge(
      getDb(),
      req.user.id,
      req.user.username,
    );
    res.json({ options, challengeId });
  } catch (err) {
    console.error('[webauthn/setup]', err);
    res.status(500).json(standardizeError('Failed to generate setup options', 'SERVER_ERROR'));
  }
});

// POST /api/auth/webauthn/enable — complete registration
router.post('/webauthn/enable', requireAuth, async (req: Req, res: Res) => {
  if (req.singleUserMode)
    return res
      .status(400)
      .json(standardizeError('WebAuthn is not available in single-user mode.', 'VALIDATION_ERROR'));
  const { challengeId, response, credential_name } = req.body || {};
  if (!challengeId || !response)
    return res
      .status(400)
      .json(standardizeError('challengeId and response are required', 'VALIDATION_ERROR'));

  try {
    const db = getDb();
    const result = await verifyRegistration(
      db,
      req.user.id,
      challengeId,
      response,
      credential_name,
    );
    if (!result.verified)
      return res
        .status(400)
        .json(standardizeError(result.error || 'Registration failed', 'VALIDATION_ERROR'));

    db.prepare(
      "UPDATE users SET webauthn_enabled = 1, updated_at = datetime('now') WHERE id = ?",
    ).run(req.user.id);

    logAudit({
      user_id: req.user.id,
      action: 'webauthn.credential_added',
      ip_address: req.ip,
      user_agent: req.get('user-agent'),
    });
    res.json({ enabled: true, credential_id: result.credentialId });
  } catch (err) {
    console.error('[webauthn/enable]', err);
    res.status(500).json(standardizeError('Registration failed', 'SERVER_ERROR'));
  }
});

// DELETE /api/auth/webauthn/credentials/:credentialId — remove one key
router.delete('/webauthn/credentials/:credentialId', requireAuth, async (req: Req, res: Res) => {
  const { password } = req.body || {};
  if (!password)
    return res.status(400).json(standardizeError('password is required', 'VALIDATION_ERROR'));

  const db = getDb();
  const user = db.prepare('SELECT password_hash FROM users WHERE id = ?').get(req.user.id);
  try {
    const bcrypt = require('bcryptjs');
    if (!(await bcrypt.compare(password, user.password_hash)))
      return res.status(401).json(standardizeError('Password is incorrect', 'AUTH_ERROR'));

    const result = deleteCredential(db, req.params.credentialId, req.user.id);
    if (result.changes === 0)
      return res.status(404).json(standardizeError('Credential not found', 'NOT_FOUND'));

    const { n } = db
      .prepare('SELECT COUNT(*) AS n FROM webauthn_credentials WHERE user_id = ?')
      .get(req.user.id);
    if (n === 0)
      db.prepare(
        "UPDATE users SET webauthn_enabled = 0, updated_at = datetime('now') WHERE id = ?",
      ).run(req.user.id);

    logAudit({
      user_id: req.user.id,
      action: 'webauthn.credential_removed',
      ip_address: req.ip,
      user_agent: req.get('user-agent'),
    });
    res.json({ success: true, webauthn_enabled: n > 0 });
  } catch (err) {
    console.error('[webauthn/credentials/delete]', err);
    res.status(500).json(standardizeError('Failed to remove credential', 'SERVER_ERROR'));
  }
});

// POST /api/auth/webauthn/disable — remove all keys, disable WebAuthn
router.post('/webauthn/disable', requireAuth, async (req: Req, res: Res) => {
  const { password } = req.body || {};
  if (!password)
    return res.status(400).json(standardizeError('password is required', 'VALIDATION_ERROR'));

  const db = getDb();
  const user = db
    .prepare('SELECT password_hash, webauthn_enabled FROM users WHERE id = ?')
    .get(req.user.id);
  if (!user?.webauthn_enabled)
    return res.status(400).json(standardizeError('WebAuthn is not enabled.', 'VALIDATION_ERROR'));

  try {
    const bcrypt = require('bcryptjs');
    if (!(await bcrypt.compare(password, user.password_hash)))
      return res.status(401).json(standardizeError('Password is incorrect', 'AUTH_ERROR'));

    db.prepare('DELETE FROM webauthn_credentials WHERE user_id = ?').run(req.user.id);
    db.prepare(
      "UPDATE users SET webauthn_enabled = 0, updated_at = datetime('now') WHERE id = ?",
    ).run(req.user.id);

    logAudit({
      user_id: req.user.id,
      action: 'webauthn.disabled',
      ip_address: req.ip,
      user_agent: req.get('user-agent'),
    });
    res.json({ enabled: false });
  } catch (err) {
    console.error('[webauthn/disable]', err);
    res.status(500).json(standardizeError('Failed to disable WebAuthn', 'SERVER_ERROR'));
  }
});

// POST /api/auth/webauthn/challenge — second step of login when WebAuthn is enabled.
// Mirrors POST /totp/challenge exactly.
router.post('/webauthn/challenge', async (req: Req, res: Res) => {
  req.csrfSkip = true;
  const { challenge_token, response } = req.body || {};
  if (!challenge_token || !response)
    return res
      .status(400)
      .json(standardizeError('challenge_token and response are required', 'VALIDATION_ERROR'));

  const db = getDb();
  const session = consumeLoginChallenge(db, challenge_token);
  if (!session)
    return res
      .status(401)
      .json(standardizeError('Challenge expired or invalid. Please sign in again.', 'AUTH_ERROR'));

  const user = db.prepare('SELECT * FROM users WHERE id = ? AND active = 1').get(session.userId);
  if (!user) return res.status(401).json(standardizeError('User not found.', 'AUTH_ERROR'));

  try {
    const result = await verifyAuthentication(
      db,
      session.userId,
      session.authChallengeId,
      response,
    );
    if (!result.verified) {
      logAudit({
        user_id: session.userId,
        action: 'webauthn.failure',
        ip_address: req.ip,
        user_agent: req.get('user-agent'),
      });
      return res
        .status(401)
        .json(standardizeError('Security key verification failed.', 'AUTH_ERROR'));
    }

    const { createSession } = require('../services/authService.cts');
    const s = await createSession(session.userId);
    if (!s)
      return res.status(500).json(standardizeError('Failed to create session', 'SERVER_ERROR'));

    logAudit({
      user_id: session.userId,
      action: 'login.success',
      details: { method: 'webauthn' },
      ip_address: req.ip,
      user_agent: req.get('user-agent'),
    });
    recordLogin(session.userId, req.ip, req.get('user-agent'), s.sessionId);

    res.cookie(COOKIE_NAME, s.sessionId, cookieOpts(req));
    res.json({ user: s.user });
  } catch (err) {
    console.error('[webauthn/challenge]', err);
    res.status(500).json(standardizeError('Login failed', 'SERVER_ERROR'));
  }
});

module.exports = router;
