/**
 * 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, updateById } from '@zen/core/database';
import { updateUser } from './lib/auth.js';
import { listRoles, getRoleById, createRole, updateRole, deleteRole, getUserRoles, assignUserRole, revokeUserRole } from '@zen/core/users';
import { uploadImage, deleteFile, generateUniqueFilename, getFileExtension, FILE_TYPE_PRESETS, FILE_SIZE_LIMITS, validateUpload } from '@zen/core/storage';

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/me
// ---------------------------------------------------------------------------

async function handleGetCurrentUser(_request, _params, { session }) {
  return apiSuccess({
    user: {
      id: session.user.id,
      email: session.user.email,
      name: session.user.name,
      role: session.user.role,
      image: session.user.image,
      emailVerified: session.user.email_verified,
      createdAt: session.user.created_at
    }
  });
}

// ---------------------------------------------------------------------------
// 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 id, email, name, role, image, email_verified, created_at FROM zen_auth_users ORDER BY ${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) }
  });
}

// ---------------------------------------------------------------------------
// 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 }) {
  try {
    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 });
}

// ---------------------------------------------------------------------------
// 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');
  }
}

// ---------------------------------------------------------------------------
// Route definitions
// ---------------------------------------------------------------------------
//
// Order matters: specific paths (/users/me, /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' },
  { path: '/users/me',                      method: 'GET',    handler: handleGetCurrentUser,         auth: 'user'  },
  { path: '/users/profile',                 method: 'PUT',    handler: handleUpdateProfile,          auth: 'user'  },
  { path: '/users/profile/picture',         method: 'POST',   handler: handleUploadProfilePicture,   auth: 'user'  },
  { path: '/users/profile/picture',         method: 'DELETE', handler: handleDeleteProfilePicture,   auth: 'user'  },
  { path: '/users/:id/roles',               method: 'GET',    handler: handleGetUserRoles,           auth: 'admin' },
  { path: '/users/:id/roles',               method: 'POST',   handler: handleAssignUserRole,         auth: 'admin' },
  { path: '/users/:id/roles/:roleId',       method: 'DELETE', handler: handleRevokeUserRole,         auth: 'admin' },
  { path: '/users/:id',                     method: 'GET',    handler: handleGetUserById,             auth: 'admin' },
  { path: '/users/:id',                     method: 'PUT',    handler: handleUpdateUserById,          auth: 'admin' },
  { path: '/roles',                         method: 'GET',    handler: handleListRoles,              auth: 'admin' },
  { path: '/roles',                         method: 'POST',   handler: handleCreateRole,             auth: 'admin' },
  { path: '/roles/:id',                     method: 'GET',    handler: handleGetRole,                auth: 'admin' },
  { path: '/roles/:id',                     method: 'PUT',    handler: handleUpdateRole,             auth: 'admin' },
  { path: '/roles/:id',                     method: 'DELETE', handler: handleDeleteRole,             auth: 'admin' },
]);
