import { Controller, Get } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import type { INestApplication } from '@nestjs/common';
import {
  ADMIN_SESSION_COOKIE_AUTH,
  USER_SESSION_COOKIE_AUTH,
  buildOpenApiDocument,
  setupOpenApi,
} from './openapi';

// Minimal controller to feed SwaggerModule.createDocument — the
// document builder needs a non-empty route table to produce a spec
// it considers valid.
@Controller('smoke')
class SmokeController {
  @Get()
  hello(): { ok: boolean } {
    return { ok: true };
  }
}

async function bootApp(): Promise<INestApplication> {
  const moduleRef = await Test.createTestingModule({
    controllers: [SmokeController],
  }).compile();
  return moduleRef.createNestApplication({ logger: false });
}

describe('buildOpenApiDocument', () => {
  it('returns a well-shaped OpenAPI 3 document with title + version', async () => {
    const app = await bootApp();
    const doc = buildOpenApiDocument(app);
    expect(doc.openapi).toMatch(/^3\./);
    expect(doc.info.title).toBe('APF Portal BFF API');
    expect(doc.info.version).toBe('0.0.0');
    await app.close();
  });

  it('declares both cookie-auth schemes (user + admin)', async () => {
    const app = await bootApp();
    const doc = buildOpenApiDocument(app);
    const schemes = doc.components?.securitySchemes;
    expect(schemes?.[USER_SESSION_COOKIE_AUTH]).toMatchObject({
      type: 'apiKey',
      in: 'cookie',
      name: USER_SESSION_COOKIE_AUTH,
    });
    expect(schemes?.[ADMIN_SESSION_COOKIE_AUTH]).toMatchObject({
      type: 'apiKey',
      in: 'cookie',
      name: ADMIN_SESSION_COOKIE_AUTH,
    });
    await app.close();
  });

  it('captures the smoke controller route in the paths map', async () => {
    const app = await bootApp();
    const doc = buildOpenApiDocument(app);
    expect(doc.paths['/smoke']).toBeDefined();
    expect(doc.paths['/smoke']?.get).toBeDefined();
    await app.close();
  });
});

describe('setupOpenApi', () => {
  const originalNodeEnv = process.env['NODE_ENV'];

  afterEach(() => {
    if (originalNodeEnv === undefined) {
      delete process.env['NODE_ENV'];
    } else {
      process.env['NODE_ENV'] = originalNodeEnv;
    }
  });

  it('short-circuits in production — no routes mounted, no spec built', async () => {
    process.env['NODE_ENV'] = 'production';
    const httpAdapter = { get: jest.fn() };
    const app = {
      getHttpAdapter: jest.fn().mockReturnValue(httpAdapter),
      use: jest.fn(),
    } as unknown as INestApplication;
    setupOpenApi(app, 'api');
    expect(httpAdapter.get).not.toHaveBeenCalled();
    expect(app.use).not.toHaveBeenCalled();
  });

  it('mounts the JSON spec + Scalar UI in dev', async () => {
    process.env['NODE_ENV'] = 'development';
    const app = await bootApp();
    const httpGetSpy = jest.spyOn(app.getHttpAdapter(), 'get');
    const useSpy = jest.spyOn(app, 'use');
    setupOpenApi(app, 'api');
    // JSON spec at /api/openapi.json (via the HTTP adapter directly,
    // bypassing Nest's router).
    expect(httpGetSpy).toHaveBeenCalledWith('/api/openapi.json', expect.any(Function));
    // Scalar UI mounted under /api/docs.
    expect(useSpy).toHaveBeenCalledWith('/api/docs', expect.any(Function));
    await app.close();
  });
});
