import {
  ProgramSession,
  ProgramRegistrationOnlineSession,
  BackgroundJob,
} from 'src/common/entities';
import { SessionProviderType } from 'src/common/enum/session-provider.enum';
import {
  CreateSessionInput,
  UpdateSessionInput,
  CreateSharedSessionInput,
} from 'src/common/interfaces/online-session.interface';
import { RegisterParticipantDto } from '../dto/register-participant.dto';
import { BulkRegisterParticipantsDto } from '../dto/bulk-register-participants.dto';
import {
  BulkRegistrationStart,
  BulkRegistrationFailureList,
  SessionRegistrationList,
  ProvisionStatusOverview,
  ProvisionRegistrationList,
  ProvisionRegistrationStatus,
  EligibleCountSummary,
  ProgramEligibleRegistrationList,
  ProgramEligibleRegistrationsQuery,
  RmContactOption,
  RegistrationActivationResult,
  SessionAttendanceSummary,
} from './online-session.interface';
import { RegistrationOnlineSessionActivationStatus } from 'src/common/enum/registration-online-session-activation-status.enum';
import { UserTypeFilterValue } from 'src/common/utils/user-type-filter.util';

/**
 * The PORT every online-session provider implements. The orchestrator only ever
 * talks to this contract, so it never knows whether it is driving Zoom, Teams,
 * or anything else. Implementations self-register in the
 * OnlineSessionProviderRegistry (see ProcessorRegistryService for the idiom).
 *
 * Operations return the persisted `program_session` because a session — whatever
 * the provider — is always backed by that entity; provider-specific data lives in
 * its `webinar_details` / `meeting_details` jsonb.
 */
export interface OnlineSessionProvider {
  /** Stable key used for registration/resolution (e.g. 'zoom'). */
  readonly key: SessionProviderType;

  /** Create the remote resource and persist it onto the program session. */
  create(input: CreateSessionInput): Promise<ProgramSession>;

  /**
   * Provision ONE recurring resource shared across several program sessions (the
   * "same link" model) and persist one session row per program session, all
   * sharing its external id. Returns the persisted sessions.
   */
  createShared(input: CreateSharedSessionInput): Promise<ProgramSession[]>;

  /**
   * Fire-and-forget: auto-issue general (role + placeholder/"system") join links for
   * ONE session, scoped by session id. No communication (invite/system-link email or
   * WhatsApp) is sent — that stays specific to the `createShared` ("same link") flow.
   */
  generateGeneralLinks(session: ProgramSession, actorUserId?: number): void;

  /** Update the remote resource + persisted details for an existing session. */
  update(session: ProgramSession, input: UpdateSessionInput): Promise<ProgramSession>;

  /** Delete the remote resource and clear the persisted details. */
  remove(session: ProgramSession, actorUserId?: number): Promise<void>;

  // ---------------------------------------------------------------------------
  // Registration (admin operations on a provisioned session)
  // ---------------------------------------------------------------------------

  /** Register / unregister / downgrade a single program registrant. */
  register(
    input: RegisterParticipantDto,
  ): Promise<ProgramRegistrationOnlineSession | void>;

  /** Kick off bulk registration of a program's eligible registrants (background job). */
  bulkRegister(
    input: BulkRegisterParticipantsDto,
    actorUserId?: number,
  ): Promise<BulkRegistrationStart>;

  /** Poll the status/progress of a bulk registration job. */
  bulkRegisterStatus(jobId: number): Promise<BackgroundJob>;

  /**
   * Paginated, enriched per-item failure list for a bulk registration job.
   * `rmContactId`, when given (an RM caller), restricts the list to their own contacts.
   */
  bulkRegisterFailures(
    jobId: number,
    paging: { page: number; limit: number },
    rmContactId?: number,
  ): Promise<BulkRegistrationFailureList>;

  /** Re-run only the failed items of an earlier bulk job as a fresh job. */
  retryBulkRegistration(jobId: number, actorUserId?: number): Promise<BulkRegistrationStart>;

  /**
   * Current failure list for a whole program (optionally one session), merged
   * across the program's job chain and de-staled — the by-program/session view
   * of "what's still failing" rather than a single job's snapshot.
   * `rmContactId`, when given (an RM caller), restricts the list to their own contacts.
   */
  getProgramRegistrationFailures(
    programId: number,
    query: { sessionId?: number; page: number; limit: number },
    rmContactId?: number,
  ): Promise<BulkRegistrationFailureList>;

  /** Re-run the current failures of a whole program (optionally one session) as a fresh job. */
  retryProgramRegistration(
    programId: number,
    sessionId?: number,
    actorUserId?: number,
  ): Promise<BulkRegistrationStart>;

  /**
   * Program-level provisioning overview: every online session of the program (or
   * one session), each with live counts — total eligible, generated, failed, and
   * yet-to-generate. `rmContactId`, when given (an RM caller), scopes the
   * eligible/counted set to their own contacts. `userType`, when given, narrows that
   * same eligible set to the listed account types (Org/Seeker) — so every count
   * reflects it (see `resolveUserTypeFilter`).
   */
  getProgramProvisionStatus(
    programId: number,
    sessionId?: number,
    rmContactId?: number,
    userType?: UserTypeFilterValue[],
  ): Promise<ProvisionStatusOverview>;

  /**
   * Per-session provisioning drilldown: the eligible registrations bucketed into
   * generated / failed / yet-to-generate, optionally filtered to one bucket.
   * `rmContactId`, when given (an RM caller), scopes the set to their own contacts;
   * `query.userType` scopes it to the listed account types (Org/Seeker).
   */
  getSessionProvisionRegistrations(
    sessionId: number,
    query: {
      status?: ProvisionRegistrationStatus;
      page: number;
      limit: number;
      userType?: UserTypeFilterValue[];
    },
    rmContactId?: number,
  ): Promise<ProvisionRegistrationList>;

  /**
   * Paginated admin "registration ↔ join URL" view for a session. `rmContactId`,
   * when given (an RM caller), scopes the set to their own contacts.
   */
  listRegistrations(
    sessionId: number,
    query: { page: number; limit: number; search?: string },
    rmContactId?: number,
  ): Promise<SessionRegistrationList>;

  /** Excel export of the registration ↔ join-URL view; returns the file URL. */
  exportRegistrations(
    sessionId: number,
    search?: string,
    rmContactId?: number,
  ): Promise<{ fileUrl: string }>;

  /**
   * Registration-level activation toggle: flips the registrant's state for every
   * UPCOMING session of their program — deactivating removes those sessions'
   * provider join links, reactivating re-provisions them. Past and currently-live
   * sessions are out of scope.
   */
  setRegistrationActivation(
    registrationId: number,
    activationStatus: RegistrationOnlineSessionActivationStatus,
    reason: string | null | undefined,
    actingUserId: number | null | undefined,
  ): Promise<RegistrationActivationResult>;

  /** Live active/inactive breakdown of one session's eligible (provisioned) registrants. */
  getEligibleCount(sessionId: number): Promise<EligibleCountSummary>;

  /** Paginated, searchable list of a program's seat-allocated registrants — no session context. */
  listProgramEligibleRegistrations(
    programId: number,
    query: ProgramEligibleRegistrationsQuery,
  ): Promise<ProgramEligibleRegistrationList>;

  /** The dynamic half of the eligible-registrations filter set: every user holding the RM role. */
  getProgramEligibleRmContacts(): Promise<RmContactOption[]>;

  /**
   * Per-session Registered/Attended/Absent + duration-bucket rollup, keyed by
   * program-session id. `rmContactId`, when given (an RM caller), scopes every
   * count to their own contacts.
   */
  getSessionAttendanceSummary(
    sessions: ProgramSession[],
    rmContactId?: number,
  ): Promise<Map<number, SessionAttendanceSummary>>;
}
