/*
 * {
 *   "success": true,
 *   "statusCode": 200,
 *   "message": "OK",
 *   "data": { "id": 1, "name": "user911" },
 *   "timestamp": "2026-05-21T10:00:00.000Z",
 *   "path": "/api/users/1"
 * }
 */
import {
  CallHandler,
  ExecutionContext,
  HttpStatus,
  Injectable,
  NestInterceptor,
  StreamableFile,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { Observable, map } from 'rxjs';

// Shape of every successful API response.
export interface PaginationMeta {
  page: number;
  limit: number;
  total: number;
  totalPages: number;
}

export interface ApiResponse<T> {
  success: true;
  statusCode: number;
  message: string;
  data: T | null;
  meta?: PaginationMeta;
  timestamp: string;
  path: string;
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<
  T,
  ApiResponse<T>
> {
  intercept(
    context: ExecutionContext,
    next: CallHandler<T>,
  ): Observable<ApiResponse<T>> {
    const request = context.switchToHttp().getRequest<Request>();
    const response = context.switchToHttp().getResponse<Response>();

    return next.handle().pipe(
      map((payload) => {
        if (payload instanceof StreamableFile) {
          return payload as unknown as ApiResponse<T>;
        }

        return this.buildResponse(payload, request, response);
      }),
    );
  }

  /**
   *response object from the controller's return value.
   */
  private buildResponse(
    payload: T,
    request: Request,
    response: Response,
  ): ApiResponse<T> {
    const statusCode = response.statusCode ?? HttpStatus.OK;
    const { message, data, meta } = this.extractResponsePayload(payload);

    return {
      success: true,
      statusCode,
      message: message ?? this.getDefaultMessage(statusCode),
      data: data as T | null,
      ...(meta && { meta }),
      timestamp: new Date().toISOString(),
      path: request.url,
    };
  }

  private extractResponsePayload(payload: unknown): {
    message?: string;
    data: unknown;
    meta?: PaginationMeta;
  } {
    if (this.isPaginatedPayload(payload)) {
      return {
        message: this.hasMessageField(payload) ? payload.message : undefined,
        data: payload.data,
        meta: payload.meta,
      };
    }

    if (!this.hasMessageField(payload)) {
      return { data: payload };
    }

    const { message, ...rest } = payload;
    const hasOtherFields = Object.keys(rest).length > 0;

    return {
      message,
      data: hasOtherFields ? rest : null,
    };
  }

  private isPaginatedPayload(payload: unknown): payload is {
    message?: string;
    data: unknown;
    meta: PaginationMeta;
  } {
    if (
      payload === null ||
      typeof payload !== 'object' ||
      Array.isArray(payload)
    ) {
      return false;
    }

    const value = payload as Record<string, unknown>;
    const meta = value.meta;

    if (meta === null || typeof meta !== 'object' || Array.isArray(meta)) {
      return false;
    }

    const pagination = meta as Record<string, unknown>;

    return (
      'data' in value &&
      typeof pagination.page === 'number' &&
      typeof pagination.limit === 'number' &&
      typeof pagination.total === 'number' &&
      typeof pagination.totalPages === 'number'
    );
  }

  private hasMessageField(
    payload: unknown,
  ): payload is { message: string } & Record<string, unknown> {
    return (
      payload !== null &&
      typeof payload === 'object' &&
      !Array.isArray(payload) &&
      'message' in payload &&
      typeof payload.message === 'string'
    );
  }

  /**
   * Pick a default message when the controller didn't provide one.
   */
  private getDefaultMessage(statusCode: HttpStatus): string {
    if (statusCode === HttpStatus.CREATED) return 'Created';
    if (statusCode === HttpStatus.NO_CONTENT) return 'No Content';
    return 'OK';
  }
}
