import { Request, Response, NextFunction } from "express"
import * as Sentry from "@sentry/node"
import { logger, LogContext } from "../utils/logger.js"

// Simple error handling without categories

// Simple error classes
export class ApiError extends Error {
  public readonly statusCode: number
  public readonly isOperational: boolean
  public readonly context?: Record<string, any>

  constructor(
    message: string,
    statusCode: number = 500,
    isOperational: boolean = true,
    context?: Record<string, any>,
  ) {
    super(message)
    this.name = this.constructor.name
    this.statusCode = statusCode
    this.isOperational = isOperational
    this.context = context

    // Maintains proper stack trace for where our error was thrown
    Error.captureStackTrace(this, this.constructor)
  }
}

// Validation errors
export class ValidationError extends ApiError {
  constructor(message: string, context?: Record<string, any>) {
    super(message, 400, true, context)
  }
}

// Authentication errors
export class AuthenticationError extends ApiError {
  constructor(message: string = "Authentication failed", context?: Record<string, any>) {
    super(message, 401, true, context)
  }
}

// Authorization errors
export class AuthorizationError extends ApiError {
  constructor(message: string = "Access denied", context?: Record<string, any>) {
    super(message, 403, true, context)
  }
}

// Not found errors
export class NotFoundError extends ApiError {
  constructor(message: string = "Resource not found", context?: Record<string, any>) {
    super(message, 404, true, context)
  }
}

// Rate limit errors
export class RateLimitError extends ApiError {
  constructor(message: string = "Rate limit exceeded", context?: Record<string, any>) {
    super(message, 429, true, context)
  }
}

// External service errors
export class ExternalServiceError extends ApiError {
  constructor(service: string, message?: string, context?: Record<string, any>) {
    super(message || `External service error: ${service}`, 502, true, { service, ...context })
  }
}

// Database errors
export class DatabaseError extends ApiError {
  constructor(message: string, context?: Record<string, any>) {
    super(message, 500, false, context)
  }
}

// Configuration errors
export class ConfigurationError extends ApiError {
  constructor(message: string, context?: Record<string, any>) {
    super(message, 500, false, context)
  }
}

// Business logic errors
export class BusinessLogicError extends ApiError {
  constructor(message: string, context?: Record<string, any>) {
    super(message, 400, true, context)
  }
}

// Error response interface
interface ErrorResponse {
  error: {
    message: string
    code: string
    statusCode: number
    requestId?: string
    timestamp: string
    context?: Record<string, any>
  }
}

// Global error handler class
export class GlobalErrorHandler {
  /**
   * Main error handling middleware
   * Should be registered as the last middleware in the Express app
   */
  static handle(error: Error, req: Request, res: Response, next: NextFunction): void {
    // If response was already sent, delegate to default Express error handler
    if (res.headersSent) {
      return next(error)
    }

    // Create error context for logging
    const errorContext: LogContext = {
      requestId: req.requestId,
      traceId: req.traceId,
      userId: (req as any).userId,
      endpoint: req.path,
      operation: "error_handling",
    }

    // Handle different types of errors
    // ==========================================
    // ERROR TYPE CLASSIFICATION & SENTRY BEHAVIOR:
    // ==========================================
    //
    // 1. ApiError (Custom Error Classes):
    //    - ValidationError, AuthenticationError, DatabaseError, etc.
    //    - Sentry: ✅ 5xx errors sent to Sentry, ❌ 4xx errors NOT sent
    //    - Production Logs: ERROR level with statusCode, duration, error details
    //    - Examples: DatabaseError(500) → Sentry, ValidationError(400) → No Sentry
    //
    // 2. Operational Errors (Expected Errors):
    //    - User input errors, business logic errors, expected failures
    //    - Sentry: ❌ NEVER sent to Sentry (expected behavior)
    //    - Production Logs: WARN level with basic error info
    //    - Examples: Invalid JSON, missing required fields, business rule violations
    //
    // 3. Programming Errors (Unexpected Errors):
    //    - Unhandled exceptions, bugs, infrastructure failures
    //    - Sentry: ✅ ALWAYS sent to Sentry (critical issues)
    //    - Production Logs: ERROR level with full stack trace and error details
    //    - Examples: Null pointer exceptions, unhandled promise rejections, system crashes
    //
    // PRODUCTION LOG SUMMARY:
    // - ApiError 5xx: ERROR + Sentry (Critical infrastructure issues)
    // - ApiError 4xx: ERROR + No Sentry (Expected client errors)
    // - Operational: WARN + No Sentry (Expected business logic issues)
    // - Programming: ERROR + Sentry (Unexpected system failures)

    if (error instanceof ApiError) {
      // Custom error classes (ValidationError, DatabaseError, etc.)
      // Sentry: 5xx errors YES, 4xx errors NO
      // Production: ERROR level with statusCode and error details
      GlobalErrorHandler.handleApiError(error, req, res, errorContext)
    } else if (GlobalErrorHandler.isOperationalError(error)) {
      // Expected errors (user input, business logic)
      // Sentry: NO (expected behavior)
      // Production: WARN level with basic error info
      GlobalErrorHandler.handleOperationalError(error, req, res, errorContext)
    } else {
      // Unexpected errors (bugs, infrastructure failures)
      // Sentry: YES (critical issues)
      // Production: ERROR level with full stack trace
      GlobalErrorHandler.handleProgrammingError(error, req, res, errorContext)
    }
  }

  /**
   * Handle custom API errors (ValidationError, DatabaseError, etc.)
   *
   * SENTRY BEHAVIOR:
   * - ✅ 5xx errors (500, 502, 503) → Sent to Sentry (critical infrastructure)
   * - ❌ 4xx errors (400, 401, 403, 404, 429) → NOT sent to Sentry (expected client errors)
   *
   * PRODUCTION LOGS:
   * - Level: ERROR
   * - Content: timestamp, level, message, requestId, endpoint, userId, statusCode, duration, error details
   * - Example: {"timestamp":"2024-01-01T10:00:00.000Z","level":"ERROR","message":"Database connection failed","requestId":"req-123","endpoint":"/cfapi/optimize","userId":"user-456","statusCode":500,"duration":2500,"error":{"name":"DatabaseError","message":"Connection timeout","stack":"..."}}
   */
  private static handleApiError(
    error: ApiError,
    req: Request,
    res: Response,
    context: LogContext,
  ): void {
    const errorResponse = GlobalErrorHandler.formatErrorResponse(error, req)

    // Use errorWithSentry for critical errors, error for operational errors
    if (error.statusCode >= 500 || !error.isOperational) {
      logger.errorWithSentry(
        `API Error: ${error.message}`,
        context,
        {
          errorType: error.constructor.name,
          statusCode: error.statusCode,
          isOperational: error.isOperational,
          context: error.context,
        },
        error,
      )
    } else {
      logger.error(
        `API Error: ${error.message}`,
        context,
        {
          errorType: error.constructor.name,
          statusCode: error.statusCode,
          isOperational: error.isOperational,
          context: error.context,
        },
        error,
      )
    }

    // Send response
    res.status(error.statusCode).json(errorResponse)
  }

  /**
   * Handle operational errors (expected errors like user input validation)
   *
   * SENTRY BEHAVIOR:
   * - ❌ NEVER sent to Sentry (these are expected user/business logic errors)
   *
   * PRODUCTION LOGS:
   * - Level: WARN
   * - Content: timestamp, level, message, requestId, endpoint, userId, errorType, statusCode
   * - Example: {"timestamp":"2024-01-01T10:00:00.000Z","level":"WARN","message":"Operational Error: Invalid email format","requestId":"req-123","endpoint":"/cfapi/create-user","userId":"user-456","errorType":"ValidationError","statusCode":400}
   *
   * EXAMPLES:
   * - Invalid JSON in request body
   * - Missing required fields
   * - Business rule violations
   * - User permission issues
   */
  private static handleOperationalError(
    error: Error,
    req: Request,
    res: Response,
    context: LogContext,
  ): void {
    const apiError = new ApiError(error.message, 400, true)
    const errorResponse = GlobalErrorHandler.formatErrorResponse(apiError, req)

    // Log as warning for operational errors (no Sentry)
    logger.warn(
      `Operational Error: ${error.message}`,
      context,
      {
        errorType: error.constructor.name,
        statusCode: 400,
      },
      error,
    )

    res.status(400).json(errorResponse)
  }

  /**
   * Handle programming errors (unexpected errors like bugs and infrastructure failures)
   *
   * SENTRY BEHAVIOR:
   * - ✅ ALWAYS sent to Sentry (these are critical system issues that need immediate attention)
   *
   * PRODUCTION LOGS:
   * - Level: ERROR
   * - Content: timestamp, level, message, requestId, endpoint, userId, errorType, statusCode, isOperational, stack
   * - Example: {"timestamp":"2024-01-01T10:00:00.000Z","level":"ERROR","message":"Programming Error: Cannot read property 'id' of undefined","requestId":"req-123","endpoint":"/cfapi/optimize","userId":"user-456","errorType":"TypeError","statusCode":500,"isOperational":false,"stack":"TypeError: Cannot read property 'id' of undefined\n    at UserService.getUser (/app/services/user.js:45:12)\n    at ..."}
   *
   * EXAMPLES:
   * - Null pointer exceptions
   * - Unhandled promise rejections
   * - System crashes
   * - Infrastructure failures
   * - Memory leaks
   * - Uncaught exceptions
   */
  private static handleProgrammingError(
    error: Error,
    req: Request,
    res: Response,
    context: LogContext,
  ): void {
    const apiError = new ApiError(
      process.env.NODE_ENV === "production" ? "Internal server error" : error.message,
      500,
      false,
    )
    const errorResponse = GlobalErrorHandler.formatErrorResponse(apiError, req)

    // Log as error for programming errors (always send to Sentry)
    logger.errorWithSentry(
      `Programming Error: ${error.message}`,
      context,
      {
        errorType: error.constructor.name,
        statusCode: 500,
        isOperational: false,
        stack: error.stack,
      },
      error,
    )

    res.status(500).json(errorResponse)
  }

  /**
   * Format error response for API consumers
   */
  private static formatErrorResponse(error: ApiError, req: Request): ErrorResponse {
    return {
      error: {
        message: error.message,
        code: error.constructor.name,
        statusCode: error.statusCode,
        requestId: req.requestId,
        timestamp: new Date().toISOString(),
        ...(error.context && { context: error.context }),
      },
    }
  }

  /**
   * Determine if an error is operational (expected) or programming (unexpected)
   */
  private static isOperationalError(error: Error): boolean {
    if (error instanceof ApiError) {
      return error.isOperational
    }

    // Common operational errors
    const operationalErrors = [
      "ValidationError",
      "CastError",
      "JsonWebTokenError",
      "TokenExpiredError",
      "MulterError",
    ]

    return operationalErrors.includes(error.name)
  }

  /**
   * Handle unhandled promise rejections
   *
   * SENTRY BEHAVIOR:
   * - ✅ ALWAYS sent to Sentry for monitoring
   *
   * SYSTEM BEHAVIOR:
   * - Does NOT exit the process
   * - Unhandled rejections don't corrupt process state
   * - Service continues running after logging
   */
  static async handleUnhandledRejection(reason: any, promise: Promise<any>): Promise<void> {
    const context: LogContext = {
      operation: "unhandled_rejection",
    }

    const error = reason instanceof Error ? reason : new Error(String(reason))

    logger.errorWithSentry(
      "Unhandled Promise Rejection",
      context,
      {
        reason: reason?.toString(),
        promise: promise.toString(),
        isOperational: false,
      },
      error,
    )

    // Do NOT exit - unhandled rejections don't corrupt process state
  }

  /**
   * Handle uncaught exceptions
   *
   * SENTRY BEHAVIOR:
   * - ✅ ALWAYS sent to Sentry for monitoring
   *
   * SYSTEM BEHAVIOR:
   * - Does NOT exit the process
   * - Modern Node.js error handling makes process exit less necessary
   * - Service continues running after logging
   */
  static async handleUncaughtException(error: Error): Promise<void> {
    const context: LogContext = {
      operation: "uncaught_exception",
    }

    logger.errorWithSentry(
      "Uncaught Exception",
      context,
      {
        stack: error.stack,
        isOperational: false,
      },
      error,
    )

    // Do NOT exit - keep service running
  }
}

// Convenience function for creating API errors
export function createApiError(
  message: string,
  statusCode: number = 500,
  context?: Record<string, any>,
): ApiError {
  return new ApiError(message, statusCode, true, context)
}
