/**
 * Global Error Handler Middleware
 * 
 * Enterprise-grade error handling with:
 * - Structured logging
 * - Correlation ID tracking
 * - Error classification
 * - Prisma error mapping
 * - Production vs development responses
 */

import type { Request, Response, NextFunction } from 'express';
import { AppError, isOperationalError, mapPrismaError, ErrorCode } from '../../core/errors';
import { logger } from '../utils/logger';

/**
 * Extended Request with correlation ID
 */
export interface RequestWithContext extends Request {
  correlationId: string;
  user?: { id: string; role: string };
}

/**
 * Global error handler
 * MUST be the last middleware in the chain
 */
export function errorHandler(
  err: Error,
  req: RequestWithContext,
  res: Response,
  _next: NextFunction
): void {
  const correlationId = req.correlationId;
  
  // Map known errors to AppError
  let appError: AppError;
  
  if (err instanceof AppError) {
    appError = err;
  } else if (err.name?.includes('Prisma') || err.message?.includes('P20')) {
    appError = mapPrismaError(err, correlationId);
  } else {
    // Unknown error - wrap as internal error
    appError = new AppError(
      err.message || 'An unexpected error occurred',
      ErrorCode.INTERNAL_ERROR,
      500,
      false,
      { originalError: err.stack },
      correlationId
    );
  }

  // Log error with full context
  const logData = {
    error: appError.message,
    code: appError.code,
    statusCode: appError.statusCode,
    stack: appError.stack,
    correlationId,
    url: req.originalUrl || req.url,
    method: req.method,
    ip: req.ip,
    userAgent: req.headers['user-agent'],
    userId: req.user?.id,
    ...appError.metadata,
  };

  if (appError.isOperational) {
    logger.warn(logData, `Operational Error: ${appError.code}`);
  } else {
    logger.error(logData, `Unexpected Error: ${err.message}`);
  }

  // Send response
  const response: Record<string, unknown> = {
    success: false,
    error: {
      code: appError.code,
      message: appError.isOperational 
        ? appError.message 
        : 'An internal server error occurred',
      correlationId,
    },
  };

  // Include additional details in development
  if (process.env.NODE_ENV === 'development') {
    response.error = {
      ...response.error,
      message: appError.message,
      stack: appError.stack,
      isOperational: appError.isOperational,
      metadata: appError.metadata,
    };
  }

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

/**
 * 404 Not Found handler
 */
export function notFoundHandler(
  req: RequestWithContext,
  res: Response,
  _next: NextFunction
): void {
  const correlationId = req.correlationId;
  
  logger.warn({
    url: req.originalUrl || req.url,
    method: req.method,
    correlationId,
    ip: req.ip,
  }, '404 Not Found');

  res.status(404).json({
    success: false,
    error: {
      code: ErrorCode.NOT_FOUND,
      message: `Route ${req.method} ${req.originalUrl} not found`,
      correlationId,
    },
  });
}

/**
 * Async handler wrapper
 * Automatically catches errors in async route handlers
 */
export function asyncHandler(
  fn: (req: RequestWithContext, res: Response, next: NextFunction) => Promise<unknown>
) {
  return (req: RequestWithContext, res: Response, next: NextFunction): void => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
}
