import { Request, Response, NextFunction } from 'express';
import { PrismaClientKnownRequestError, PrismaClientValidationError } from '@prisma/client/runtime/library';
import { ZodError } from 'zod';
import logger from '../utils/logger';

export interface AppError extends Error {
  statusCode?: number;
  isOperational?: boolean;
}

export const createError = (message: string, statusCode: number = 500): AppError => {
  const error: AppError = new Error(message);
  error.statusCode = statusCode;
  error.isOperational = true;
  return error;
};

export const asyncHandler = (fn: Function) => {
  return (req: Request, res: Response, next: NextFunction) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
};

export const errorHandler = (
  err: AppError | ZodError | PrismaClientKnownRequestError | Error,
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const startTime = Date.now();
  
  let statusCode = 500;
  let message = 'Internal Server Error';
  let details: any = undefined;
  let errorType = 'Unknown';

  // Log the error with comprehensive context
  logger.error('💥 Error occurred', {
    error: err.message,
    stack: err.stack,
    url: req.url,
    method: req.method,
    ip: req.ip,
    userAgent: req.get('User-Agent'),
    origin: req.get('Origin'),
    referer: req.get('Referer'),
    contentType: req.get('Content-Type'),
    contentLength: req.get('Content-Length'),
    hasAuthHeader: !!req.headers.authorization,
    hasCookies: !!req.cookies,
    requestTime: new Date().toISOString()
  });

  // Handle different types of errors
  if (err instanceof ZodError) {
    errorType = 'ValidationError';
    statusCode = 400;
    message = 'Validation Error';
    details = err.issues.map(error => ({
      field: error.path.join('.'),
      message: error.message,
      code: error.code,
    }));
    
    logger.warn('⚠️ Validation error', {
      errorType,
      statusCode,
      errorCount: err.issues.length,
      fields: err.issues.map(e => e.path.join('.')),
      messages: err.issues.map(e => e.message),
      url: req.url,
      method: req.method,
      ip: req.ip
    });
  } else if (err instanceof PrismaClientKnownRequestError) {
    errorType = 'PrismaError';
    
    logger.error('🗄️ Database error', {
      errorType,
      prismaCode: err.code,
      prismaMessage: err.message,
      meta: err.meta,
      url: req.url,
      method: req.method,
      ip: req.ip
    });
    
    // Prisma errors
    switch (err.code) {
      case 'P2002':
        statusCode = 409;
        message = 'Unique constraint violation';
        details = `The ${err.meta?.target} already exists`;
        logger.warn('⚠️ Unique constraint violation', {
          target: err.meta?.target,
          statusCode,
          url: req.url,
          method: req.method
        });
        break;
      case 'P2025':
        statusCode = 404;
        message = 'Record not found';
        logger.warn('⚠️ Record not found', {
          statusCode,
          url: req.url,
          method: req.method
        });
        break;
      case 'P2003':
        statusCode = 400;
        message = 'Foreign key constraint violation';
        logger.warn('⚠️ Foreign key constraint violation', {
          statusCode,
          url: req.url,
          method: req.method
        });
        break;
      case 'P2014':
        statusCode = 400;
        message = 'The change you are trying to make would violate the required relation';
        logger.warn('⚠️ Relation violation', {
          statusCode,
          url: req.url,
          method: req.method
        });
        break;
      case 'P2001':
        statusCode = 404;
        message = 'Record not found';
        logger.warn('⚠️ Record not found (P2001)', {
          statusCode,
          url: req.url,
          method: req.method
        });
        break;
      default:
        statusCode = 400;
        message = 'Database error';
        details = err.message;
        logger.error('❌ Unknown Prisma error', {
          code: err.code,
          message: err.message,
          meta: err.meta,
          statusCode,
          url: req.url,
          method: req.method
        });
    }
  } else if (err instanceof PrismaClientValidationError) {
    errorType = 'PrismaValidationError';
    statusCode = 400;
    message = 'Database validation error';
    details = err.message;
    
    logger.error('❌ Prisma validation error', {
      errorType,
      message: err.message,
      statusCode,
      url: req.url,
      method: req.method,
      ip: req.ip
    });
  } else if ((err as AppError).isOperational) {
    errorType = 'OperationalError';
    statusCode = (err as AppError).statusCode || 500;
    message = err.message;
    
    logger.warn('⚠️ Operational error', {
      errorType,
      message: err.message,
      statusCode,
      url: req.url,
      method: req.method,
      ip: req.ip
    });
  } else if (err.name === 'JsonWebTokenError') {
    errorType = 'JWTError';
    statusCode = 401;
    message = 'Invalid token';
    
    logger.warn('⚠️ JWT error', {
      errorType,
      message: err.message,
      statusCode,
      url: req.url,
      method: req.method,
      ip: req.ip
    });
  } else if (err.name === 'TokenExpiredError') {
    errorType = 'JWTExpiredError';
    statusCode = 401;
    message = 'Token expired';
    
    logger.warn('⚠️ JWT expired error', {
      errorType,
      message: err.message,
      statusCode,
      url: req.url,
      method: req.method,
      ip: req.ip
    });
  } else if (err.name === 'CastError') {
    errorType = 'CastError';
    statusCode = 400;
    message = 'Invalid ID format';
    
    logger.warn('⚠️ Cast error', {
      errorType,
      message: err.message,
      statusCode,
      url: req.url,
      method: req.method,
      ip: req.ip
    });
  } else if (err.name === 'ValidationError') {
    errorType = 'ValidationError';
    statusCode = 400;
    message = 'Validation error';
    details = err.message;
    
    logger.warn('⚠️ Validation error', {
      errorType,
      message: err.message,
      statusCode,
      url: req.url,
      method: req.method,
      ip: req.ip
    });
  } else {
    // Unknown error
    logger.error('❌ Unknown error type', {
      errorType: 'Unknown',
      errorName: err.name,
      message: err.message,
      statusCode,
      url: req.url,
      method: req.method,
      ip: req.ip,
      stack: err.stack
    });
  }

  // Don't leak error details in production
  if (process.env.NODE_ENV === 'production' && statusCode === 500) {
    message = 'Something went wrong';
    details = undefined;
    
    logger.info('🔒 Sanitizing error for production', {
      originalMessage: err.message,
      sanitizedMessage: message,
      statusCode
    });
  }

  const totalTime = Date.now() - startTime;
  
  logger.info('📤 Sending error response', {
    errorType,
    statusCode,
    message,
    hasDetails: !!details,
    url: req.url,
    method: req.method,
    totalTime: `${totalTime}ms`
  });

  res.status(statusCode).json({
    success: false,
    error: {
      message,
      ...(details && { details }),
      ...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
    },
  });
};

