/**
 * Shared, ORM-agnostic payment provider contract.
 *
 * These types are consumed by the @edr/payment-providers gateway package and by each
 * app's payment module. The enums intentionally mirror the string values of every app's
 * ORM-generated payment enums (Prisma `PaymentIntentStatus`/`PaymentMethodType` in the
 * passenger API, the TypeORM column enum in the freight API), so mapping between an app's
 * ORM enum and these shared enums is a one-line cast rather than a translation table.
 */

export enum ProviderPaymentStatus {
  REQUIRES_ACTION = "REQUIRES_ACTION",
  PROCESSING = "PROCESSING",
  SUCCEEDED = "SUCCEEDED",
  FAILED = "FAILED",
  CANCELLED = "CANCELLED",
}

export enum ProviderMethod {
  TELEBIRR = "TELEBIRR",
  CBE_BIRR = "CBE_BIRR",
  EBIRR = "EBIRR",
  WAAFI = "WAAFI",
  CARD = "CARD",
  DMONEY = "DMONEY",
  CAC_BANK = "CAC_BANK",
}

export type PaymentPlatform = "web" | "mobile";

export type ClientAction =
  | { type: "REDIRECT"; url: string }
  | {
      type: "LAUNCH_APP";
      appId: string;
      receiveCode?: string;
      shortCode: string;
    }
  | {
      type: "COLLECT_OTP";
      providerOrderId: string;
      message?: string;
    };

export interface ProviderInitiationInput {
  merchantOrderId: string;
  /** Domain-neutral order reference (booking reference, shipment order reference, etc.). */
  orderRef: string;
  amountMinor: number;
  currency: string;
  platform?: PaymentPlatform;
  /**
   * Payer account identifier (e.g. mobile-wallet MSISDN in full international format).
   * Optional and provider-specific: some wallet providers (e.g. Waafi HPP with
   * MWALLET_ACCOUNT) require the payer's phone number up front to pre-fill the hosted page.
   */
  payerAccount?: string;
  /** Optional caller-supplied redirect targets for redirect/HPP-style providers. */
  returnUrl?: string;
  redirectUrl?: string;
  /** Where the browser lands when the hosted page fails/cancels (UX only — never trusted). */
  failureUrl?: string;
}

export interface ProviderInitiationResult {
  providerOrderId: string;
  clientAction: ClientAction;
  expiresAt: Date;
  rawInitiation: Record<string, unknown>;
}

export interface ProviderStatus {
  status: ProviderPaymentStatus;
  providerTxnId?: string;
  failureCode?: string;
  failureMessage?: string;
  rawResponse: Record<string, unknown>;
}

export interface PaymentProvider {
  readonly method: ProviderMethod;
  initiate(input: ProviderInitiationInput): Promise<ProviderInitiationResult>;
  queryStatus(merchantOrderId: string): Promise<ProviderStatus>;
}

/* ------------------------------------------------------------------------------------------------
 * Payment microservice contracts (docs/payment-service)
 *
 * Shared shapes exchanged between the payment microservice (apps/edr-payment-api) and the
 * domain apps (passenger/freight). Both sides import these so the wire format cannot drift.
 * ---------------------------------------------------------------------------------------------- */

/** Which domain app owns the order being paid for. Routing discriminator on every intent. */
export enum PaymentService {
  PASSENGER = "PASSENGER",
  FREIGHT = "FREIGHT",
}

/** What kind of domain order the intent references (soft reference — never a cross-schema FK). */
export enum PaymentReferenceType {
  BOOKING = "BOOKING",
  SHIPMENT = "SHIPMENT",
}


/** Body of `POST /payments/initiate` on the payment service (internal, service-authenticated). */
export interface InitiatePaymentRequest {
  service: PaymentService;
  referenceType: PaymentReferenceType;
  /** Domain order id (booking/shipment id). Soft reference; the app has already validated it. */
  referenceId: string;
  /** Human-readable order ref (e.g. booking ref) shown on provider pages. Defaults to referenceId. */
  orderRef?: string;
  /** App-asserted authoritative amount in minor units (computed server-side by the domain app). */
  amountMinor: number;
  currency: string;
  provider: ProviderMethod;
  platform?: PaymentPlatform;
  payerAccount?: string;
  /**
   * Where the provider's hosted page sends the BROWSER back after success — each calling app
   * passes its own UI URL (passenger portal vs freight portal). Per-transaction and UX-only:
   * the redirect never confirms payment (only the webhook / status query does), so per-app
   * values are safe even though the server-to-server webhook URL is one per merchant.
   * Falls back to the payment service's provider config when omitted.
   */
  returnUrl?: string;
  /** Failure/cancel counterpart of returnUrl. */
  failureUrl?: string;
  /** Optional caller key to dedupe retried initiations beyond the per-reference upsert. */
  idempotencyKey?: string;
}

/** Body of `POST /payments/intents/:id/confirm` (OTP-based providers such as CAC Bank). */
export interface ConfirmPaymentRequest {
  otp: string;
}

/** Response of `POST /payments/initiate` and shape of intent lookups. */
export type PaymentIntentSnapshot ={
  intentId: string;
  service: PaymentService;
  referenceType: PaymentReferenceType;
  referenceId: string;
  merchantOrderId: string;
  provider: ProviderMethod;
  status: ProviderPaymentStatus;
  amountMinor: number;
  currency: string;
  clientAction?: ClientAction;
  providerTxnId?: string;
  paidAt?: string;
  failureCode?: string;
  failureMessage?: string;
  expiresAt?: string;
}

export type PaymentEventType = "payment.succeeded" | "payment.failed";

/** Versioned envelope delivered (at-least-once) to the owning app's mark-paid consumer. */
interface PaymentEventBase {
  version: 1;
  /** Outbox row id — stable across redeliveries; consumers may use it as a dedupe key. */
  eventId: string;
  eventType: PaymentEventType;
  occurredAt: string;
  service: PaymentService;
  intentId: string;
  referenceType: PaymentReferenceType;
  referenceId: string;
  merchantOrderId: string;
  provider: ProviderMethod;
  amountMinor: number;
  currency: string;
}

export interface PaymentSucceededEvent extends PaymentEventBase {
  eventType: "payment.succeeded";
  providerTxnId?: string;
  paidAt: string;
}

export interface PaymentFailedEvent extends PaymentEventBase {
  eventType: "payment.failed";
  failureCode?: string;
  failureMessage?: string;
}

export type PaymentEvent = PaymentSucceededEvent | PaymentFailedEvent;
