Source: controllers/users.controller.js

import env from "../config/env.js";

import passport from "passport";

import logger from "../config/logger.js";
import { signToken } from "../services/jwtService.js";
import getCookieOptions from "../utils/getCookieOptions.js";
import { formatResponse } from "../utils/responseFormatter.js";
import { createLocalUser, findUserByEmail } from "../models/user.model.js";

/**
 * @route   POST /signup
 * @desc    Registers a new user with email, password, and optional displayName
 * @access  Public
 *
 * Workflow:
 * - Checks for an existing user via email lookup
 * - Delegates password hashing to Mongoose pre-save middleware
 * - Validates input via Mongoose schema before save
 * - Logs lifecycle events and errors using Winston
 * - Responds with newly created user ID or a generic error message
 */

async function registerUser(req, res) {
  const { email, password, displayName } = req.body;
  try {
    const existingUser = await findUserByEmail(email);
    if (existingUser) {
      logger.warn(`[registerUser] Email already exists: ${email}`);
      return res.status(400).json(
        formatResponse({
          success: false,
          message: "Invalid user credentials",
        })
      );
    }
    const user = await createLocalUser({ email, password, displayName });
    req.user = user;
    return finalizeAuth(req, res);
  } catch (error) {
    logger.error(`[registerUser] ${error.message}`);
    res.status(500).json(
      formatResponse({
        success: false,
        message: "Failed to register user",
      })
    );
  }
}

/**
 * @route   POST /login
 * @desc    Authenticates a user and issues a signed JWT
 * @access  Public
 *
 * Workflow:
 * - Invokes Passport Local Strategy with a custom callback
 * - Validates credentials via schema-defined comparePassword method
 * - On success, generates JWT with user ID and role as payload
 * - Logs outcome and returns token for client-side usage
 * - Gracefully handles failures and token generation errors
 */
async function loginUser(req, res, next) {
  passport.authenticate("local", { session: false }, (err, user, info) => {
    if (err || !user) {
      logger.warn(`[loginUser] Login failed: ${info?.message || err.message}`);
      return res.status(401).json({ message: "Invalid credentials" });
    }

    try {
      req.user = user;
      return finalizeAuth(req, res);
    } catch (error) {
      logger.error(`[loginUser] User JWT generation failed: ${error.message}`);
      return res.status(500).json({ error: "Token generation error" });
    }
  })(req, res, next);
}

/**
 * @route   GET /auth/google/callback
 * @desc    Finalizes Google OAuth flow and issues token
 * @access  Public
 *
 * Workflow:
 * - Invoked after successful Passport Google authentication
 * - Delegates token generation to `finalizeAuth` helper
 * - Redirects user to frontend with token in query string
 * - Handles errors with structured response and logging
 *
 * @returns {void}
 */
export function handleGoogleCallback(req, res) {
  try {
    finalizeAuth(req, res, { redirectUrl: env.GOOGLE_REDIRECT_URL });
  } catch (error) {
    logger.error(`[GoogleCallback] Token issuance failed: ${error.message}`);
    res.status(500).json(
      formatResponse({
        success: false,
        message: "Failed to issue token",
        error: "OAuth token error",
      })
    );
  }
}

/**
 * @route   GET /me
 * @desc    Retrieves the currently authenticated user
 * @access  Private
 *
 * Workflow:
 * - Retrieves the user object from the request
 * - Validates the user object
 * - Returns the user object in a standardized format
 * - Handles unauthorized access with a 401 response
 *
 * @returns {Promise<Object>} User data in a standardized format
 */
export async function getCurrentUser(req, res) {
  const user = req.user;

  if (!user) {
    return res.status(401).json(
      formatResponse({
        success: false,
        message: "User not authenticated",
      })
    );
  }

  return res.status(200).json(
    formatResponse({
      message: "Authenticated user retrieved successfully",
      data: {
        user: {
          userId: user._id,
          email: user.email,
          displayName: user.displayName,
        },
      },
    })
  );
}

/**
 * Helper function to finalize authentication workflow
 * - Generates a JWT with user ID and role as payload
 * - In production, sends an http-only cookie with the token
 * - In development, returns a JSON response with the token and basic user data
 * - Handles errors with structured response and logging
 *
 * @param {Object} req   - Express request object
 * @param {Object} res   - Express response object
 * @param {Object} [options] - Optional configuration object
 * @param {String} [options.redirectUrl] - Redirect URL for production; defaults to false
 * @returns {void}
 */
function finalizeAuth(req, res, options = {}) {
  const token = signToken({ id: req.user._id, role: req.user.role });

  const cookieOptions = getCookieOptions();

  const user = {
    userId: req.user._id,
    email: req.user.email,
    displayName: req.user.displayName,
  };

  const isDev = env.NODE_ENV === "development";

  try {
    res.cookie("auth_token", token, cookieOptions);

    if (options.redirectUrl) {
      return res.redirect(options.redirectUrl);
    }

    if (isDev) logger.info(`[finalizeAuth] Token issued: ${token}`);

    return res.status(200).json(
      formatResponse({
        message: "User authenticated successfully",
        data: { user },
      })
    );
  } catch (error) {
    logger.error(`[finalizeAuth] Token delivery failed: ${error.message}`);
    return res.status(500).json(
      formatResponse({
        success: false,
        message: "Failed to issue token",
        error: "Authentication error",
      })
    );
  }
}

export { registerUser, loginUser };