/**
 * E2E integration test — POST /api/bootstrap/setup
 *
 * Regression guard for the `import type { BootstrapSetupDto }` class-erasure
 * bug (IUV-M01, issue #436).
 *
 * When `BootstrapSetupDto` is imported with `import type`, TypeScript erases
 * the class at compile time. NestJS then sees `Object` as the `@Body()`
 * metatype, and ValidationPipe with `whitelist:true + forbidNonWhitelisted:true`
 * treats every property as non-whitelisted, returning:
 *
 *   400 { message: ["property email should not exist", "property password should not exist"] }
 *
 * The fix is a plain value import (`import { BootstrapSetupDto }`), which
 * preserves the class reference so Nest can read the class-validator decorators.
 *
 * This test MUST fail if `import type` is re-introduced on `BootstrapSetupDto`.
 * A controller unit test that constructs ValidationPipe manually won't catch
 * this — only the real DI binding path exercises the metatype lookup.
 */

import 'reflect-metadata';
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
import { Test } from '@nestjs/testing';
import { ValidationPipe, type INestApplication } from '@nestjs/common';
import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';
import request from 'supertest';
import { BootstrapController } from './bootstrap.controller.js';
import type { BootstrapResultDto } from './bootstrap.dto.js';

// ─── Minimal mock dependencies ───────────────────────────────────────────────

/**
 * We use explicit `@Inject(AUTH)` / `@Inject(DB)` in the controller so we
 * can provide mock values by token without spinning up the real DB or Auth.
 */
import { AUTH } from '../auth/auth.tokens.js';
import { DB } from '../database/database.module.js';

const MOCK_USER_ID = 'mock-user-id-001';

const mockAuth = {
  api: {
    createUser: () =>
      Promise.resolve({
        user: {
          id: MOCK_USER_ID,
          name: 'Admin',
          email: 'admin@example.com',
        },
      }),
  },
};

// Override db.select() so the second query (verify user exists) returns a user.
// The bootstrap controller calls select().from() twice:
//   1. count() to check zero users  → returns [{total: 0}]
//   2. select().where().limit()     → returns [the created user]
let selectCallCount = 0;
const mockDbWithUser = {
  select: () => {
    selectCallCount++;
    return {
      from: () => {
        if (selectCallCount === 1) {
          // First call: count — zero users
          return Promise.resolve([{ total: 0 }]);
        }
        // Subsequent calls: return a mock user row
        return {
          where: () => ({
            limit: () =>
              Promise.resolve([
                {
                  id: MOCK_USER_ID,
                  name: 'Admin',
                  email: 'admin@example.com',
                  role: 'admin',
                },
              ]),
          }),
        };
      },
    };
  },
  update: () => ({
    set: () => ({
      where: () => Promise.resolve([]),
    }),
  }),
  insert: () => ({
    values: () => ({
      returning: () =>
        Promise.resolve([
          {
            id: 'token-id-001',
            label: 'Initial setup token',
          },
        ]),
    }),
  }),
};

// ─── Test suite ───────────────────────────────────────────────────────────────

describe('POST /api/bootstrap/setup — ValidationPipe DTO binding', () => {
  let app: INestApplication;

  beforeAll(async () => {
    selectCallCount = 0;

    const moduleRef = await Test.createTestingModule({
      controllers: [BootstrapController],
      providers: [
        { provide: AUTH, useValue: mockAuth },
        { provide: DB, useValue: mockDbWithUser },
      ],
    }).compile();

    app = moduleRef.createNestApplication<NestFastifyApplication>(new FastifyAdapter());

    // Mirror main.ts configuration exactly — this is what reproduced the 400.
    app.useGlobalPipes(
      new ValidationPipe({
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true,
      }),
    );

    await app.init();
    // Fastify requires waiting for the adapter to be ready
    await app.getHttpAdapter().getInstance().ready();
  });

  afterAll(async () => {
    await app.close();
  });

  it('returns 201 (not 400) when a valid {name, email, password} body is sent', async () => {
    const res = await request(app.getHttpServer())
      .post('/api/bootstrap/setup')
      .send({ name: 'Admin', email: 'admin@example.com', password: 'password123' })
      .set('Content-Type', 'application/json');

    // Before the fix (import type), Nest ValidationPipe returned 400 with
    // "property email should not exist" / "property password should not exist"
    // because the DTO class was erased and every field looked non-whitelisted.
    expect(res.status).not.toBe(400);
    expect(res.status).toBe(201);
    const body = res.body as BootstrapResultDto;
    expect(body.user).toBeDefined();
    expect(body.user.email).toBe('admin@example.com');
    expect(body.token).toBeDefined();
    expect(body.token.plaintext).toBeDefined();
  });

  it('returns 400 when extra forbidden properties are sent', async () => {
    // This proves ValidationPipe IS active and working (forbidNonWhitelisted).
    const res = await request(app.getHttpServer())
      .post('/api/bootstrap/setup')
      .send({
        name: 'Admin',
        email: 'admin@example.com',
        password: 'password123',
        extraField: 'should-be-rejected',
      })
      .set('Content-Type', 'application/json');

    expect(res.status).toBe(400);
  });

  it('returns 400 when email is invalid', async () => {
    const res = await request(app.getHttpServer())
      .post('/api/bootstrap/setup')
      .send({ name: 'Admin', email: 'not-an-email', password: 'password123' })
      .set('Content-Type', 'application/json');

    expect(res.status).toBe(400);
  });

  it('returns 400 when password is too short', async () => {
    const res = await request(app.getHttpServer())
      .post('/api/bootstrap/setup')
      .send({ name: 'Admin', email: 'admin@example.com', password: 'short' })
      .set('Content-Type', 'application/json');

    expect(res.status).toBe(400);
  });
});
