import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
  Logger,
} from "@nestjs/common";
import type { Request, Response } from "express";
import { randomUUID } from "crypto";

interface ErrorResponse {
  success: false;
  message: string;
  errorId: string;
  timestamp: string;
  path: string;
  statusCode: number;
}

/**
 * Patterns that indicate potentially sensitive information in error messages
 */
const SENSITIVE_PATTERNS = [
  /password/i,
  /secret/i,
  /api[_-]?key/i,
  /token/i,
  /credential/i,
  /connection.*string/i,
  /database.*error/i,
  /sql.*error/i,
  /prisma/i,
  /postgres/i,
  /mysql/i,
  /redis/i,
  /mongodb/i,
  /stack.*trace/i,
  /at\s+\S+\s+\(/i, // Stack trace pattern
  /\/home\//i, // File paths
  /\/var\//i,
  /\/usr\//i,
  /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/, // IP addresses
];

@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger(GlobalExceptionFilter.name);

  catch(exception: unknown, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();

    const errorId = randomUUID();
    const timestamp = new Date().toISOString();

    let status = HttpStatus.INTERNAL_SERVER_ERROR;
    let message = "An unexpected error occurred";
    let isHttpException = false;

    if (exception instanceof HttpException) {
      status = exception.getStatus();
      isHttpException = true;
      const exceptionResponse = exception.getResponse();
      message =
        typeof exceptionResponse === "string"
          ? exceptionResponse
          : ((exceptionResponse as { message?: string }).message ?? message);
    } else if (exception instanceof Error) {
      message = exception.message;
    }

    const isProduction = process.env.NODE_ENV === "production";

    // Always log the full error internally
    this.logger.error({
      errorId,
      method: request.method,
      url: request.url,
      statusCode: status,
      message: exception instanceof Error ? exception.message : String(exception),
      stack: exception instanceof Error ? exception.stack : undefined,
    });

    // Determine the safe message for client response
    const clientMessage = this.getSafeClientMessage(message, status, isProduction, isHttpException);

    const errorResponse: ErrorResponse = {
      success: false,
      message: clientMessage,
      errorId,
      timestamp,
      path: request.url,
      statusCode: status,
    };

    response.status(status).json(errorResponse);
  }

  /**
   * Get a sanitized error message safe for client response
   * - In production, always sanitize 5xx errors
   * - Check for sensitive patterns and redact if found
   * - HttpExceptions are generally safe (intentionally thrown)
   */
  private getSafeClientMessage(
    message: string,
    status: number,
    isProduction: boolean,
    isHttpException: boolean
  ): string {
    const genericMessage = "An unexpected error occurred";

    // Always sanitize 5xx errors in production (server-side errors)
    if (isProduction && status >= 500) {
      return genericMessage;
    }

    // For non-HttpExceptions, always sanitize in production
    // (these are unexpected errors that might leak internals)
    if (isProduction && !isHttpException) {
      return genericMessage;
    }

    // Check for sensitive patterns
    if (this.containsSensitiveInfo(message)) {
      this.logger.warn(`Redacted potentially sensitive error message (errorId in logs)`);
      return genericMessage;
    }

    return message;
  }

  /**
   * Check if a message contains potentially sensitive information
   */
  private containsSensitiveInfo(message: string): boolean {
    return SENSITIVE_PATTERNS.some((pattern) => pattern.test(message));
  }
}
