// src/middleware/errorHandler.ts
// ============================================================================
// CENTRALIZED ERROR HANDLING MIDDLEWARE
// ============================================================================
// This middleware standardizes all error responses per ADR-028.
// It should be the LAST `app.use()` call to catch all errors.
// ============================================================================

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';
import { ErrorCode, ApiErrorResponse } from '../types/api';

/**
 * Helper to send standardized error responses.
 */
function sendErrorResponse(
  res: Response,
  statusCode: number,
  code: string,
  message: string,
  details?: unknown,
  meta?: { requestId?: string; timestamp?: string },
): Response<ApiErrorResponse> {
  const response: ApiErrorResponse = {
    success: false,
    error: {
      code,
      message,
    },
  };

  if (details !== undefined) {
    response.error.details = details;
  }

  if (meta) {
    response.meta = meta;
  }

  return res.status(statusCode).json(response);
}

/**
 * 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 per ADR-028 and ensures consistent logging per ADR-004.
 */
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 details = err.issues.map((e) => ({ path: e.path, message: e.message }));
    log.warn(
      { err, validationErrors: details, statusCode },
      `Client Error on ${req.method} ${req.path}: ${message}`,
    );
    return sendErrorResponse(res, statusCode, ErrorCode.VALIDATION_ERROR, message, details);
  }

  // --- 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 sendErrorResponse(res, statusCode, ErrorCode.NOT_FOUND, 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 sendErrorResponse(
      res,
      statusCode,
      ErrorCode.VALIDATION_ERROR,
      err.message,
      err.validationErrors,
    );
  }

  if (err instanceof UniqueConstraintError) {
    const statusCode = 409;
    log.warn({ err, statusCode }, `Client Error on ${req.method} ${req.path}: ${err.message}`);
    return sendErrorResponse(res, statusCode, ErrorCode.CONFLICT, err.message);
  }

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

  // --- Handle Generic Client Errors (e.g., from express-jwt, or manual status setting) ---
  const errWithStatus = err as Error & { status?: number; statusCode?: number };
  let status = errWithStatus.status || errWithStatus.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}`,
    );

    // Map status codes to error codes
    let errorCode: string;
    switch (status) {
      case 400:
        errorCode = ErrorCode.BAD_REQUEST;
        break;
      case 401:
        errorCode = ErrorCode.UNAUTHORIZED;
        break;
      case 403:
        errorCode = ErrorCode.FORBIDDEN;
        break;
      case 404:
        errorCode = ErrorCode.NOT_FOUND;
        break;
      case 409:
        errorCode = ErrorCode.CONFLICT;
        break;
      case 429:
        errorCode = ErrorCode.RATE_LIMITED;
        break;
      default:
        errorCode = ErrorCode.BAD_REQUEST;
    }

    return sendErrorResponse(res, status, errorCode, 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/staging environments for visibility in test runners
  if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'staging') {
    console.error(
      `--- [${process.env.NODE_ENV?.toUpperCase()}] UNHANDLED ERROR (ID: ${errorId}) ---`,
      err,
    );
  }

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

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