import { ValidationPipe } from '@nestjs/common';
import type { Provider, Type } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import type { TestingModule } from '@nestjs/testing';
import request from 'supertest';

export type ProviderOverride = {
  provide: unknown;
  useValue: unknown;
};

export type HttpContractHarness = {
  // Avoid exporting INestApplication here because this repo can end up with
  // multiple @nestjs/common type roots (workspace hoisting), which makes the
  // INestApplication types incompatible.
  app: unknown;
  module: TestingModule;
  http: ReturnType<typeof request>;
  close: () => Promise<void>;
};

export async function createHttpContractHarness(options: {
  controllers: Array<Type<unknown>>;
  providers?: Provider[];
  overrides?: ProviderOverride[];
}): Promise<HttpContractHarness> {
  let moduleBuilder = Test.createTestingModule({
    controllers: options.controllers,
    providers: options.providers ?? [],
  });

  for (const override of options.overrides ?? []) {
    moduleBuilder = moduleBuilder.overrideProvider(override.provide).useValue(override.useValue);
  }

  const module = await moduleBuilder.compile();
  const app = module.createNestApplication();

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
    }),
  );

  await app.init();

  return {
    app,
    module,
    http: request(app.getHttpServer()),
    close: async () => {
      await app.close();
    },
  };
}