/**
 * applyEffects — interprets RedemptionEffect[] for the redemption decider.
 *
 * PERSISTENCE ORDERING:
 *   1. mark-expired (conditional DB UPDATE on voucher UNREDEEMED rows for the line).
 *   2. enqueue-outbox (DB insert + queue send)
 *   3. send-user-push (best-effort external I/O, never rolls back)
 *
 * Voucher redemption state writes happen via platform redeemVoucher in the workflow.
 */

import { eq, and } from 'drizzle-orm';
import { voucher } from '@platform-modules/commerce-fulfillment';
import type { DrizzleClient } from '@/server/db/client.js';
import { insertOutboxRow } from '@/server/db/queries/outbox.js';
import { enqueueOutbox } from '@/server/queues/outbox-producer.js';
import type { PushClient, PushNotification } from '@/server/push/types.js';
import { captureCaught } from '@/server/observability/capture.server.js';
import type { RedemptionEffect } from './effects.js';

export interface ApplyEffectsContext {
  db: DrizzleClient;
  push: PushClient;
  buildRedemptionPush: (effect: {
    userId: string;
    purchaseId: string;
    dealId: string;
  }) => Promise<PushNotification>;
}

export interface ApplyEffectsResult {
  expired: boolean;
}

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

export async function applyEffects(
  ctx: ApplyEffectsContext,
  effects: RedemptionEffect[],
): Promise<ApplyEffectsResult> {
  const { db, push, buildRedemptionPush } = ctx;
  let expired = false;

  for (const effect of effects) {
    if (effect.kind === 'mark-expired') {
      const rows = await db
        .update(voucher)
        .set({ state: 'EXPIRED' })
        .where(and(eq(voucher.lineId, effect.purchaseId), eq(voucher.state, 'UNREDEEMED')))
        .returning();
      expired = rows.length > 0;
    }
  }

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

  for (const effect of effects) {
    if (effect.kind === 'send-user-push') {
      try {
        const notification = await buildRedemptionPush({
          userId: effect.userId,
          purchaseId: effect.purchaseId,
          dealId: effect.dealId,
        });
        await push.sendToUser(effect.userId, notification);
      } catch (err) {
        captureCaught(err, {
          scope: 'server.domain.redemption.apply-effects',
          severity: 'warning',
        });
      }
    }
  }

  for (const effect of effects) {
    switch (effect.kind) {
      case 'mark-expired':
      case 'enqueue-outbox':
      case 'send-user-push':
        break;
      default:
        assertNever(effect);
    }
  }

  return { expired };
}
