import {
  CallHandler,
  ExecutionContext,
  HttpException,
  Injectable,
  NestInterceptor,
} from '@nestjs/common';
import { Observable, catchError, finalize, throwError } from 'rxjs';
import { MetricsService } from './metrics.service';

type MetricRequest = {
  method?: string;
  baseUrl?: string;
  route?: { path?: unknown };
};

@Injectable()
export class MetricsInterceptor implements NestInterceptor {
  constructor(private readonly metrics: MetricsService) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    if (context.getType() !== 'http') {
      return next.handle();
    }
    const request = context.switchToHttp().getRequest<MetricRequest>();
    const response = context.switchToHttp().getResponse<{ statusCode: number }>();
    const startedAt = Date.now();
    const route = this.normalizedRoute(request);
    let errorStatusCode: number | undefined;
    this.metrics.requestStarted();

    return next.handle().pipe(
      catchError((error: unknown) => {
        errorStatusCode = error instanceof HttpException ? error.getStatus() : 500;
        return throwError(() => error);
      }),
      finalize(() => {
        this.metrics.requestFinished(
          request.method ?? 'UNKNOWN',
          route,
          errorStatusCode ?? response.statusCode,
          Date.now() - startedAt,
        );
      }),
    );
  }

  private normalizedRoute(request: MetricRequest): string {
    const routePath = request.route?.path;
    if (typeof routePath !== 'string') {
      return '__unmatched__';
    }
    const joined = `${request.baseUrl ?? ''}/${routePath}`.replace(/\/{2,}/g, '/');
    return joined.length > 1 ? joined.replace(/\/$/, '') : joined;
  }
}
