import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
  HttpStatus,
  Logger,
} from '@nestjs/common';
import { type Response } from 'express';
import { BusinessException } from '../../common/errors/business.exception';
import type { ErrorResponseBody } from './types/error-response-body.type';

@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>();

    if (response.headersSent) {
      return;
    }

    let status = HttpStatus.INTERNAL_SERVER_ERROR;
    let message = 'Internal server error';
    let code: string | undefined;
    let errors: unknown[] | undefined;

    if (exception instanceof BusinessException) {
      status = exception.getStatus();
      const exResponse = exception.getResponse();
      if (typeof exResponse === 'object' && exResponse !== null) {
        const responseObj = exResponse as Record<string, unknown>;
        message = (responseObj['messageKey'] as string | undefined) ?? message;
        code = responseObj['code'] as string | undefined;
      }
    } else if (exception instanceof HttpException) {
      status = exception.getStatus();
      const exResponse = exception.getResponse();
      if (typeof exResponse === 'object' && exResponse !== null) {
        const responseObj = exResponse as Record<string, unknown>;
        message = (responseObj['message'] as string | undefined) ?? exception.message;
        errors = responseObj['errors'] as unknown[] | undefined;
      } else {
        message = exception.message;
      }
    } else if (exception instanceof Error) {
      // Express body-parser PayloadTooLargeError (and similar middleware
      // errors) carry HTTP status info as a `status`/`statusCode` property.
      // Without this branch they bubble up as 500 — masking real 413s.
      const errLike = exception as Error & {
        status?: number;
        statusCode?: number;
        type?: string;
      };
      const middlewareStatus = errLike.statusCode ?? errLike.status;
      if (typeof middlewareStatus === 'number' && middlewareStatus >= 400 && middlewareStatus < 600) {
        status = middlewareStatus;
        message = exception.message;
        if (typeof errLike.type === 'string') {
          code = errLike.type;
        }
      } else {
        this.logger.error(`Unhandled exception: ${exception.message}`, exception.stack);
      }
    } else {
      this.logger.error('Unknown exception thrown', String(exception));
    }

    const body: ErrorResponseBody = {
      statusCode: status,
      message,
      timestamp: new Date().toISOString(),
    };
    if (code !== undefined) {
      body.code = code;
    }
    if (errors !== undefined) {
      body.errors = errors;
    }

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