/**
 * Error handler middleware for Express applications.
 * Handles different types of errors and sends appropriate responses.
 */

// Import necessary modules
const { JsonWebTokenError } = require('jsonwebtoken');
const { validationResult } = require('express-validator');

/**
 * Custom error class for handling application-specific errors
 */
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    this.isOperational = true;
    Error.captureStackTrace(this, this.constructor);
  }
}

/**
 * Error handling middleware
 * @param {Error} err - The error object
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 * @param {Function} next - Express next middleware function
 */
const errorHandler = (err, req, res, next) => {
  let error = { ...err };
  error.message = err.message;
  
  // If it's already an instance of AppError or ErrorResponse, use it directly
  if (err.statusCode && (err.status === 'fail' || err.status === 'error')) {
    error = err;
  }

  // Log to console for dev
  console.error('Error Handler:', {
    name: err.name,
    message: err.message,
    stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
    code: err.code,
    statusCode: err.statusCode,
    path: req.path,
    method: req.method,
    body: process.env.NODE_ENV === 'development' ? req.body : undefined,
    params: process.env.NODE_ENV === 'development' ? req.params : undefined,
    query: process.env.NODE_ENV === 'development' ? req.query : undefined,
  });

  // Handle specific error types
  // Mongoose bad ObjectId
  if (err.name === 'CastError') {
    const message = `Resource not found with id of ${err.value}`;
    error = new AppError(message, 404);
  }

  // Mongoose duplicate key
  if (err.code === 11000) {
    const field = Object.keys(err.keyValue)[0];
    const message = `Duplicate field value: ${field} '${err.keyValue[field]}' already exists`;
    error = new AppError(message, 400);
  }

  // Mongoose validation error
  if (err.name === 'ValidationError') {
    const message = Object.values(err.errors).map(val => val.message).join('. ');
    error = new AppError(`Validation Error: ${message}`, 400);
  }

  // JWT errors
  if (err.name === 'JsonWebTokenError') {
    const message = 'Invalid token';
    error = new AppError(message, 401);
  }

  if (err.name === 'TokenExpiredError') {
    const message = 'Token has expired';
    error = new AppError(message, 401);
  }
  
  // Handle express-validator errors
  if (err.errors && Array.isArray(err.errors)) {
    const message = err.errors.map(e => e.msg).join('. ');
    error = new AppError(message || 'Validation failed', 400);
  }
  
  // Send response
  res.status(error.statusCode || 500).json({
    success: false,
    error: error.message || 'Server Error',
    stack: process.env.NODE_ENV === 'development' ? err.stack : {}
  });
};

/**
 * 404 Not Found handler
 */
const notFound = (req, res, next) => {
  const error = new AppError(`Not Found - ${req.originalUrl}`, 404);
  next(error);
};

/**
 * Catch async errors in Express routes
 * @param {Function} fn - The async route handler function
 * @returns {Function} - A new function that handles async errors
 */
const catchAsync = fn => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

module.exports = {
  AppError,
  errorHandler,
  notFound,
  catchAsync
};
