/**
 * applyEffects — interprets RefundEffect[] for the refund decider.
 *
 * PERSISTENCE ORDERING:
 *   1. set-refunded (order status recompute + voucher CANCELLED on full refund)
 *   2. enqueue-outbox (DB insert + queue send)
 *
 * This is the ONLY place in refund flows that calls:
 *   - order status recompute + platform voucher UPDATE for refund state
 *   - insertOutboxRow / enqueueOutbox
 *
 * The Stripe refundPurchase() call stays in the workflow orchestrator (it is
 * non-idempotent external I/O and must run BEFORE the decider).
 */

import { eq, and } from 'drizzle-orm';
import { voucher } from '@platform-modules/commerce-fulfillment';
import type { DrizzleClient } from '@/server/db/client.js';
import { orderLine } from '@/server/db/schema.js';
import { recomputeOrderRefundStatus } from '@/server/db/queries/purchases.js';
import { insertOutboxRowOnce } from '@/server/db/queries/outbox.js';
import { enqueueOutbox } from '@/server/queues/outbox-producer.js';
import { buildClawbackPayload } from '@/server/referrals/build-clawback-payload.js';
import type { RefundEffect } from './effects.js';

export interface ApplyEffectsContext {
  db: DrizzleClient;
}

export interface ApplyEffectsResult {
  outboxId: string | null;
}

function assertNever(x: never): never {
  throw new Error(`Unhandled RefundEffect kind: ${JSON.stringify(x)}`);
}

export async function applyEffects(
  ctx: ApplyEffectsContext,
  effects: RefundEffect[],
): Promise<ApplyEffectsResult> {
  const { db } = ctx;
  let outboxId: string | null = null;

  for (const effect of effects) {
    if (effect.kind === 'set-refunded') {
      // Order status (refunded / partially_refunded) is recomputed from executed
      // refund intents — providers settle via setPurchaseRefund; this re-assert
      // covers providers that could not settle inline.
      const [lineRow] = await db
        .select({ orderId: orderLine.orderId })
        .from(orderLine)
        .where(eq(orderLine.id, effect.purchaseId))
        .limit(1);
      if (lineRow) {
        await recomputeOrderRefundStatus(db, lineRow.orderId);
      }
      // Cancel the redemption slot only when the full line amount was refunded.
      if (effect.cancelVoucher) {
        await db
          .update(voucher)
          .set({ state: 'CANCELLED' })
          .where(and(eq(voucher.lineId, effect.purchaseId), eq(voucher.state, 'UNREDEEMED')));
      }
    }
  }

  for (const effect of effects) {
    if (effect.kind === 'enqueue-outbox') {
      const { id, inserted } = await insertOutboxRowOnce(db, {
        aggregateType: effect.aggregateType,
        aggregateId: effect.aggregateId,
        eventType: effect.eventType,
        dedupeKey: effect.dedupeKey,
        payload: effect.payload,
      });
      outboxId = id;
      if (inserted) await enqueueOutbox(id);
    }
  }

  for (const effect of effects) {
    if (effect.kind === 'referral-clawback') {
      const { id, inserted } = await insertOutboxRowOnce(db, {
        aggregateType: 'referral',
        aggregateId: effect.purchaseId,
        eventType: 'referral.clawback',
        dedupeKey: `referral.clawback:${effect.refundEventId}`,
        payload: buildClawbackPayload({
          sourceType: 'refund',
          purchaseId: effect.purchaseId,
          refundEventId: effect.refundEventId,
          refundFraction: effect.refundFraction,
          refundAmountAgorot: effect.refundAmountAgorot,
        }),
      });
      if (inserted) await enqueueOutbox(id);
    }
  }

  for (const effect of effects) {
    switch (effect.kind) {
      case 'set-refunded':
      case 'enqueue-outbox':
      case 'referral-clawback':
        break;
      default:
        assertNever(effect);
    }
  }

  return { outboxId };
}
