import {
  ArgumentsHost,
  Catch,
  HttpException,
  HttpStatus,
  Logger,
  type ExceptionFilter,
} from '@nestjs/common';
import type { FastifyReply, FastifyRequest } from 'fastify';

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

  catch(exception: unknown, host: ArgumentsHost): void {
    const context = host.switchToHttp();
    const request = context.getRequest<FastifyRequest>();
    const response = context.getResponse<FastifyReply>();
    const databaseErrorCode = this.getDatabaseErrorCode(exception);
    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : databaseErrorCode === '23505' || databaseErrorCode === '23503'
          ? HttpStatus.CONFLICT
          : HttpStatus.INTERNAL_SERVER_ERROR;

    const exceptionResponse =
      exception instanceof HttpException ? exception.getResponse() : undefined;
    const validationDetails =
      typeof exceptionResponse === 'object' &&
      exceptionResponse !== null &&
      'message' in exceptionResponse &&
      Array.isArray(exceptionResponse.message)
        ? exceptionResponse.message
        : undefined;
    const message =
      databaseErrorCode === '23505'
        ? 'A record with the same unique value already exists.'
        : databaseErrorCode === '23503'
          ? 'The operation conflicts with a related record.'
          : status >= 500
            ? 'Internal server error.'
            : typeof exceptionResponse === 'string'
              ? exceptionResponse
              : typeof exceptionResponse === 'object' &&
                  exceptionResponse !== null &&
                  'message' in exceptionResponse &&
                  typeof exceptionResponse.message === 'string'
                ? exceptionResponse.message
                : exception instanceof Error
                  ? exception.message
                  : 'Request failed.';

    if (status >= 500) {
      this.logger.error(
        {
          requestId: request.id,
          method: request.method,
          url: request.url,
          exception,
        },
        'Unhandled request error',
      );
    }

    void response.status(status).send({
      error: {
        statusCode: status,
        message,
        details: validationDetails,
        requestId: request.id,
        timestamp: new Date().toISOString(),
        path: request.url,
      },
    });
  }

  private getDatabaseErrorCode(exception: unknown): string | undefined {
    if (typeof exception !== 'object' || exception === null || !('code' in exception)) {
      return undefined;
    }
    return typeof exception.code === 'string' ? exception.code : undefined;
  }
}
