import 'reflect-metadata';
import { getMetadataStorage } from 'class-validator';
import { BootstrapSetupDto } from './admin/bootstrap.dto.js';

/**
 * Boot-time self-check: the global ValidationPipe must be able to SEE the
 * decorated properties of the DTOs it guards (#1391, #436 class).
 *
 * WHY THIS EXISTS. When Nest resolves a @Body() metatype to Object — via
 * `import type` class erasure (#436), or a dependency graph where the
 * controller's decorators and the application's route enhancers disagree
 * (#1391's hypothesized dual-@nestjs/common on a mixed install) — the
 * ValidationPipe's whitelist treats every property as forbidden. The first
 * symptom is a 400 on the FIRST bootstrap attempt of a fresh install, the
 * worst place to discover wiring damage: the operator cannot tell a broken
 * payload from a broken daemon.
 *
 * This check fails LOUD at boot instead: if the pipe cannot see the DTO's
 * decorated properties, the gateway refuses to start with a named cause.
 * It catches the whole class — erasure, decorator metadata loss — on every
 * host, at the moment the damage exists rather than at first use.
 *
 * Storage sharing note: class-validator keys its metadata storage on
 * globalThis, so duplicate package copies do NOT hide metadata (measured,
 * #1391 diagnosis). What hides it is losing the metatype itself, which is
 * what this asserts against.
 */

/**
 * DTOs the global pipe guards, mapped to the properties the whitelist must
 * admit. Target is the CONSTRUCTOR (the object class itself): class-validator
 * decorators register metadata keyed on the constructor, and its executor
 * looks up `object.constructor` (ValidationExecutor.js:50) — the probe
 * through `prototype` returns zero. Extend when adding DTOs to the app.
 */
export const PIPE_GUARDED_DTOS: Array<{
  name: string;
  target: abstract new (...args: never[]) => unknown;
  properties: string[];
}> = [
  {
    name: 'BootstrapSetupDto',
    target: BootstrapSetupDto,
    properties: ['name', 'email', 'password'],
  },
];

export class PipeMetatypeCheckError extends Error {
  constructor(missing: string[]) {
    super(
      'ValidationPipe metatype check failed: ' +
        missing.join('; ') +
        '. The global ValidationPipe cannot see decorated DTO properties — ' +
        'every request body would be rejected as non-whitelisted. ' +
        'Check for import-type erasure or decorator metadata loss in the ' +
        'dependency graph (see issues #436, #1391).',
    );
    this.name = 'PipeMetatypeCheckError';
  }
}

/**
 * Assert the pipe's whitelist can see every guarded DTO's decorated
 * properties. Throws PipeMetatypeCheckError (fail-loud at boot) listing
 * each miss. Pure function of module state: no I/O, safe to call twice.
 */
export function assertValidationPipeSeesDtoDecorators(): void {
  const storage = getMetadataStorage();
  const missing: string[] = [];

  for (const dto of PIPE_GUARDED_DTOS) {
    // class-validator records constraints keyed on the DTO's constructor
    // (decorators run on the class), and its executor resolves them via
    // object.constructor. A property with no recorded metadata is invisible
    // to the whitelist — whatever the cause — and fails here.
    // Signature mirrors ValidationExecutor.js:50 — (constructor, schema, always,
    // strictGroups, groups?). No schema, always=true, no groups: every
    // constraint regardless of grouping, which is what the whitelist sees.
    const metadatas = storage.getTargetValidationMetadatas(dto.target, '', true, false);
    const decorated = new Set(metadatas.map((m) => m.propertyName));

    for (const property of dto.properties) {
      if (!decorated.has(property)) {
        missing.push(
          `${dto.name}.${property} has no class-validator constraints visible to the pipe`,
        );
      }
    }
  }

  if (missing.length > 0) {
    throw new PipeMetatypeCheckError(missing);
  }
}
