/**
 * Auth Feature — API Routes
 *
 * User management endpoints (profile, admin CRUD).
 * Auth is enforced by the router before any handler is called — no manual
 * session validation is needed here. The validated session is injected via
 * the context argument: (request, params, { session }).
 */

import { query, create, updateById, findOne } from '@zen/core/database';
import { updateUser, requestPasswordReset } from './auth.js';
import { hashPassword, verifyPassword, generateId } from '../../core/users/password.js';
import { createEmailChangeToken, verifyEmailChangeToken, applyEmailChange, sendEmailChangeConfirmEmail, sendEmailChangeOldNotifyEmail, sendEmailChangeNewNotifyEmail, sendPasswordChangedEmail, sendPasswordResetEmail, sendInvitationEmail } from './email.js';
import { createAccountSetup } from '../../core/users/verifications.js';
import { listRoles, getRoleById, createRole, updateRole, deleteRole, getUserRoles, assignUserRole, revokeUserRole, deleteUserSessions, PERMISSIONS, getRegisteredPermissions } from '@zen/core/users';
import { uploadImage, deleteFile, generateUniqueFilename, getFileExtension, FILE_TYPE_PRESETS, FILE_SIZE_LIMITS, validateUpload } from '@zen/core/storage';
import { getPublicBaseUrl } from '@zen/core/shared/config';

const generateUserFilePath = (userId, category, filename) => `users/${userId}/${category}/${filename}`;
import { fail, info } from '@zen/core/shared/logger';
import { defineApiRoutes, apiSuccess, apiError } from '@zen/core/api';

/** Maximum number of users returned per paginated request */
const MAX_PAGE_LIMIT = 100;

/**
 * Extension → MIME type map derived from the validated file extension.
 * The client-supplied file.type is NEVER trusted.
 */
const EXTENSION_TO_MIME = {
  '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
  '.png': 'image/png', '.gif': 'image/gif', '.webp': 'image/webp',
};

/**
 * Log the raw error server-side and return an opaque fallback.
 * Never forward internal error details to the client.
 */
function logAndObscureError(error, fallback) {
  fail(`Internal handler error: ${error.message}`);
  return fallback;
}

// ---------------------------------------------------------------------------
// GET /zen/api/users/:id   (admin only)
// ---------------------------------------------------------------------------

async function handleGetUserById(_request, { id: userId }) {
  const result = await query(
    'SELECT id, email, name, role, image, email_verified, created_at FROM zen_auth_users WHERE id = $1',
    [userId]
  );

  if (result.rows.length === 0) {
    return apiError('Not Found', 'User not found');
  }

  return apiSuccess({ user: result.rows[0] });
}

// ---------------------------------------------------------------------------
// PUT /zen/api/users/:id   (admin only)
// ---------------------------------------------------------------------------

async function handleUpdateUserById(request, { id: userId }) {
  try {
    const body = await request.json();
    const allowedFields = ['name', 'role', 'email_verified'];
    const updateData = { updated_at: new Date() };

    for (const field of allowedFields) {
      if (body[field] !== undefined) {
        if (field === 'email_verified') {
          updateData[field] = Boolean(body[field]);
        } else if (field === 'role') {
          const role = String(body[field]).toLowerCase();
          if (['admin', 'user'].includes(role)) {
            updateData[field] = role;
          }
        } else if (field === 'name' && body[field] != null) {
          updateData[field] = String(body[field]).trim() || null;
        }
      }
    }

    const updated = await updateById('zen_auth_users', userId, updateData);
    if (!updated) {
      return apiError('Not Found', 'User not found');
    }

    const result = await query(
      'SELECT id, email, name, role, image, email_verified, created_at FROM zen_auth_users WHERE id = $1',
      [userId]
    );
    return apiSuccess({
      success: true,
      user: result.rows[0],
      message: 'User updated successfully'
    });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Failed to update user');
  }
}

// ---------------------------------------------------------------------------
// GET /zen/api/users   (admin only)
// ---------------------------------------------------------------------------

async function handleListUsers(request) {
  // Both page and limit are clamped server-side; client-supplied values
  // cannot force full-table scans or negative offsets.
  const url = new URL(request.url);
  const page = Math.max(1, parseInt(url.searchParams.get('page') || '1', 10) || 1);
  const limit = Math.min(
    Math.max(1, parseInt(url.searchParams.get('limit') || '10', 10) || 10),
    MAX_PAGE_LIMIT
  );
  const offset = (page - 1) * limit;

  const sortBy = url.searchParams.get('sortBy') || 'created_at';
  const sortOrder = url.searchParams.get('sortOrder') || 'desc';

  // Whitelist allowed sort columns to prevent SQL injection via identifier injection.
  const allowedSortColumns = ['id', 'email', 'name', 'role', 'email_verified', 'created_at'];
  const sortColumn = allowedSortColumns.includes(sortBy) ? sortBy : 'created_at';
  const order = sortOrder.toLowerCase() === 'asc' ? 'ASC' : 'DESC';

  // Wrap the whitelisted column in double-quotes to enforce identifier boundaries.
  const quotedSortColumn = `"${sortColumn}"`;

  const result = await query(
    `SELECT u.id, u.email, u.name, u.role, u.image, u.email_verified, u.created_at,
      COALESCE(
        (SELECT json_agg(json_build_object('id', r.id, 'name', r.name, 'color', r.color) ORDER BY r.created_at ASC)
         FROM zen_auth_roles r
         JOIN zen_auth_user_roles ur ON ur.role_id = r.id
         WHERE ur.user_id = u.id),
        '[]'::json
      ) AS roles
     FROM zen_auth_users u ORDER BY u.${quotedSortColumn} ${order} LIMIT $1 OFFSET $2`,
    [limit, offset]
  );

  const countResult = await query('SELECT COUNT(*) FROM zen_auth_users');
  const total = parseInt(countResult.rows[0].count);

  return apiSuccess({
    users: result.rows,
    pagination: { page, limit, total, totalPages: Math.ceil(total / limit) }
  });
}

// ---------------------------------------------------------------------------
// POST /zen/api/users/profile/email
// ---------------------------------------------------------------------------

const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

async function handleInitiateEmailChange(request, _params, { session }) {
  try {
    const body = await request.json();
    const { newEmail, password } = body;

    if (!newEmail || !EMAIL_REGEX.test(newEmail) || newEmail.length > 254) {
      return apiError('Bad Request', 'Adresse courriel invalide');
    }

    if (!password) {
      return apiError('Bad Request', 'Le mot de passe est requis');
    }

    const normalizedEmail = newEmail.trim().toLowerCase();

    if (normalizedEmail === session.user.email.toLowerCase()) {
      return apiError('Bad Request', 'Cette adresse courriel est déjà la vôtre');
    }

    const existing = await findOne('zen_auth_users', { email: normalizedEmail });
    if (existing) {
      return apiError('Conflict', 'Cette adresse courriel est déjà utilisée');
    }

    const account = await findOne('zen_auth_accounts', { user_id: session.user.id, provider_id: 'credential' });
    if (!account || !account.password) {
      return apiError('Bad Request', 'Impossible de vérifier le mot de passe');
    }

    const passwordValid = await verifyPassword(password, account.password);
    if (!passwordValid) {
      return apiError('Unauthorized', 'Mot de passe incorrect');
    }

    const token = await createEmailChangeToken(session.user.id, normalizedEmail);
    const baseUrl = getPublicBaseUrl();

    try {
      await sendEmailChangeConfirmEmail(normalizedEmail, token, baseUrl);
    } catch (emailError) {
      fail(`handleInitiateEmailChange: failed to send confirmation email: ${emailError.message}`);
      return apiError('Internal Server Error', 'Impossible d\'envoyer le courriel de confirmation');
    }

    try {
      await sendEmailChangeOldNotifyEmail(session.user.email, normalizedEmail, 'pending');
    } catch (emailError) {
      fail(`handleInitiateEmailChange: failed to send notification email: ${emailError.message}`);
    }

    return apiSuccess({ success: true, message: `Un courriel de confirmation a été envoyé à ${normalizedEmail}` });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Impossible d\'initier le changement de courriel');
  }
}

// ---------------------------------------------------------------------------
// GET /zen/api/users/email/confirm
// ---------------------------------------------------------------------------

async function handleConfirmEmailChange(request, _params, { session }) {
  try {
    const url = new URL(request.url);
    const token = url.searchParams.get('token');

    if (!token) {
      return apiError('Bad Request', 'Jeton de confirmation manquant');
    }

    const result = await verifyEmailChangeToken(token);
    if (!result) {
      return apiError('Bad Request', 'Lien de confirmation invalide ou expiré');
    }

    const { userId, newEmail } = result;

    if (userId !== session.user.id) {
      return apiError('Forbidden', 'Ce lien ne vous appartient pas');
    }

    const existing = await findOne('zen_auth_users', { email: newEmail });
    if (existing) {
      return apiError('Conflict', 'Cette adresse courriel est déjà utilisée');
    }

    await applyEmailChange(userId, newEmail);

    return apiSuccess({ success: true, message: 'Adresse courriel mise à jour avec succès' });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Impossible de confirmer le changement de courriel');
  }
}

// ---------------------------------------------------------------------------
// PUT /zen/api/users/:id/email   (admin only)
// ---------------------------------------------------------------------------

async function handleAdminUpdateUserEmail(request, { id: userId }) {
  try {
    const body = await request.json();
    const { newEmail } = body;

    if (!newEmail || !EMAIL_REGEX.test(newEmail) || newEmail.length > 254) {
      return apiError('Bad Request', 'Adresse courriel invalide');
    }

    const normalizedEmail = newEmail.trim().toLowerCase();

    const targetUser = await findOne('zen_auth_users', { id: userId });
    if (!targetUser) {
      return apiError('Not Found', 'Utilisateur introuvable');
    }

    if (normalizedEmail === targetUser.email.toLowerCase()) {
      return apiError('Bad Request', 'Cette adresse courriel est déjà celle de l\'utilisateur');
    }

    const existing = await findOne('zen_auth_users', { email: normalizedEmail });
    if (existing) {
      return apiError('Conflict', 'Cette adresse courriel est déjà utilisée');
    }

    const oldEmail = targetUser.email;
    await applyEmailChange(userId, normalizedEmail);

    try {
      await sendEmailChangeOldNotifyEmail(oldEmail, normalizedEmail, 'changed');
    } catch (emailError) {
      fail(`handleAdminUpdateUserEmail: failed to notify old email ${oldEmail}: ${emailError.message}`);
    }

    try {
      await sendEmailChangeNewNotifyEmail(normalizedEmail, oldEmail);
    } catch (emailError) {
      fail(`handleAdminUpdateUserEmail: failed to notify new email ${normalizedEmail}: ${emailError.message}`);
    }

    const updated = await query(
      'SELECT id, email, name, role, image, email_verified, created_at FROM zen_auth_users WHERE id = $1',
      [userId]
    );

    return apiSuccess({ success: true, user: updated.rows[0], message: 'Courriel mis à jour avec succès' });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Impossible de mettre à jour le courriel');
  }
}

// ---------------------------------------------------------------------------
// PUT /zen/api/users/profile
// ---------------------------------------------------------------------------

async function handleUpdateProfile(request, _params, { session }) {
  try {
    const body = await request.json();
    const { name } = body;

    if (!name || !name.trim()) {
      return apiError('Bad Request', 'Name is required');
    }

    const updatedUser = await updateUser(session.user.id, { name: name.trim() });

    return apiSuccess({
      success: true,
      user: {
        id: updatedUser.id,
        email: updatedUser.email,
        name: updatedUser.name,
        role: updatedUser.role,
        image: updatedUser.image,
        email_verified: updatedUser.email_verified,
        created_at: updatedUser.created_at
      },
      message: 'Profile updated successfully'
    });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Failed to update profile');
  }
}

// ---------------------------------------------------------------------------
// POST /zen/api/users/profile/picture
// ---------------------------------------------------------------------------

async function handleUploadProfilePicture(request, _params, { session }) {
  try {
    const formData = await request.formData();
    const file = formData.get('file');

    if (!file) {
      return apiError('Bad Request', 'No file provided');
    }

    // Read the buffer once — used for both content inspection and the upload.
    const buffer = Buffer.from(await file.arrayBuffer());

    // Validate file — passes buffer for magic-byte / content-pattern inspection.
    const validation = validateUpload({
      filename: file.name,
      size: file.size,
      allowedTypes: FILE_TYPE_PRESETS.IMAGES,
      maxSize: FILE_SIZE_LIMITS.AVATAR,
      buffer,
    });

    if (!validation.valid) {
      return apiError('Bad Request', validation.errors.join(', '));
    }

    // Check for an existing profile picture to replace later.
    const currentUser = await query(
      'SELECT image FROM zen_auth_users WHERE id = $1',
      [session.user.id]
    );
    const oldImageKey = currentUser.rows[0]?.image ?? null;

    // Generate storage path.
    const uniqueFilename = generateUniqueFilename(file.name, 'avatar');
    const key = generateUserFilePath(session.user.id, 'profile', uniqueFilename);

    // Derive content-type from the validated extension — never from file.type,
    // which is fully attacker-controlled.
    const ext = getFileExtension(file.name).toLowerCase();
    const contentType = EXTENSION_TO_MIME[ext] ?? 'application/octet-stream';

    const uploadResult = await uploadImage({
      key,
      body: buffer,
      contentType,
      metadata: { userId: session.user.id, originalName: file.name }
    });

    if (!uploadResult.success) {
      return apiError('Internal Server Error', 'Failed to upload image');
    }

    // Commit the DB write first. Only on success do we remove the old object —
    // an orphaned old object is preferable to a broken DB reference.
    let updatedUser;
    try {
      updatedUser = await updateUser(session.user.id, { image: key });
    } catch (dbError) {
      // Roll back the newly uploaded object so storage stays clean.
      try {
        await deleteFile(key);
      } catch (rollbackError) {
        fail(`Rollback delete of newly uploaded object failed: ${rollbackError.message}`);
      }
      throw dbError;
    }

    if (oldImageKey) {
      try {
        await deleteFile(oldImageKey);
        info(`Deleted old profile picture: ${oldImageKey}`);
      } catch (deleteError) {
        // Non-fatal: log for operator cleanup; the DB reference is consistent.
        fail(`Failed to delete old profile picture (orphaned object): ${deleteError.message}`);
      }
    }

    return apiSuccess({
      success: true,
      user: {
        id: updatedUser.id,
        email: updatedUser.email,
        name: updatedUser.name,
        role: updatedUser.role,
        image: updatedUser.image,
        email_verified: updatedUser.email_verified,
        created_at: updatedUser.created_at
      },
      message: 'Profile picture uploaded successfully'
    });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Failed to upload profile picture');
  }
}

// ---------------------------------------------------------------------------
// DELETE /zen/api/users/profile/picture
// ---------------------------------------------------------------------------

async function handleDeleteProfilePicture(_request, _params, { session }) {
  try {
    const currentUser = await query(
      'SELECT image FROM zen_auth_users WHERE id = $1',
      [session.user.id]
    );

    if (currentUser.rows.length === 0) {
      return apiError('Not Found', 'User not found');
    }

    const imageKey = currentUser.rows[0].image;
    if (!imageKey) {
      return apiError('Bad Request', 'No profile picture to delete');
    }

    const updatedUser = await updateUser(session.user.id, { image: null });

    try {
      await deleteFile(imageKey);
      info(`Deleted profile picture: ${imageKey}`);
    } catch (deleteError) {
      // Non-fatal: the DB is already updated; log for operator cleanup.
      fail(`Failed to delete profile picture from storage: ${deleteError.message}`);
    }

    return apiSuccess({
      success: true,
      user: {
        id: updatedUser.id,
        email: updatedUser.email,
        name: updatedUser.name,
        role: updatedUser.role,
        image: updatedUser.image,
        email_verified: updatedUser.email_verified,
        created_at: updatedUser.created_at
      },
      message: 'Profile picture deleted successfully'
    });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Failed to delete profile picture');
  }
}

// ---------------------------------------------------------------------------
// GET /zen/api/users/:id/roles   (admin only)
// ---------------------------------------------------------------------------

async function handleGetUserRoles(_request, { id: userId }) {
  const roles = await getUserRoles(userId);
  return apiSuccess({ roles });
}

// ---------------------------------------------------------------------------
// POST /zen/api/users/:id/roles   (admin only)
// ---------------------------------------------------------------------------

async function handleAssignUserRole(request, { id: userId }) {
  try {
    const body = await request.json();
    const { roleId } = body;

    if (!roleId) return apiError('Bad Request', 'roleId is required');

    const roleCheck = await query(`SELECT id FROM zen_auth_roles WHERE id = $1`, [roleId]);
    if (roleCheck.rows.length === 0) return apiError('Not Found', 'Role not found');

    await assignUserRole(userId, roleId);
    return apiSuccess({ success: true });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Failed to assign role');
  }
}

// ---------------------------------------------------------------------------
// DELETE /zen/api/users/:id/roles/:roleId   (admin only)
// ---------------------------------------------------------------------------

async function handleRevokeUserRole(_request, { id: userId, roleId }, context) {
  try {
    if (context.session.user.id === userId) {
      const roleHasPerm = await query(
        `SELECT 1 FROM zen_auth_role_permissions WHERE role_id = $1 AND permission_key = $2`,
        [roleId, PERMISSIONS.USERS_MANAGE]
      );
      if (roleHasPerm.rows.length > 0) {
        const otherRoles = await query(
          `SELECT 1 FROM zen_auth_user_roles ur
           JOIN zen_auth_role_permissions rp ON rp.role_id = ur.role_id
           WHERE ur.user_id = $1 AND rp.permission_key = $2 AND ur.role_id != $3
           LIMIT 1`,
          [userId, PERMISSIONS.USERS_MANAGE, roleId]
        );
        if (otherRoles.rows.length === 0) {
          return apiError('Forbidden', "Vous ne pouvez pas retirer ce rôle car c'est votre seule source de la permission de gestion des utilisateurs.");
        }
      }
    }
    await revokeUserRole(userId, roleId);
    return apiSuccess({ success: true });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Failed to revoke role');
  }
}

// ---------------------------------------------------------------------------
// GET /zen/api/roles   (admin only)
// ---------------------------------------------------------------------------

async function handleListRoles() {
  const roles = await listRoles();
  return apiSuccess({ roles });
}

// ---------------------------------------------------------------------------
// GET /zen/api/permissions   (admin only)
// Catalogue dynamique : core + permissions enregistrées par les modules.
// ---------------------------------------------------------------------------

async function handleListPermissions() {
  const permissions = getRegisteredPermissions();
  const groups = permissions.reduce((acc, perm) => {
    if (!acc[perm.group_name]) acc[perm.group_name] = [];
    acc[perm.group_name].push(perm);
    return acc;
  }, {});
  return apiSuccess({ permissions, groups });
}

// ---------------------------------------------------------------------------
// POST /zen/api/roles   (admin only)
// ---------------------------------------------------------------------------

async function handleCreateRole(request) {
  try {
    const body = await request.json();
    const { name, description, color, permissionKeys } = body;

    if (!name || !String(name).trim()) {
      return apiError('Bad Request', 'Role name is required');
    }

    const role = await createRole({
      name: String(name).trim(),
      description: description ? String(description).trim() : null,
      color: color ? String(color) : '#6b7280'
    });

    if (Array.isArray(permissionKeys) && permissionKeys.length > 0) {
      const updatedRole = await updateRole(role.id, { permissionKeys });
      return apiSuccess({ role: updatedRole });
    }

    return apiSuccess({ role: { ...role, permission_keys: [] } });
  } catch (error) {
    if (error.message === 'Role name is required') return apiError('Bad Request', error.message);
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Failed to create role');
  }
}

// ---------------------------------------------------------------------------
// GET /zen/api/roles/:id   (admin only)
// ---------------------------------------------------------------------------

async function handleGetRole(_request, { id: roleId }) {
  const role = await getRoleById(roleId);
  if (!role) return apiError('Not Found', 'Role not found');
  return apiSuccess({ role });
}

// ---------------------------------------------------------------------------
// PUT /zen/api/roles/:id   (admin only)
// ---------------------------------------------------------------------------

async function handleUpdateRole(request, { id: roleId }) {
  try {
    const body = await request.json();
    const { name, description, color, permissionKeys } = body;

    const role = await updateRole(roleId, {
      ...(name !== undefined && { name: String(name).trim() }),
      ...(description !== undefined && { description: description ? String(description).trim() : null }),
      ...(color !== undefined && { color: String(color) }),
      ...(permissionKeys !== undefined && { permissionKeys: Array.isArray(permissionKeys) ? permissionKeys : [] })
    });

    return apiSuccess({ role });
  } catch (error) {
    if (error.message === 'Role not found') return apiError('Not Found', error.message);
    if (error.message === 'Role name cannot be empty') return apiError('Bad Request', error.message);
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Failed to update role');
  }
}

// ---------------------------------------------------------------------------
// DELETE /zen/api/roles/:id   (admin only)
// ---------------------------------------------------------------------------

async function handleDeleteRole(_request, { id: roleId }) {
  try {
    await deleteRole(roleId);
    return apiSuccess({ success: true });
  } catch (error) {
    if (error.message === 'Cannot delete a system role') return apiError('Bad Request', error.message);
    if (error.message === 'Role not found') return apiError('Not Found', error.message);
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Failed to delete role');
  }
}

// ---------------------------------------------------------------------------
// GET /zen/api/users/profile/sessions   (user — list own sessions)
// ---------------------------------------------------------------------------

function parseUserAgent(ua) {
  if (!ua) return { browser: 'Navigateur inconnu', os: 'Système inconnu', device: 'desktop' };
  const device = /Mobile|Android|iPhone|iPad/i.test(ua) ? 'mobile' : 'desktop';
  let os = 'Système inconnu';
  if (/Windows/i.test(ua)) os = 'Windows';
  else if (/Android/i.test(ua)) os = 'Android';
  else if (/iPhone|iPad/i.test(ua)) os = 'iOS';
  else if (/Mac OS X/i.test(ua)) os = 'macOS';
  else if (/Linux/i.test(ua)) os = 'Linux';
  let browser = 'Navigateur inconnu';
  if (/Edg\//i.test(ua)) browser = 'Edge';
  else if (/Chrome\//i.test(ua)) browser = 'Chrome';
  else if (/Firefox\//i.test(ua)) browser = 'Firefox';
  else if (/Safari\//i.test(ua)) browser = 'Safari';
  return { browser, os, device };
}

async function handleListSessions(_request, _params, { session }) {
  try {
    const result = await query(
      'SELECT id, ip_address, user_agent, created_at, expires_at FROM zen_auth_sessions WHERE user_id = $1 ORDER BY created_at DESC',
      [session.user.id]
    );
    const sessions = result.rows.map(s => ({
      id: s.id,
      ip_address: s.ip_address,
      created_at: s.created_at,
      expires_at: s.expires_at,
      ...parseUserAgent(s.user_agent),
    }));
    return apiSuccess({ sessions, currentSessionId: session.session.id });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Impossible de récupérer les sessions');
  }
}

// ---------------------------------------------------------------------------
// DELETE /zen/api/users/profile/sessions   (user — revoke all own sessions)
// ---------------------------------------------------------------------------

async function handleDeleteAllSessions(_request, _params, { session }) {
  try {
    await deleteUserSessions(session.user.id);
    return apiSuccess({ success: true });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Impossible de révoquer les sessions');
  }
}

// ---------------------------------------------------------------------------
// DELETE /zen/api/users/profile/sessions/:sessionId   (user — revoke one session)
// ---------------------------------------------------------------------------

async function handleDeleteSession(_request, { sessionId }, { session }) {
  try {
    const result = await query(
      'DELETE FROM zen_auth_sessions WHERE id = $1 AND user_id = $2 RETURNING id',
      [sessionId, session.user.id]
    );
    if (result.rows.length === 0) {
      return apiError('Not Found', 'Session introuvable');
    }
    return apiSuccess({ success: true, isCurrent: sessionId === session.session.id });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Impossible de révoquer la session');
  }
}

// ---------------------------------------------------------------------------
// POST /zen/api/users/profile/password   (user — change own password)
// ---------------------------------------------------------------------------

const PASSWORD_REGEX_UPPER = /[A-Z]/;
const PASSWORD_REGEX_LOWER = /[a-z]/;
const PASSWORD_REGEX_DIGIT = /\d/;

function validateNewPassword(password) {
  if (!password || password.length < 8) return 'Le mot de passe doit contenir au moins 8 caractères';
  if (password.length > 128) return 'Le mot de passe doit contenir 128 caractères ou moins';
  if (!PASSWORD_REGEX_UPPER.test(password)) return 'Le mot de passe doit contenir au moins une majuscule';
  if (!PASSWORD_REGEX_LOWER.test(password)) return 'Le mot de passe doit contenir au moins une minuscule';
  if (!PASSWORD_REGEX_DIGIT.test(password)) return 'Le mot de passe doit contenir au moins un chiffre';
  return null;
}

async function handleChangeOwnPassword(request, _params, { session }) {
  try {
    const body = await request.json();
    const { currentPassword, newPassword } = body;

    if (!currentPassword) return apiError('Bad Request', 'Le mot de passe actuel est requis');

    const passwordError = validateNewPassword(newPassword);
    if (passwordError) return apiError('Bad Request', passwordError);

    const account = await findOne('zen_auth_accounts', { user_id: session.user.id, provider_id: 'credential' });
    if (!account || !account.password) return apiError('Bad Request', 'Impossible de vérifier le mot de passe');

    const valid = await verifyPassword(currentPassword, account.password);
    if (!valid) return apiError('Unauthorized', 'Mot de passe actuel incorrect');

    const hashed = await hashPassword(newPassword);
    await updateById('zen_auth_accounts', account.id, { password: hashed, updated_at: new Date() });

    try {
      await sendPasswordChangedEmail(session.user.email);
    } catch (emailError) {
      fail(`handleChangeOwnPassword: failed to send notification: ${emailError.message}`);
    }

    return apiSuccess({ success: true, message: 'Mot de passe mis à jour avec succès' });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Impossible de mettre à jour le mot de passe');
  }
}

// ---------------------------------------------------------------------------
// PUT /zen/api/users/:id/password   (admin — set any user's password)
// ---------------------------------------------------------------------------

async function handleAdminSetUserPassword(request, { id: userId }) {
  try {
    const body = await request.json();
    const { newPassword } = body;

    const passwordError = validateNewPassword(newPassword);
    if (passwordError) return apiError('Bad Request', passwordError);

    const targetUser = await findOne('zen_auth_users', { id: userId });
    if (!targetUser) return apiError('Not Found', 'Utilisateur introuvable');

    const account = await findOne('zen_auth_accounts', { user_id: userId, provider_id: 'credential' });
    if (!account) return apiError('Not Found', 'Compte introuvable');

    const hashed = await hashPassword(newPassword);
    await updateById('zen_auth_accounts', account.id, { password: hashed, updated_at: new Date() });

    try {
      await sendPasswordChangedEmail(targetUser.email);
    } catch (emailError) {
      fail(`handleAdminSetUserPassword: failed to send notification: ${emailError.message}`);
    }

    return apiSuccess({ success: true, message: 'Mot de passe mis à jour' });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Impossible de mettre à jour le mot de passe');
  }
}

// ---------------------------------------------------------------------------
// POST /zen/api/users/:id/send-password-reset   (admin — send reset link)
// ---------------------------------------------------------------------------

async function handleAdminSendPasswordReset(_request, { id: userId }) {
  try {
    const targetUser = await findOne('zen_auth_users', { id: userId });
    if (!targetUser) return apiError('Not Found', 'Utilisateur introuvable');

    const result = await requestPasswordReset(targetUser.email);

    if (result.token) {
      await sendPasswordResetEmail(targetUser.email, result.token, getPublicBaseUrl());
    }

    return apiSuccess({ success: true, message: 'Lien de réinitialisation envoyé' });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Impossible d\'envoyer le lien de réinitialisation');
  }
}

// ---------------------------------------------------------------------------
// POST /zen/api/users   (admin only)
// ---------------------------------------------------------------------------

async function handleAdminCreateUser(request) {
  try {
    const body = await request.json();
    const { name, email, password, roleIds } = body;

    if (!name || typeof name !== 'string' || name.trim().length === 0) {
      return apiError('Bad Request', 'Le nom est requis');
    }

    if (name.trim().length > 100) {
      return apiError('Bad Request', 'Le nom doit contenir 100 caractères ou moins');
    }

    if (!email || !EMAIL_REGEX.test(email) || email.length > 254) {
      return apiError('Bad Request', 'Adresse courriel invalide');
    }

    const normalizedEmail = email.trim().toLowerCase();

    const existing = await findOne('zen_auth_users', { email: normalizedEmail });
    if (existing) {
      return apiError('Conflict', 'Cette adresse courriel est déjà utilisée');
    }

    const userId = generateId();
    const hasPassword = typeof password === 'string' && password.length > 0;

    const user = await create('zen_auth_users', {
      id: userId,
      email: normalizedEmail,
      name: name.trim(),
      email_verified: hasPassword,
      image: null,
      role: 'user',
      updated_at: new Date()
    });

    if (hasPassword) {
      const hashedPassword = await hashPassword(password);
      await create('zen_auth_accounts', {
        id: generateId(),
        account_id: normalizedEmail,
        provider_id: 'credential',
        user_id: user.id,
        password: hashedPassword,
        updated_at: new Date()
      });
    } else {
      const setup = await createAccountSetup(normalizedEmail);
      const baseUrl = getPublicBaseUrl();
      try {
        await sendInvitationEmail(normalizedEmail, setup.token, baseUrl);
      } catch (emailError) {
        fail(`handleAdminCreateUser: failed to send invitation email to ${normalizedEmail}: ${emailError.message}`);
      }
    }

    if (Array.isArray(roleIds) && roleIds.length > 0) {
      for (const roleId of roleIds) {
        if (typeof roleId === 'string' && roleId.length > 0) {
          try {
            await assignUserRole(user.id, roleId);
          } catch (err) {
            fail(`handleAdminCreateUser: failed to assign role ${roleId} to user ${user.id}: ${err.message}`);
          }
        }
      }
    }

    return apiSuccess({ user, invited: !hasPassword });
  } catch (error) {
    logAndObscureError(error, null);
    return apiError('Internal Server Error', 'Impossible de créer l\'utilisateur');
  }
}

// ---------------------------------------------------------------------------
// Route definitions
// ---------------------------------------------------------------------------
//
// Order matters: specific paths (/users/profile) must come before
// parameterised paths (/users/:id) so they match first.

export const routes = defineApiRoutes([
  { path: '/users',                         method: 'GET',    handler: handleListUsers,              auth: 'admin', permission: PERMISSIONS.USERS_VIEW   },
  { path: '/users',                         method: 'POST',   handler: handleAdminCreateUser,        auth: 'admin', permission: PERMISSIONS.USERS_MANAGE   },
  { path: '/users/profile',                 method: 'PUT',    handler: handleUpdateProfile,          auth: 'user'  },
  { path: '/users/profile/email',           method: 'POST',   handler: handleInitiateEmailChange,    auth: 'user'  },
  { path: '/users/profile/password',        method: 'POST',   handler: handleChangeOwnPassword,      auth: 'user'  },
  { path: '/users/profile/picture',         method: 'POST',   handler: handleUploadProfilePicture,   auth: 'user'  },
  { path: '/users/profile/picture',         method: 'DELETE', handler: handleDeleteProfilePicture,   auth: 'user'  },
  { path: '/users/profile/sessions',        method: 'GET',    handler: handleListSessions,           auth: 'user'  },
  { path: '/users/profile/sessions',        method: 'DELETE', handler: handleDeleteAllSessions,      auth: 'user'  },
  { path: '/users/profile/sessions/:sessionId', method: 'DELETE', handler: handleDeleteSession,      auth: 'user'  },
  { path: '/users/email/confirm',           method: 'GET',    handler: handleConfirmEmailChange,     auth: 'user'  },
  { path: '/users/:id/roles',               method: 'GET',    handler: handleGetUserRoles,           auth: 'admin', permission: PERMISSIONS.USERS_VIEW   },
  { path: '/users/:id/roles',               method: 'POST',   handler: handleAssignUserRole,         auth: 'admin', permission: PERMISSIONS.USERS_MANAGE   },
  { path: '/users/:id/roles/:roleId',       method: 'DELETE', handler: handleRevokeUserRole,         auth: 'admin', permission: PERMISSIONS.USERS_MANAGE   },
  { path: '/users/:id',                     method: 'GET',    handler: handleGetUserById,            auth: 'admin', permission: PERMISSIONS.USERS_VIEW   },
  { path: '/users/:id',                     method: 'PUT',    handler: handleUpdateUserById,          auth: 'admin', permission: PERMISSIONS.USERS_MANAGE   },
  { path: '/users/:id/email',               method: 'PUT',    handler: handleAdminUpdateUserEmail,    auth: 'admin', permission: PERMISSIONS.USERS_MANAGE   },
  { path: '/users/:id/password',            method: 'PUT',    handler: handleAdminSetUserPassword,    auth: 'admin', permission: PERMISSIONS.USERS_MANAGE   },
  { path: '/users/:id/send-password-reset', method: 'POST',   handler: handleAdminSendPasswordReset,  auth: 'admin', permission: PERMISSIONS.USERS_MANAGE   },
  { path: '/roles',                         method: 'GET',    handler: handleListRoles,              auth: 'admin', permission: PERMISSIONS.ROLES_VIEW   },
  { path: '/permissions',                   method: 'GET',    handler: handleListPermissions,        auth: 'admin', permission: PERMISSIONS.ROLES_VIEW   },
  { path: '/roles',                         method: 'POST',   handler: handleCreateRole,             auth: 'admin', permission: PERMISSIONS.ROLES_MANAGE },
  { path: '/roles/:id',                     method: 'GET',    handler: handleGetRole,                auth: 'admin', permission: PERMISSIONS.ROLES_VIEW   },
  { path: '/roles/:id',                     method: 'PUT',    handler: handleUpdateRole,             auth: 'admin', permission: PERMISSIONS.ROLES_MANAGE },
  { path: '/roles/:id',                     method: 'DELETE', handler: handleDeleteRole,             auth: 'admin', permission: PERMISSIONS.ROLES_MANAGE },
]);
