import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Decimal } from '@prisma/client/runtime/library';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

function isDateLike(input: unknown): input is Date {
  if (input instanceof Date) return true;
  if (!input || typeof input !== 'object') return false;
  const candidate = input as { getTime?: unknown; toISOString?: unknown; constructor?: { name?: string } };
  if (candidate.constructor?.name === 'Date') return true;
  return typeof candidate.getTime === 'function' && typeof candidate.toISOString === 'function';
}

function sanitizeValue(input: unknown, seen = new WeakSet<object>()): unknown {
  if (typeof input === 'bigint') return input.toString();
  if (input instanceof Decimal) return input.toNumber();
  if (isDateLike(input)) {
    const time = input.getTime();
    return Number.isNaN(time) ? null : new Date(time).toISOString();
  }
  if (input === null || input === undefined) return input;
  if (typeof input !== 'object') return input;

  if (seen.has(input)) return '[Circular]';
  seen.add(input);

  if (Array.isArray(input)) return input.map((item) => sanitizeValue(item, seen));

  const out: Record<string, unknown> = {};
  for (const [k, v] of Object.entries(input as Record<string, unknown>)) {
    out[k] = sanitizeValue(v, seen);
  }
  return out;
}

@Injectable()
export class BigIntSerializerInterceptor implements NestInterceptor<unknown, unknown> {
  intercept(_context: ExecutionContext, next: CallHandler): Observable<unknown> {
    return next.handle().pipe(map((data) => sanitizeValue(data)));
  }
}
