import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
  ArrayNotEmpty,
  IsArray,
  IsEnum,
  IsIn,
  IsInt,
  IsNotEmpty,
  IsOptional,
  IsString,
  ValidateIf,
} from 'class-validator';
import { SessionCommunicationPurposeEnum } from 'src/common/enum/session-communication-purpose.enum';
import { BulkCommunicationSelectionModeEnum } from 'src/common/enum/bulk-communication-selection-mode.enum';
import {
  GENERAL_LINK_PURPOSES,
  generalLinkPurposeNeedsOccurrence,
  isGeneralLinkProgramLevelPurpose,
  isProgramLevelPurpose,
  SessionOccurrence,
} from '../session-communication.constants';

/**
 * Bulk send request. Channels are derived from `purpose` on the server
 * (ABSENT/INVITE = email + whatsapp, VALUE_CARD = email only) and are never
 * accepted from the client.
 */
export class SendBulkSessionCommunicationDto {
  @ApiProperty({ description: 'Program id the registrations belong to', example: 5 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  programId: number;

  @ApiPropertyOptional({
    description:
      'Online program session id. Required for session-scoped purposes (INVITE/ABSENT/VALUE_CARD); ' +
      'omitted for program-level purposes (WELCOME/PROGRAM_COMPLETION).',
    example: 12,
  })
  @ValidateIf((dto: SendBulkSessionCommunicationDto) => !isProgramLevelPurpose(dto.purpose))
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  sessionId?: number;

  @ApiProperty({
    enum: SessionCommunicationPurposeEnum,
    example: SessionCommunicationPurposeEnum.INVITE,
  })
  @IsNotEmpty()
  @IsEnum(SessionCommunicationPurposeEnum)
  purpose: SessionCommunicationPurposeEnum;

  @ApiProperty({
    enum: BulkCommunicationSelectionModeEnum,
    description:
      'ALL = every eligible registration of the session (ABSENT auto-filters to absentees); ' +
      'SELECTED = only registrationIds; EXCLUDED = every eligible registration except registrationIds',
    example: BulkCommunicationSelectionModeEnum.ALL,
  })
  @IsNotEmpty()
  @IsEnum(BulkCommunicationSelectionModeEnum)
  selectionMode: BulkCommunicationSelectionModeEnum;

  @ApiPropertyOptional({
    description: 'Required when selectionMode is SELECTED or EXCLUDED',
    type: [Number],
    example: [101, 102, 103],
  })
  @ValidateIf(
    (dto: SendBulkSessionCommunicationDto) =>
      dto.selectionMode === BulkCommunicationSelectionModeEnum.SELECTED ||
      dto.selectionMode === BulkCommunicationSelectionModeEnum.EXCLUDED,
  )
  @IsArray()
  @ArrayNotEmpty()
  @IsInt({ each: true })
  @Type(() => Number)
  registrationIds?: number[];
}

/**
 * Bulk Value Card send. Value Card is email-only and carries a free-text `description` and
 * the value-card file(s) — supplied as `attachmentUrls` (e.g. PPT links). The server fetches
 * each URL and attaches the file to every email. The audience mirrors the standard bulk
 * selection (every eligible seat-allocated, active registrant of the session, or the
 * SELECTED/EXCLUDED subset).
 */
export class SendValueCardBulkDto {
  @ApiProperty({ description: 'Program id the registrations belong to', example: 5 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  programId: number;

  @ApiProperty({ description: 'Online program session id the value card is for', example: 12 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  sessionId: number;

  @ApiProperty({
    enum: BulkCommunicationSelectionModeEnum,
    description:
      'ALL = every eligible registration of the session; SELECTED = only registrationIds; ' +
      'EXCLUDED = every eligible registration except registrationIds',
    example: BulkCommunicationSelectionModeEnum.ALL,
  })
  @IsNotEmpty()
  @IsEnum(BulkCommunicationSelectionModeEnum)
  selectionMode: BulkCommunicationSelectionModeEnum;

  @ApiPropertyOptional({
    description: 'Required when selectionMode is SELECTED or EXCLUDED',
    type: [Number],
    example: [101, 102, 103],
  })
  @ValidateIf(
    (dto: SendValueCardBulkDto) =>
      dto.selectionMode === BulkCommunicationSelectionModeEnum.SELECTED ||
      dto.selectionMode === BulkCommunicationSelectionModeEnum.EXCLUDED,
  )
  @IsArray()
  @ArrayNotEmpty()
  @IsInt({ each: true })
  @Type(() => Number)
  registrationIds?: number[];

  @ApiProperty({ description: 'Value card body text, merged into the email as {description}' })
  @IsNotEmpty()
  @IsString()
  description: string;

  @ApiProperty({
    description:
      'URLs of the value-card file(s) (e.g. PPT links). The server fetches each and attaches ' +
      'it to every email.',
    type: [String],
    example: ['https://s3.../value-cards/session-12/card.pptx'],
  })
  @IsArray()
  @ArrayNotEmpty()
  @IsString({ each: true })
  attachmentUrls: string[];
}

/**
 * Single Value Card send — a top-up for ONE registration that the bulk run missed or that
 * needs a resend (e.g. a late registrant, or a bounced address since corrected).
 *
 * Deliberately carries NO `description`/`attachmentUrls`: both are read from the session's own
 * stored `valueCardDetails`, written by the bulk send. That's what makes this a top-up rather
 * than a second, independently-worded send — every recipient of a session's value card gets
 * byte-identical content, and there is no way to quietly send one person a different card.
 *
 * Consequently the bulk send must have happened first (it is what populates those details);
 * see `SessionCommunicationService.sendValueCardSingle`, which rejects the request otherwise.
 */
export class SendValueCardSingleDto {
  @ApiProperty({ description: 'Program id the registration belongs to', example: 5 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  programId: number;

  @ApiProperty({ description: 'Online program session id the value card is for', example: 12 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  sessionId: number;

  @ApiProperty({ description: 'Registration id to send the value card to', example: 101 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  registrationId: number;
}

/**
 * Bulk Common-Invite send. Notifies the staff who were issued a generated common Zoom link
 * (RM / Admin / Shoba) of the join link + meeting id/passcode. The audience is derived on the
 * server from zoom_generated_registrant_link. Program-level by default; pass `sessionId` to scope
 * the send to a single session — which also selects the per-session template variant (its message
 * carries that session's name/date/time). Use this when the program's links are generated per
 * session rather than one shared link for the whole program.
 */
export class SendCommonInviteBulkDto {
  @ApiProperty({ description: 'Program id whose common-link staff receive the invite', example: 5 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  programId: number;

  @ApiPropertyOptional({
    description:
      'Optional session id. When provided, the send is scoped to this session and uses the ' +
      'per-session common-invite template (session name/date/time). Omit for the program-level invite.',
    example: 12,
  })
  @IsOptional()
  @IsInt()
  @Type(() => Number)
  sessionId?: number;
}

/**
 * Bulk System-Links send. Email only: notifies the ADMIN users of the generated system/placeholder
 * Zoom links. Audience + link list are derived on the server. Program-level by default; pass
 * `sessionId` to scope the send (and the link table) to a single session and use the per-session
 * system-links template (session name/date/time). Use this when links are generated per session.
 */
export class SendSystemLinksBulkDto {
  @ApiProperty({ description: 'Program id whose admins receive the system links', example: 5 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  programId: number;

  @ApiPropertyOptional({
    description:
      'Optional session id. When provided, the send is scoped to this session and uses the ' +
      'per-session system-links template (session name/date/time). Omit for the program-level send.',
    example: 12,
  })
  @IsOptional()
  @IsInt()
  @Type(() => Number)
  sessionId?: number;
}

/**
 * Single send request for one registration in a session.
 */
export class SendSingleSessionCommunicationDto {
  @ApiProperty({ description: 'Program id the registration belongs to', example: 5 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  programId: number;

  @ApiPropertyOptional({
    description:
      'Online program session id. Required for session-scoped purposes (INVITE/ABSENT/VALUE_CARD); ' +
      'omitted for program-level purposes (WELCOME/PROGRAM_COMPLETION).',
    example: 12,
  })
  @ValidateIf((dto: SendSingleSessionCommunicationDto) => !isProgramLevelPurpose(dto.purpose))
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  sessionId?: number;

  @ApiProperty({ description: 'Registration id to send to', example: 101 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  registrationId: number;

  @ApiProperty({
    enum: SessionCommunicationPurposeEnum,
    example: SessionCommunicationPurposeEnum.ABSENT,
  })
  @IsNotEmpty()
  @IsEnum(SessionCommunicationPurposeEnum)
  purpose: SessionCommunicationPurposeEnum;
}

/**
 * Bulk send to general-link recipients (zoom_generated_registrant_link) for one of the four
 * general-link purposes. Reuses the seeker WELCOME/INVITE/ABSENT/PROGRAM_COMPLETION templates
 * and their business rules: status = REGISTERED is always required, and GENERAL_LINK_ABSENT/
 * GENERAL_LINK_INVITE (final session) are always gated to real absentees / all-priors-attended,
 * same as the seeker flow — there is no opt-out.
 */
export class SendGeneralLinkBulkDto {
  @ApiProperty({ description: 'Program id whose general-link recipients receive the communication', example: 5 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  programId: number;

  @ApiPropertyOptional({
    description:
      'Online program session id. Required for session-scoped purposes (GENERAL_LINK_INVITE/ ' +
      'GENERAL_LINK_ABSENT); omitted for program-level purposes (GENERAL_LINK_WELCOME/ ' +
      'GENERAL_LINK_PROGRAM_COMPLETION), which cover every session of the program.',
    example: 12,
  })
  @ValidateIf((dto: SendGeneralLinkBulkDto) => !isGeneralLinkProgramLevelPurpose(dto.purpose))
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  sessionId?: number;

  @ApiProperty({
    enum: GENERAL_LINK_PURPOSES,
    description: 'Which general-link communication to send',
    example: SessionCommunicationPurposeEnum.GENERAL_LINK_INVITE,
  })
  @IsNotEmpty()
  @IsIn(GENERAL_LINK_PURPOSES)
  purpose: SessionCommunicationPurposeEnum;
}

/**
 * Single send to one general-link recipient (one zoom_generated_registrant_link row), identified
 * by its own id — these recipients have no registrationId. `occurrence` selects between the
 * REGULAR and FINAL session templates for GENERAL_LINK_INVITE/GENERAL_LINK_ABSENT (chosen
 * manually by the caller; there's no attendance data to derive it from) and must be omitted for
 * GENERAL_LINK_WELCOME/GENERAL_LINK_PROGRAM_COMPLETION, which are occurrence-independent.
 */
export class SendGeneralLinkSingleDto {
  @ApiProperty({ description: 'Program id the recipient belongs to', example: 5 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  programId: number;

  @ApiProperty({
    description: 'zoom_generated_registrant_link row id to send to',
    example: 4210,
  })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  generatedLinkId: number;

  @ApiProperty({
    enum: GENERAL_LINK_PURPOSES,
    description: 'Which general-link communication to send',
    example: SessionCommunicationPurposeEnum.GENERAL_LINK_INVITE,
  })
  @IsNotEmpty()
  @IsIn(GENERAL_LINK_PURPOSES)
  purpose: SessionCommunicationPurposeEnum;

  @ApiPropertyOptional({
    enum: [SessionOccurrence.REGULAR, SessionOccurrence.FINAL],
    description:
      'Required for GENERAL_LINK_INVITE/GENERAL_LINK_ABSENT (picks the regular- or final-session ' +
      'template); must be omitted for GENERAL_LINK_WELCOME/GENERAL_LINK_PROGRAM_COMPLETION.',
    example: SessionOccurrence.REGULAR,
  })
  @ValidateIf((dto: SendGeneralLinkSingleDto) => generalLinkPurposeNeedsOccurrence(dto.purpose))
  @IsNotEmpty()
  @IsIn([SessionOccurrence.REGULAR, SessionOccurrence.FINAL])
  occurrence?: SessionOccurrence;
}

/**
 * Bulk general-link ("pre-test") Value Card send. Session-scoped and email-only.
 *
 * Mirrors {@link SendValueCardBulkDto} field for field, minus `selectionMode`/`registrationIds`:
 * general-link recipients have no program registration to select by id, so the audience is always
 * every general-link recipient of the session.
 */
export class SendGeneralLinkValueCardBulkDto {
  @ApiProperty({ description: 'Program id whose general-link recipients receive the card', example: 5 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  programId: number;

  @ApiProperty({ description: 'Online program session id the value card is for', example: 12 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  sessionId: number;

  @ApiProperty({ description: 'Value card body text, merged into the email as {description}' })
  @IsNotEmpty()
  @IsString()
  description: string;

  @ApiProperty({
    description:
      'URLs of the value-card file(s). The server fetches each, re-hosts it under ' +
      'pretestvaluecard/sessions/<sessionId>/, stores the result on the session as ' +
      'pretest_value_card_details, and attaches it to every email.',
    type: [String],
    example: ['https://s3.../pre-test/session-12/card.pptx'],
  })
  @IsArray()
  @ArrayNotEmpty()
  @IsString({ each: true })
  attachmentUrls: string[];
}

/**
 * Single general-link ("pre-test") Value Card send — a top-up for one generated-link recipient.
 *
 * Carries no `description`/`attachmentUrls` and no `sessionId`, all by design: the first two come
 * from the session's stored `pretest_value_card_details` (so every recipient of a session gets
 * identical material), and the session is taken from the generated link itself, which is always
 * issued against one session. Requires the bulk send to have gone out first — it is what writes
 * those details.
 */
export class SendGeneralLinkValueCardSingleDto {
  @ApiProperty({ description: 'Program id the recipient belongs to', example: 5 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  programId: number;

  @ApiProperty({ description: 'zoom_generated_registrant_link row id to send to', example: 4210 })
  @IsNotEmpty()
  @IsInt()
  @Type(() => Number)
  generatedLinkId: number;
}
