/**
 * Slim Nest test harness — boots ONLY the passenger domain modules needed for pricing/booking
 * tests, deliberately excluding the IAM (TriaIamModule), SharedAuth, and MinIO stack from
 * app.module.ts. Those drag in `@tria-plc/api-common`'s file-crud/minio chain which requires the
 * ESM-only `file-type` package that jest's CommonJS resolver cannot load.
 *
 * Two entry points:
 *   - createServiceHarness(): resolve services directly (FareEngineService, etc.) for unit/DB-level
 *     assertions on the money math.
 *   - createHttpHarness(): a full Nest HTTP app with the SAME global ValidationPipe as main.ts, so
 *     controller/DTO/pipe behavior (client-trust, DTO validation) is exercised end-to-end over HTTP.
 *
 * The IAM JwtGuard is overridden with an always-allow stub so protected routes are reachable; auth
 * *enforcement* findings (which guards are missing) are asserted separately via route metadata, not
 * by booting the real guard.
 */
import { Global, INestApplication, Module, ValidationPipe } from "@nestjs/common";
import { Test, TestingModule } from "@nestjs/testing";
import { ConfigModule } from "@nestjs/config";
import { EventEmitterModule } from "@nestjs/event-emitter";
import { ScheduleModule } from "@nestjs/schedule";
import { getDataSourceToken } from "@nestjs/typeorm";
import { PrismaClient } from "@prisma/client";

import { PrismaModule } from "../../src/common/prisma.module";
import { PrismaService } from "../../src/common/prisma.service";
import { SessionActivityInterceptor } from "../../src/common/interceptors/session-activity.interceptor";
import { FareEngineModule } from "../../src/modules/fare-engine/fare-engine.module";
import { CurrencyModule } from "../../src/modules/currency/currency.module";
import { CurrenciesModule } from "../../src/modules/currencies/currencies.module";
import { PromosModule } from "../../src/modules/promos/promos.module";
import { SeatClassesModule } from "../../src/modules/seat-classes/seat-classes.module";
import { StationsModule } from "../../src/modules/stations/stations.module";
import { SchedulesModule } from "../../src/modules/schedules/schedules.module";
import { SegmentsModule } from "../../src/modules/segments/segments.module";
import { SystemConfigModule } from "../../src/modules/system-config/system-config.module";

/**
 * A stub TypeORM DataSource, provided globally so IAM-derived providers that reach the slim
 * harness transitively (e.g. NotificationsService via ExcessBaggageModule) can instantiate.
 * Pricing tests never trigger the code paths that actually use it.
 */
const fakeDataSource = {
  query: async () => [],
  transaction: async (cb: (m: unknown) => unknown) => cb({}),
  getRepository: () => ({}),
  createQueryRunner: () => ({
    connect: async () => undefined,
    startTransaction: async () => undefined,
    commitTransaction: async () => undefined,
    rollbackTransaction: async () => undefined,
    release: async () => undefined,
    manager: {},
  }),
};

@Global()
@Module({
  providers: [{ provide: getDataSourceToken(), useValue: fakeDataSource }],
  exports: [getDataSourceToken()],
})
class TestGlobalsModule {}

/** Modules that are safe to import in isolation (verified free of the IAM/MinIO chain). */
const DOMAIN_MODULES = [
  FareEngineModule,
  CurrencyModule,
  CurrenciesModule,
  PromosModule,
  SeatClassesModule,
  StationsModule,
  SchedulesModule,
  SegmentsModule,
  SystemConfigModule,
];
// NOTE: ExcessBaggageModule/PaymentsModule/BookingsModule are intentionally excluded — they pull in
// NotificationsModule → @golevelup RabbitMQ which connects at boot. Their suites instantiate the
// service directly with mocked collaborators (see excess-baggage / booking-trust specs).

async function buildModule(): Promise<TestingModule> {
  return Test.createTestingModule({
    imports: [
      ConfigModule.forRoot({ isGlobal: true }),
      EventEmitterModule.forRoot(),
      ScheduleModule.forRoot(),
      TestGlobalsModule,
      PrismaModule,
      ...DOMAIN_MODULES,
    ],
  })
    // SessionActivityInterceptor needs the IAM TypeORM DataSource, which the slim harness
    // deliberately omits. Replace it with a pass-through — it does not affect pricing logic.
    .overrideProvider(SessionActivityInterceptor)
    .useValue({ intercept: (_ctx: unknown, next: { handle: () => unknown }) => next.handle() })
    .compile();
}

export interface ServiceHarness {
  moduleRef: TestingModule;
  prisma: PrismaClient;
  close: () => Promise<void>;
}

/** Resolve services for direct method-level assertions. */
export async function createServiceHarness(): Promise<ServiceHarness> {
  const moduleRef = await buildModule();
  const prisma = moduleRef.get(PrismaService) as unknown as PrismaClient;
  return {
    moduleRef,
    prisma,
    close: async () => {
      await moduleRef.close();
    },
  };
}

export interface HttpHarness {
  app: INestApplication;
  moduleRef: TestingModule;
  prisma: PrismaClient;
  close: () => Promise<void>;
}

/** Boot a full HTTP app with the production ValidationPipe config from src/main.ts:56. */
export async function createHttpHarness(): Promise<HttpHarness> {
  const moduleRef = await buildModule();
  const app = moduleRef.createNestApplication();
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      transform: true,
      forbidUnknownValues: false,
    }),
  );
  await app.init();
  const prisma = moduleRef.get(PrismaService) as unknown as PrismaClient;
  return {
    app,
    moduleRef,
    prisma,
    close: async () => {
      await app.close();
    },
  };
}
