import {
  registerDecorator,
  ValidationArguments,
  ValidationOptions,
  ValidatorConstraint,
  ValidatorConstraintInterface,
} from 'class-validator';

/**
 * A caller may not stamp an event as having happened before now.
 *
 * A request cannot reach the server at the instant it was built, and a caller's
 * clock is not the server's, so a timestamp that honestly means "now" always
 * arrives a little stale. Comparing straight against `Date.now()` would reject
 * it. The skew allowance below is what makes an honest "now" pass — it is not a
 * window for backdating, and it is deliberately far too small to reach any
 * earlier event worth backdating to.
 */
export const CLOCK_SKEW_TOLERANCE_MS = 60_000;

@ValidatorConstraint({ name: 'IsNotBackdated', async: false })
export class IsNotBackdatedConstraint implements ValidatorConstraintInterface {
  validate(value: unknown, args: ValidationArguments): boolean {
    // Absence is not this validator's business; pair with @IsOptional.
    if (value === undefined || value === null || value === '') return true;
    const parsed = new Date(value as string | Date);
    // An unparseable value is a format error — let @IsISO8601/@IsDateString own
    // that message rather than reporting it as a backdate.
    if (Number.isNaN(parsed.getTime())) return true;
    const toleranceMs = (args.constraints?.[0] as number | undefined) ?? CLOCK_SKEW_TOLERANCE_MS;
    return parsed.getTime() >= Date.now() - toleranceMs;
  }

  defaultMessage(args: ValidationArguments): string {
    return `${args.property} cannot be backdated — it must be now or later`;
  }
}

/**
 * Rejects a timestamp earlier than now, give or take {@link CLOCK_SKEW_TOLERANCE_MS}.
 * Pass a different tolerance only with a reason.
 */
export function IsNotBackdated(
  toleranceMs: number = CLOCK_SKEW_TOLERANCE_MS,
  validationOptions?: ValidationOptions,
) {
  return function (object: object, propertyName: string) {
    registerDecorator({
      target: object.constructor,
      propertyName,
      options: validationOptions,
      constraints: [toleranceMs],
      validator: IsNotBackdatedConstraint,
    });
  };
}
