import { ApiHideProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
  ArrayNotEmpty,
  IsArray,
  IsIn,
  IsInt,
  IsOptional,
  IsString,
  Matches,
  Max,
  Min,
  Validate,
  ValidateIf,
  ValidationArguments,
  ValidatorConstraint,
  ValidatorConstraintInterface,
} from 'class-validator';
import { ROLE_KEYS } from 'src/common/constants/strings-constants';

const ROLE_KEY_VALUES = Object.values(ROLE_KEYS);
const MAX_PLACEHOLDER_COUNT = 200;
/** Bare hostname[.hostname...] — good enough to reject obviously-wrong input; Zoom itself is the source of truth on deliverability. */
const DOMAIN_PATTERN = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)+$/;

@ValidatorConstraint({ name: 'atLeastOneLinkGroup', async: false })
class AtLeastOneLinkGroupConstraint implements ValidatorConstraintInterface {
  validate(_value: unknown, args: ValidationArguments): boolean {
    const dto = args.object as GenerateZoomGeneralLinksV1Dto;
    const hasRoles = !!dto.roleKeys?.length;
    const hasPlaceholder = !!dto.placeholderName && !!dto.placeholderCount && !!dto.placeholderDomain;
    return hasRoles || hasPlaceholder;
  }

  defaultMessage(): string {
    return 'Provide at least one of roleKeys or a placeholder batch (placeholderName/placeholderCount/placeholderDomain)';
  }
}

@ValidatorConstraint({ name: 'placeholderTrioComplete', async: false })
class PlaceholderTrioCompleteConstraint implements ValidatorConstraintInterface {
  validate(_value: unknown, args: ValidationArguments): boolean {
    const dto = args.object as GenerateZoomGeneralLinksV1Dto;
    const fields = [dto.placeholderName, dto.placeholderCount, dto.placeholderDomain];
    const providedCount = fields.filter((field) => field !== undefined && field !== null).length;
    return providedCount === 0 || providedCount === fields.length;
  }

  defaultMessage(): string {
    return 'placeholderName, placeholderCount, and placeholderDomain must all be provided together, or all omitted';
  }
}

/**
 * `POST /v1/zoom/general-links` body. Exactly one of `programId` / `sessionId` is required (the
 * scope): `programId` generates links across every Zoom-provisioned session of that program;
 * `sessionId` narrows to that one session only. Either or both recipient groups may be supplied:
 * - `roleKeys`: every real user holding one of these roles gets their own tagged-email Zoom
 *   registrant (see `buildZoomUserRegistrantEmail`) for each target session.
 * - `placeholderName`/`placeholderCount`/`placeholderDomain`: generates that many placeholder
 *   registrants named `"<placeholderName> <n>"` with emails `<placeholderName-slug><n>@<placeholderDomain>`
 *   for each target session.
 */
export class GenerateZoomGeneralLinksV1Dto {
  @ApiPropertyOptional({ description: 'Generates links across every Zoom-provisioned session of this program.' })
  @ValidateIf((dto) => !dto.sessionId)
  @Type(() => Number)
  @IsInt()
  programId?: number;

  @ApiPropertyOptional({ description: 'Generates links for this one session only.' })
  @ValidateIf((dto) => !dto.programId)
  @Type(() => Number)
  @IsInt()
  sessionId?: number;

  @ApiPropertyOptional({ enum: ROLE_KEY_VALUES, isArray: true })
  @IsOptional()
  @IsArray()
  @ArrayNotEmpty()
  @IsIn(ROLE_KEY_VALUES, { each: true })
  roleKeys?: string[];

  @ApiPropertyOptional({ description: 'Batch label, e.g. "Staff" — rows are named "Staff 1".."Staff <count>".' })
  @IsOptional()
  @IsString()
  placeholderName?: string;

  @ApiPropertyOptional({ maximum: MAX_PLACEHOLDER_COUNT })
  @IsOptional()
  @Type(() => Number)
  @IsInt()
  @Min(1)
  @Max(MAX_PLACEHOLDER_COUNT)
  placeholderCount?: number;

  @ApiPropertyOptional({ description: 'Domain for generated placeholder emails, e.g. "example.com".' })
  @IsOptional()
  @IsString()
  @Matches(DOMAIN_PATTERN, { message: 'placeholderDomain must be a valid domain, e.g. example.com' })
  placeholderDomain?: string;

  /** Not a real input — hosts the whole-object cross-field checks above, since class-validator only runs them attached to a property. Always undefined; never read. */
  @ApiHideProperty()
  @Validate(AtLeastOneLinkGroupConstraint)
  @Validate(PlaceholderTrioCompleteConstraint)
  atLeastOneGroupCheck?: never;
}
