import { CallHandler, ExecutionContext, InternalServerErrorException } from '@nestjs/common';
import { firstValueFrom, of, throwError } from 'rxjs';
import { MetricsInterceptor } from './metrics.interceptor';
import { MetricsService } from './metrics.service';

describe('MetricsInterceptor', () => {
  const createContext = (statusCode = 200) =>
    ({
      getType: () => 'http',
      switchToHttp: () => ({
        getRequest: () => ({
          method: 'GET',
          baseUrl: '/api/v1/posts',
          route: { path: '/:id' },
        }),
        getResponse: () => ({ statusCode }),
      }),
    }) as unknown as ExecutionContext;

  it('uses a route template instead of a high-cardinality raw URL', async () => {
    const metrics = {
      requestStarted: jest.fn(),
      requestFinished: jest.fn(),
    } as unknown as MetricsService;
    const interceptor = new MetricsInterceptor(metrics);

    await firstValueFrom(
      interceptor.intercept(createContext(), { handle: () => of({ ok: true }) } as CallHandler),
    );

    expect(metrics.requestStarted).toHaveBeenCalledTimes(1);
    expect(metrics.requestFinished).toHaveBeenCalledWith(
      'GET',
      '/api/v1/posts/:id',
      200,
      expect.any(Number),
    );
  });

  it('records the actual exception status', async () => {
    const metrics = {
      requestStarted: jest.fn(),
      requestFinished: jest.fn(),
    } as unknown as MetricsService;
    const interceptor = new MetricsInterceptor(metrics);
    const handler = {
      handle: () => throwError(() => new InternalServerErrorException()),
    } as CallHandler;

    await expect(firstValueFrom(interceptor.intercept(createContext(), handler))).rejects.toBeInstanceOf(
      InternalServerErrorException,
    );
    expect(metrics.requestFinished).toHaveBeenCalledWith(
      'GET',
      '/api/v1/posts/:id',
      500,
      expect.any(Number),
    );
  });
});
