import { ProgramSession } from 'src/common/entities';
import { OnlineTypeEnum } from 'src/common/enum/online-type.enum';
import { ExportJobStatus } from 'src/common/enum/export-job-status.enum';
import { JobTypeEnum } from 'src/common/enum/job-type.enum';
import { RegistrationOnlineSessionActivationStatus } from 'src/common/enum/registration-online-session-activation-status.enum';
import {
  ProgramEligibleKpiCategory,
  ProgramEligibleKpiFilter,
} from 'src/common/enum/program-eligible-kpi.enum';
import { SessionCommunicationStatusSummary } from 'src/session-communication/session-communication.types';
import {
  MeetingDetails,
  WebinarDetails,
} from 'src/common/interfaces/online-details.interface';

/** Raw page returned by the repository: rows + total count. */
export interface OnlineSessionListResult {
  data: ProgramSession[];
  total: number;
}

/** Pagination envelope returned alongside a list of online sessions. */
export interface OnlineSessionPagination {
  totalPages: number;
  pageNumber: number;
  pageSize: number;
  totalRecords: number;
  numberOfRecords: number;
}

/** Service-level paginated list of online sessions. */
export interface OnlineSessionList {
  data: ProgramSession[];
  pagination: OnlineSessionPagination;
}

/**
 * One failed entry in a bulk create. Provisioning hits an external provider
 * (Zoom), so a batch cannot be atomic — successes and failures are reported
 * side by side, keyed by the program session the entry targeted.
 */
export interface BulkOnlineSessionFailure {
  programSessionId: number;
  error: string;
}

/** Outcome of a bulk online-session create: provisioned sessions + failures. */
export interface BulkOnlineSessionResult {
  created: ProgramSession[];
  failed: BulkOnlineSessionFailure[];
}

/** Outcome of kicking off a bulk registration background job. */
export interface BulkRegistrationStart {
  jobId: number;
  status: ExportJobStatus;
  total: number;
}

/**
 * One registrant that could not be pushed to the provider during a bulk job.
 * `reason` is a stable machine code (an ERROR_CODES value, e.g. `Z_BR_001`);
 * `message` is the human-readable text. Stored append-style in
 * `background_jobs.metadata.failures[]` (Open Q7: jsonb for M1).
 */
export interface BulkRegistrationFailure {
  registrationId: number;
  sessionId: number;
  reason: string;
  message?: string;
}

/**
 * One registrant excluded from a bulk job before any provider call, with the
 * reason it was skipped (`NO_SEAT`, or the disqualifying registration status).
 */
export interface BulkRegistrationIneligible {
  registrationId: number;
  registrationSeqNumber: string | null;
  reason: string;
}

/**
 * Shape persisted in `background_jobs.metadata` for a BULK_ZOOM_REGISTRATION job.
 * Carries the run config plus the transparency data (eligibility breakdown +
 * per-item failures) the admin polls for.
 */
export interface BulkRegistrationJobMetadata {
  sessionIds: number[];
  role: string;
  batchSize: number;
  eligibleCount: number;
  ineligible: BulkRegistrationIneligible[];
  failures: BulkRegistrationFailure[];
  /** Set when this job re-runs the failed items of an earlier job. */
  retryOfJobId?: number;
}

/**
 * Detailed status of a bulk registration job: the running counts plus the
 * eligibility breakdown. Returned by the poll endpoint so the admin sees the
 * progress and *why* items were skipped before the job ran. The per-item failure
 * list is served separately (and paginated + enriched) by the failures endpoint,
 * since it can be large and is only needed once a job reports failures.
 */
export interface BulkRegistrationStatus {
  jobId: number;
  type: string;
  status: ExportJobStatus;
  total: number;
  generated: number;
  skipped: number;
  failed: number;
  eligible: number;
  ineligible: BulkRegistrationIneligible[];
  errorMessage: string | null;
  retryOfJobId: number | null;
  completedAt: Date | null;
}

/**
 * One failed bulk-registration item enriched for the admin failures view: the
 * stored `reason`/`message` plus the registrant's identity (seq number, name,
 * contact) and the target session's name, so the admin can act on it directly
 * instead of resolving raw ids. Enrichment fields are null when the underlying
 * registration/session has since been deleted.
 */
export interface BulkRegistrationFailureRow {
  registrationId: number;
  registrationSeqNumber: string | null;
  fullName: string | null;
  email: string | null;
  mobile: string | null;
  sessionId: number;
  sessionName: string | null;
  reason: string;
  message?: string;
  /** The registrant's join link on this session's extension row, if one exists (regardless of status). */
  joinUrl: string | null;
  /** Null when the registrant has no extension row for this session. */
  activationStatus: RegistrationOnlineSessionActivationStatus | null;
}

/** Paginated, enriched per-item failure list for a bulk registration job. */
export interface BulkRegistrationFailureList {
  data: BulkRegistrationFailureRow[];
  pagination: { page: number; limit: number; total: number };
}

/**
 * One row of the admin "registration ↔ join URL" view for a session. Neutral
 * shape so the orchestrator never depends on a provider's repository types.
 */
export interface SessionRegistrationRow {
  registrationId: number;
  registrationSeqNumber: string | null;
  fullName: string | null;
  email: string | null;
  mobile: string | null;
  registrationStatus: string;
  seatAllocated: boolean;
  joinUrl: string | null;
  isPanelist: boolean | null;
  /** Null when the registrant has no provisioned extension for this session yet. */
  activationStatus: RegistrationOnlineSessionActivationStatus | null;
}

/** Paginated admin registration ↔ join-URL view of a session. */
export interface SessionRegistrationList {
  data: SessionRegistrationRow[];
  pagination: { page: number; limit: number; total: number };
}

/** One row of a program's seat-allocated registrant list — no single session context, so `joinUrl` is the registrant's latest extension row across any session. */
export interface ProgramEligibleRegistrationRow {
  registrationId: number;
  registrationSeqNumber: string | null;
  fullName: string | null;
  email: string | null;
  mobile: string | null;
  seatAllocated: 'Yes' | 'No';
  gender: string | null;
  profileUrl: string | null;
  activationStatus: string;
  rmContact: string | null;
  paymentStatus: string | null;
  registrationMode: string | null;
  age: number | null;
  city: string | null;
  joinUrl: string | null;
  /** Attended vs completed program sessions, e.g. "3/5" (attended = is_attended; completed = ended sessions). */
  attendedSessions: string;
  /**
   * Per-session Attended/Absent breakdown across the program's conducted sessions,
   * ordered chronologically — lets the frontend render which sessions the
   * registrant attended rather than only the "3/5" summary.
   */
  sessionAttendance: ProgramSessionAttendance[];
}

/** Lifecycle state of a session relative to now. */
export type SessionAttendanceState = 'not_started' | 'in_progress' | 'completed';

/** One session's attendance status for a single registrant (see {@link ProgramEligibleRegistrationRow.sessionAttendance}). */
export interface ProgramSessionAttendance {
  sessionId: number;
  name: string | null;
  /** 'not_started' (starts in the future), 'in_progress' (started, not ended), or 'completed' (ended). */
  sessionState: SessionAttendanceState;
  /** true = attended, false = absent. */
  status: boolean;
}

/**
 * Program-wide KPI tiles for the eligible-registrations screen — see the zoom
 * module's mirror ({@link ProgramEligibleKpis} in zoom-registration.interface.ts)
 * for the full field-by-field rationale. Always computed over the full eligible
 * set for the program (RM scope aside); ignores the list's search/filters.
 */
export interface ProgramEligibleKpis {
  totalEligible: number;
  active: number;
  inactive: number;
  absentAtLeastOnce: number;
  presentForAll: number;
  totalSessions: number;
  eligibleForFinalSession: number | null;
  /** Strict complement of eligibleForFinalSession (totalEligible - eligibleForFinalSession); null iff that is null. */
  notEligibleForFinalSession: number | null;
}

/**
 * One eligible-registrations KPI tile — see the zoom module's mirror
 * ({@link ProgramEligibleKpiTile} in zoom-registration.interface.ts) for the full rationale.
 */
export interface ProgramEligibleKpiTile {
  label: string;
  value: number;
  kpiCategory?: ProgramEligibleKpiCategory;
  kpiFilter?: ProgramEligibleKpiFilter;
}

/** Paginated program-wide seat-allocated registrant list. */
export interface ProgramEligibleRegistrationList {
  data: ProgramEligibleRegistrationRow[];
  pagination: { offset: number; limit: number; total: number };
  kpis: ProgramEligibleKpiTile[];
}

/** Query for {@link ProgramEligibleRegistrationRow}'s list — see the zoom module's mirror for the `filters` contract. */
export interface ProgramEligibleRegistrationsQuery {
  offset: number;
  limit: number;
  search?: string;
  sortKey?: string;
  sortOrder?: 'ASC' | 'DESC';
  filters?: Record<string, string | string[]>;
  /** Server-derived: the caller's own user id when they hold the RM role — scopes results to their contacts. */
  rmContactId?: number;
  /** v1 only — see the zoom module's mirror for the full contract. Must be passed together with `kpiFilter`. */
  kpiCategory?: ProgramEligibleKpiCategory;
  /** v1 only — see the zoom module's mirror for the full contract. `ALL`/omitted = no narrowing. */
  kpiFilter?: ProgramEligibleKpiFilter;
}

/** One RM, for the `rmContact` filter's option list — filter by id (a real FK), not name. */
export interface RmContactOption {
  id: number;
  name: string;
}

/** Outcome of the registration-level activation toggle: which upcoming sessions' rows were flipped. */
export interface RegistrationActivationResult {
  registrationId: number;
  activationStatus: RegistrationOnlineSessionActivationStatus;
  updatedOnlineSessionIds: number[];
}

/** Live active/inactive breakdown of one session's eligible (provisioned) registrants. */
export interface EligibleCountSummary {
  onlineSessionId: number;
  totalEligible: number;
  activeCount: number;
  inactiveCount: number;
}

/**
 * Provisioning counts for one online session, computed live: of the
 * registrants active for THIS session (program-eligible minus anyone
 * deactivated for this specific session), how many have a Zoom link
 * generated, how many are still failing, and how many are yet to be
 * generated. The three buckets partition `totalEligible`:
 * generated + failed + yetToGenerate === totalEligible. Session-scoped, so it
 * can differ across sessions of the same program.
 */
export interface ProvisionCounts {
  totalEligible: number;
  generated: number;
  failed: number;
  yetToGenerate: number;
  /**
   * Live count of `zoom_generated_registrant_link` rows (staff/placeholder links, no
   * `ProgramRegistration` of their own) for this session — an independent 5th bucket,
   * NOT part of the `totalEligible` partition above (see {@link ProvisionRegistrationStatus}'s
   * `'generalLink'` value).
   */
  generalLinks: number;
}

/**
 * One provisioning KPI tile — label/value plus the `filter` to re-issue
 * `GET provision-status/:sessionId/registrations` with (`?status=<filter>`) to drill into the
 * rows behind this tile. `filter` is omitted for a tile that doesn't correspond to one bucket
 * (e.g. `'Total Eligible'`, the sum of generated + failed + yetToGenerate) — nothing to narrow to.
 */
export interface ProvisionKpiTile {
  label: string;
  value: number;
  filter?: ProvisionRegistrationStatus;
}

/** One session row of the program provisioning overview: full details + counts + the tile view of those counts. */
export interface ProvisionSessionStatus {
  session: ProgramSession;
  counts: ProvisionCounts;
  kpis: ProvisionKpiTile[];
}

/**
 * Program-level provisioning overview: every online session of the program (or a
 * single session when scoped), each with its own session-scoped provisioning
 * counts (see {@link ProvisionCounts}). The top-level `totalEligible` here is
 * program-scoped (every seat-allocated registrant of the program, regardless
 * of any session) — NOT the same as any individual session's `counts.totalEligible`.
 */
export interface ProvisionStatusOverview {
  programId: number;
  totalEligible: number;
  sessions: ProvisionSessionStatus[];
}

/**
 * Which provisioning bucket a registration falls into for a session. `'generalLink'` is
 * different from the other three: it's not a `ProgramRegistration` bucket at all, but the
 * pre-generated staff/placeholder rows from `zoom_generated_registrant_link` (see
 * {@link ProvisionRegistrationRow.registrationId}).
 */
export type ProvisionRegistrationStatus = 'generated' | 'failed' | 'pending' | 'generalLink';

/** One registration row in the per-session provisioning drilldown. */
export interface ProvisionRegistrationRow {
  /** Null for a `'generalLink'` row — a pre-generated staff/placeholder link with no `ProgramRegistration` of its own. */
  registrationId: number | null;
  registrationSeqNumber: string | null;
  fullName: string | null;
  email: string | null;
  mobile: string | null;
  /** Display value (first letter capitalized, e.g. 'Generated') — filter by re-sending {@link ProvisionRegistrationStatus}'s lowercase value instead, not this string. */
  status: string;
  /** The registrant's individual join link; present (non-null) only when generated. */
  joinUrl: string | null;
  /**
   * Display value (first letter capitalized, e.g. 'Active'). Reflects the session-specific
   * extension row when one exists; otherwise falls back to the registration's own program-level
   * activation rollup (only reachable for an upcoming, not-yet-provisioned session — a started
   * session excludes such a registrant from this list entirely). Always null for a
   * `'generalLink'` row.
   */
  activationStatus: string | null;
  /** Failure code + message, present only when status is 'failed'. */
  reason?: string;
  message?: string;
  /**
   * The `zoom_generated_registrant_link` row's own id — set only on a `'generalLink'` row, the id
   * `/session-communication/general-link/single` takes as `generatedLinkId` to send a one-off
   * communication to this recipient. Null for every registration-based row (generated/failed/pending).
   */
  generatedLinkId: number | null;
}

/**
 * Per-session provisioning drilldown: the eligible registrations bucketed into
 * generated / failed / yet-to-generate / generalLink, optionally filtered to one
 * bucket, plus a summary count and pagination.
 */
export interface ProvisionRegistrationList {
  sessionId: number;
  summary: ProvisionCounts;
  kpis: ProvisionKpiTile[];
  data: ProvisionRegistrationRow[];
  pagination: { page: number; limit: number; total: number };
}

/**
 * Latest bulk-communication outcome for one purpose (Invite/Absent) on a session.
 * Derived from the newest hdb_session_communication_status row for that (session, purpose).
 *
 * Alias of the session-communication domain's own summary shape — that domain writes those rows,
 * so it owns the projection; the analytics general-attendees response surfaces the same shape for
 * the general-link Value Card. Kept as a named export here so this module's long-standing
 * consumers keep importing it from where they always have.
 */
export type SessionBulkCommunicationStatus = SessionCommunicationStatusSummary;

/**
 * Per-session bulk-communication summary exposed on the online-session GET responses.
 * Invite/Absent/Value Card are session-scoped; Welcome and Program Completion are program-level
 * (their status rows have no session id), so the same status is echoed onto every session of the
 * program. null = never triggered.
 */
export interface SessionBulkCommunications {
  welcome: SessionBulkCommunicationStatus | null;
  invite: SessionBulkCommunicationStatus | null;
  absent: SessionBulkCommunicationStatus | null;
  valueCard: SessionBulkCommunicationStatus | null;
  programCompletion: SessionBulkCommunicationStatus | null;
}

/**
 * Program-level bulk-communication summary exposed on the eligible-registrations GET response.
 * Only the program-scoped purposes apply here — Welcome and Program Completion (their status rows
 * have no session id); Invite/Absent/Value Card are session-scoped and have no single-program value.
 * null = never triggered.
 */
export interface ProgramBulkCommunications {
  welcome: SessionBulkCommunicationStatus | null;
  programCompletion: SessionBulkCommunicationStatus | null;
}

/**
 * Still-running background job (status PENDING/PROCESSING; any JobTypeEnum) created by the
 * session's own creator that targets this session (job.metadata.sessionIds includes it).
 * null when no such job exists.
 */
export interface PendingSessionJob {
  id: number;
  type: JobTypeEnum;
  status: ExportJobStatus;
  createdAt: Date;
  completedAt: Date | null;
}

/**
 * Attendee-facing view of a session. The host `startUrl` (which grants host
 * control) is stripped from the nested details before this is returned.
 */
export interface OnlineSessionResponse {
  id: number;
  programId: number | null;
  programSessionId: number;
  name: string;
  onlineType: OnlineTypeEnum;
  startsAt: Date;
  endsAt: Date;
  registrationStartsAt: Date;
  registrationEndsAt: Date;
  webinarDetails: WebinarDetails | null;
  meetingDetails: MeetingDetails | null;
  /** Zoom-reported actual meeting end time (webhook-derived), independent of the scheduled `endsAt`. */
  actualMeetingEndsAt: Date | null;
  /** Minutes before start the host/admin can start the session (store-only; FE gates). */
  hostStartOpensMinutesBefore: number | null;
  /** Whether the session has ended (endsAt is in the past). */
  completed: boolean;
  /** Whether the final-session-confirm API has been run for this (final) session. */
  finalized: boolean;
  /** Session length in minutes (startsAt→endsAt); list table header key 'duration'. */
  duration?: number | null;
  /** Invite/Absent bulk-communication status. Present only on GET responses. */
  communications?: SessionBulkCommunications;
  /** Registered/attended/absent + duration-bucket counts. Present only on GET responses. */
  attendanceSummary?: SessionAttendanceSummary;
  /**
   * Attendance counts flattened onto the row so each ONLINE_SESSION_LIST_TABLE_HEADERS key
   * resolves directly. Present only alongside attendanceSummary (GET list/detail responses).
   */
  registered?: number;
  attended?: number;
  absent?: number;
  under60?: number;
  from60to90?: number;
  from90to120?: number;
  /** Last Value Card sent for the session (description + uploaded document URLs); null if none. */
  valueCardDetails: { description: string; documentUrls: string[] } | null;
  /** Same shape, for the general-link ("pre-test") Value Card — see ProgramSession.pretestValueCardDetails. */
  pretestValueCardDetails: { description: string; documentUrls: string[] } | null;
  /**
   * Still-running background job created by this session's creator that targets this session;
   * null if none currently running. Present only on GET responses.
   */
  pendingJob?: PendingSessionJob | null;
}

/** Duration buckets among attended seekers, in minutes: <60, 60-90, 90+ (from90to120 is open-ended, no upper bound). */
export interface SessionAttendanceDurationBuckets {
  under60: number;
  from60to90: number;
  from90to120: number;
}

/**
 * Per-session attendance rollup for the online-session list/detail views:
 * how many are currently active for this session (registered), how many
 * actually attended vs not, and how attended seekers' durations broke down.
 * `registered`/`attended`/`absent`/buckets all exclude registrants deactivated
 * for THIS specific session (see getEligibleActivationSummaries and
 * ZoomAnalyticsAttendeeSummaryRepository.listBySession's activation checks) and
 * are scoped to the caller's own contacts when they hold the RM role.
 */
export interface SessionAttendanceSummary {
  onlineSessionId: number;
  /** Currently-active registrant count for this session — a deactivated registrant is not counted. */
  registered: number;
  attended: number;
  absent: number;
  durationBuckets: SessionAttendanceDurationBuckets;
}
