/**
 * Ports — the interfaces the domain depends on.
 *
 * Everything outside the domain (database, disk, SMTP, third-party APIs) is
 * reached through one of these. Each has a credential-free development
 * implementation in the app layer, which is what lets every workflow in
 * SPEC.md be demonstrated locally without a single external account
 * (ARCHITECTURE.md §9).
 */

/* -------------------------------------------------------------------------- */
/* Time and identity                                                          */
/* -------------------------------------------------------------------------- */

/** Injected so tests can freeze time rather than sleep. */
export interface Clock {
  now(): Date;
}

export const systemClock: Clock = {
  now: () => new Date(),
};

export interface IdGenerator {
  /** A collision-resistant, non-sequential identifier. */
  generate(): string;
}

/* -------------------------------------------------------------------------- */
/* File storage                                                               */
/* -------------------------------------------------------------------------- */

export interface StoredFileMetadata {
  readonly key: string;
  readonly originalFilename: string;
  readonly contentType: string;
  readonly sizeBytes: number;
  readonly checksumSha256: string;
}

export interface PutFileInput {
  readonly content: Uint8Array;
  readonly originalFilename: string;
  readonly contentType: string;
  /** Logical folder, e.g. `applicant-requirements`. Never user-controlled. */
  readonly prefix: string;
}

/**
 * Private object storage. There is deliberately no "public URL" operation:
 * uploaded student documents must never be publicly enumerable (SPEC.md §25),
 * so the only way to read one back is a short-lived signed link.
 */
export interface FileStorage {
  put(input: PutFileInput): Promise<StoredFileMetadata>;
  get(key: string): Promise<Uint8Array>;
  delete(key: string): Promise<void>;
  exists(key: string): Promise<boolean>;
  /** Returns a URL that expires after `ttlSeconds`. */
  createSignedDownloadUrl(key: string, ttlSeconds: number): Promise<string>;
  /** Verifies a signed URL's signature and expiry. */
  verifySignedDownload(key: string, signature: string, expiresAt: number): boolean;
}

/* -------------------------------------------------------------------------- */
/* Malware scanning                                                           */
/* -------------------------------------------------------------------------- */

export type ScanVerdict = 'CLEAN' | 'INFECTED' | 'UNSCANNED';

export interface ScanResult {
  readonly verdict: ScanVerdict;
  readonly detail?: string;
}

/** Production hook required by SPEC.md §25. The dev adapter reports UNSCANNED. */
export interface MalwareScanner {
  scan(content: Uint8Array, filename: string): Promise<ScanResult>;
}

/* -------------------------------------------------------------------------- */
/* Notifications                                                              */
/* -------------------------------------------------------------------------- */

export interface EmailMessage {
  readonly to: string;
  readonly subject: string;
  readonly text: string;
  readonly html?: string;
  readonly replyTo?: string;
}

export interface Mailer {
  /**
   * Whether a sent message actually leaves the machine.
   *
   * The development driver prints messages to the console, which is genuinely
   * useful — but a caller that treats printing as sending will tell a user
   * "we have emailed you" about a message that will never arrive, and they will
   * wait for it. Anything that reports delivery to a person must check this
   * first (ASSUMPTIONS.md G21).
   */
  readonly delivers: boolean;
  send(message: EmailMessage): Promise<void>;
}

/**
 * Notification templates live in the database so wording can change without a
 * deployment (SPEC.md §27). This is the render contract the notification
 * service uses.
 */
export interface NotificationTemplate {
  readonly key: string;
  readonly subject: string;
  readonly body: string;
}

export interface TemplateRenderer {
  render(template: NotificationTemplate, variables: Record<string, string>): {
    subject: string;
    body: string;
  };
}

/* -------------------------------------------------------------------------- */
/* Background work                                                            */
/* -------------------------------------------------------------------------- */

export interface JobPayloadMap {
  'notification.send': { notificationId: string };
  'report.generate': { reportId: string; requestedByUserId: string };
  'import.process': { importJobId: string };
  'document.scan': { attachmentId: string };
}

export type JobName = keyof JobPayloadMap;

/**
 * Queue abstraction. The inline implementation runs the handler synchronously,
 * which is adequate for the MVP; moving to Redis/BullMQ changes only the
 * binding, never a call site (SPEC.md §5).
 */
export interface JobQueue {
  enqueue<K extends JobName>(name: K, payload: JobPayloadMap[K]): Promise<void>;
}

/* -------------------------------------------------------------------------- */
/* Rate limiting and anti-bot                                                 */
/* -------------------------------------------------------------------------- */

export interface RateLimitResult {
  readonly allowed: boolean;
  readonly remaining: number;
  readonly retryAfterSeconds: number;
}

export interface RateLimiter {
  /** `key` identifies the subject, e.g. `login:jane@example.local` or an IP. */
  consume(key: string, limit: number, windowSeconds: number): Promise<RateLimitResult>;
  reset(key: string): Promise<void>;
}

export interface CaptchaVerifier {
  verify(token: string | null, remoteIp: string | null): Promise<boolean>;
}

/* -------------------------------------------------------------------------- */
/* Entrance examination                                                       */
/* -------------------------------------------------------------------------- */

export type ExamOutcome = 'PASSED' | 'FAILED' | 'FOR_REVIEW' | 'PENDING';

export interface ExamBookingRequest {
  readonly applicationId: string;
  readonly examScheduleId: string | null;
  /** Reference issued by an external system, when one is in use. */
  readonly externalReference?: string | null;
}

export interface ExamBookingResult {
  readonly bookingReference: string;
  readonly scheduledAt: Date | null;
  readonly venue: string | null;
  readonly isExternal: boolean;
}

export interface ExamResultRecord {
  readonly bookingReference: string;
  readonly rawScore: number | null;
  readonly percentile: number | null;
  readonly outcome: ExamOutcome;
  readonly takenAt: Date | null;
  readonly remarks?: string | null;
}

/**
 * The adapter boundary SPEC.md §12 asks for. `internal` manages exam schedules
 * in this system; `external` records a booking reference from an existing
 * ILA-CAT style application. Admissions logic is identical either way.
 */
export interface EntranceExamProvider {
  readonly mode: 'internal' | 'external';
  book(request: ExamBookingRequest): Promise<ExamBookingResult>;
  cancel(bookingReference: string, reason: string): Promise<void>;
  fetchResult(bookingReference: string): Promise<ExamResultRecord | null>;
}

/* -------------------------------------------------------------------------- */
/* Lead ingestion                                                             */
/* -------------------------------------------------------------------------- */

export interface InboundLead {
  readonly externalId: string | null;
  readonly fullName: string;
  readonly email: string | null;
  readonly mobile: string | null;
  readonly programOfInterest: string | null;
  readonly sourceKey: string;
  readonly campaign: string | null;
  readonly utm: Record<string, string | null>;
  readonly capturedAt: Date;
  readonly rawPayload: Record<string, unknown>;
}

/**
 * Future Meta Lead Ads synchronization (SPEC.md §9). The mock returns a fixed
 * deterministic set so the CRM import path is demonstrable without Meta
 * credentials; CSV import uses the same `InboundLead` shape.
 */
export interface LeadAdsProvider {
  readonly name: string;
  fetchNewLeads(since: Date): Promise<InboundLead[]>;
}

/* -------------------------------------------------------------------------- */
/* Payments                                                                   */
/* -------------------------------------------------------------------------- */

export interface GatewayChargeRequest {
  readonly assessmentId: string;
  readonly amountCentavos: number;
  readonly description: string;
  readonly returnUrl: string;
  readonly idempotencyKey: string;
}

export interface GatewayChargeResult {
  readonly providerReference: string;
  readonly redirectUrl: string | null;
  readonly status: 'PENDING' | 'SUCCEEDED' | 'FAILED';
}

/**
 * Online payment gateway. Explicitly out of MVP scope (SPEC.md §38); the
 * interface exists so the cashier module is not written against a specific
 * provider, and the null adapter fails loudly rather than pretending to charge.
 */
export interface PaymentGateway {
  readonly name: string;
  readonly isConfigured: boolean;
  createCharge(request: GatewayChargeRequest): Promise<GatewayChargeResult>;
  verifyWebhook(rawBody: string, signature: string): boolean;
}

/* -------------------------------------------------------------------------- */
/* Audit                                                                      */
/* -------------------------------------------------------------------------- */

export type AuditAction =
  | 'CREATE'
  | 'UPDATE'
  | 'DELETE'
  | 'READ_SENSITIVE'
  | 'APPROVE'
  | 'REJECT'
  | 'SUBMIT'
  | 'PUBLISH'
  | 'PAYMENT_POST'
  | 'PAYMENT_VOID'
  | 'GRADE_CHANGE'
  | 'DOCUMENT_RELEASE'
  | 'EXPORT'
  | 'LOGIN_SUCCESS'
  | 'LOGIN_FAILURE'
  | 'LOGOUT'
  | 'PASSWORD_RESET'
  | 'PERMISSION_CHANGE'
  | 'SETTING_CHANGE'
  | 'IMPORT';

export interface AuditEntry {
  readonly actorUserId: string | null;
  readonly actorRoles: readonly string[];
  readonly action: AuditAction;
  readonly targetType: string;
  readonly targetId: string | null;
  /**
   * Non-secret before/after summary. The writer redacts known sensitive keys;
   * document contents and credentials must never be passed here (SPEC.md §25).
   */
  readonly before?: Record<string, unknown> | null;
  readonly after?: Record<string, unknown> | null;
  readonly reason?: string | null;
  readonly ipAddress?: string | null;
  readonly userAgent?: string | null;
  readonly sessionId?: string | null;
  readonly metadata?: Record<string, unknown> | null;
}

/**
 * Append-only audit writer. There is no update or delete operation by design —
 * the interface itself makes tampering unexpressible in application code
 * (SPEC.md §8, §26).
 */
export interface AuditWriter {
  write(entry: AuditEntry): Promise<void>;
}

/* -------------------------------------------------------------------------- */
/* Password hashing                                                           */
/* -------------------------------------------------------------------------- */

export interface PasswordHasher {
  hash(plaintext: string): Promise<string>;
  verify(hash: string, plaintext: string): Promise<boolean>;
  /** True when the stored hash uses outdated parameters and should be upgraded. */
  needsRehash(hash: string): boolean;
}
