// src/middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
import crypto from 'crypto';
import { ZodError } from 'zod';
import {
  ForeignKeyConstraintError,
  NotFoundError,
  UniqueConstraintError,
  ValidationError,
} from '../services/db/errors.db';
import { logger } from '../services/logger.server';

/**
 * A centralized error handling middleware for the Express application.
 * This middleware should be the LAST `app.use()` call to catch all errors from previous routes and middleware.
 *
 * It standardizes error responses and ensures consistent logging.
 */
export const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
  // If headers have already been sent, delegate to the default Express error handler.
  if (res.headersSent) {
    return next(err);
  }

  // Use the request-scoped logger if available, otherwise fall back to the global logger.
  const log = req.log || logger;

  // --- Handle Zod Validation Errors (from validateRequest middleware) ---
  if (err instanceof ZodError) {
    const statusCode = 400;
    const message = 'The request data is invalid.';
    const errors = err.issues.map((e) => ({ path: e.path, message: e.message }));
    log.warn({ err, validationErrors: errors, statusCode }, `Client Error on ${req.method} ${req.path}: ${message}`);
    return res.status(statusCode).json({ message, errors });
  }

  // --- Handle Custom Operational Errors ---
  if (err instanceof NotFoundError) {
    const statusCode = 404;
    log.warn({ err, statusCode }, `Client Error on ${req.method} ${req.path}: ${err.message}`);
    return res.status(statusCode).json({ message: err.message });
  }

  if (err instanceof ValidationError) {
    const statusCode = 400;
    log.warn(
      { err, validationErrors: err.validationErrors, statusCode },
      `Client Error on ${req.method} ${req.path}: ${err.message}`,
    );
    return res.status(statusCode).json({ message: err.message, errors: err.validationErrors });
  }

  if (err instanceof UniqueConstraintError) {
    const statusCode = 409;
    log.warn({ err, statusCode }, `Client Error on ${req.method} ${req.path}: ${err.message}`);
    return res.status(statusCode).json({ message: err.message }); // Use 409 Conflict for unique constraints
  }

  if (err instanceof ForeignKeyConstraintError) {
    const statusCode = 400;
    log.warn({ err, statusCode }, `Client Error on ${req.method} ${req.path}: ${err.message}`);
    return res.status(statusCode).json({ message: err.message });
  }

  // --- Handle Generic Client Errors (e.g., from express-jwt, or manual status setting) ---
  let status = (err as any).status || (err as any).statusCode;
  // Default UnauthorizedError to 401 if no status is present, a common case for express-jwt.
  if (err.name === 'UnauthorizedError' && !status) {
    status = 401;
  }
  if (status && status >= 400 && status < 500) {
    log.warn({ err, statusCode: status }, `Client Error on ${req.method} ${req.path}: ${err.message}`);
    return res.status(status).json({ message: err.message });
  }

  // --- Handle All Other (500-level) Errors ---
  const errorId = crypto.randomBytes(4).toString('hex');
  log.error(
    {
      err,
      errorId,
      req: { method: req.method, url: req.url, headers: req.headers, body: req.body },
    },
    `Unhandled API Error (ID: ${errorId})`,
  );

  // Also log to console in test environment for visibility in test runners
  if (process.env.NODE_ENV === 'test') {
    console.error(`--- [TEST] UNHANDLED ERROR (ID: ${errorId}) ---`, err);
  }

  // In production, send a generic message to avoid leaking implementation details.
  if (process.env.NODE_ENV === 'production') {
    return res.status(500).json({
      message: `An unexpected server error occurred. Please reference error ID: ${errorId}`,
    });
  }

  // In non-production environments (dev, test, etc.), send more details for easier debugging.
  return res.status(500).json({ message: err.message, stack: err.stack, errorId });
};