import { createDbService } from '@/server/services/db.js';
import type Stripe from 'stripe';
import { randomUUID } from 'node:crypto';
import type { MultidealEnv } from '../env.js';
import { checkAndMarkSoldOut } from '@/server/workflows/purchase.js';
import { writeReleasesOnChargeSucceeded } from '@/server/workflows/settlement-release-writer.js';
import type { DealType } from '@/lib/deal-types';
import { uploadPurchaseQr } from '@/server/storage/qr_png.js';
import * as purchaseQueries from '@/server/db/queries/purchases.js';
import { upsertOrderPaymentFee } from '@/server/db/queries/order-payment-fees.js';
import { getInvoiceProvider } from '@/server/invoicing/get-provider.js';
import type { IssueTaxDocInput, IssueTaxDocOutcome } from '@/server/invoicing/provider.js';
import type { DrizzleDb, TxDrizzleClient } from '@/server/db/client.js';
import {
  markPaid,
  claimForCharge,
  getOrderById,
  type OrdersSchema,
} from '@platform-modules/commerce-orders';
import type { Transaction } from '@platform-modules/db';
import { getCurrentVatRate } from '@/server/db/queries/vat.js';
import { getTaxDoc, upsertTaxDoc } from '@/server/db/queries/tax-documents.js';
import { captureCaught } from '@/server/observability/capture.server';
import { PaymentErrorCode } from './provider.js';
import type { ChargeOk, PaymentFailure } from './provider.js';
import { finalizePromoRedemption } from '@/server/promo/service.js';
import { and, eq, sql } from 'drizzle-orm';
import {
  order,
  orderLine,
  paymentFinalizeCheckpoints,
  paymentExpectedContracts,
  orderLineVoucherExt,
  users,
  vendors,
  dealSkus,
  deals,
} from '@/server/db/schema.js';
import { fulfillOrder, voucher } from '@platform-modules/commerce-fulfillment';
import type { Querier } from '@platform-modules/db';
import {
  generateVoucherQrToken,
  makeFulfillmentPorts,
} from '@/server/fulfillment/fulfillment-platform.js';
import { loadVouchersForLine } from '@/server/fulfillment/voucher-line-state.js';
import { consumePendingGuestAccessToken } from '@/server/db/queries/guest-access-token.js';
import {
  asPaymentsFinalizeCtxId,
  decide as decidePaymentsFinalize,
} from '@/server/domain/payments-finalize/machine.js';
import type { PaymentsFinalizeEffect } from '@/server/domain/payments-finalize/effects.js';
import { getStripe } from '@/server/payments/stripe/client.js';
import {
  asDealId,
  asOrderId,
  asOrderLineId,
  asUserId,
  asVendorId,
  toModuleRef,
} from '@/server/platform-seams/ids.js';
import { dispatchReferralEarnForPurchase } from '@/server/referrals/outbox.js';
import { consumeInventoryReservationsForHolders } from '@/server/stock/reserveStock.js';
import {
  completePaymentFinalizeCheckpoint,
  failPaymentFinalizeCheckpoint,
  fencePaymentFinalizeCheckpoint,
  heartbeatPaymentFinalizeCheckpoint,
  insertExpectedPaymentContract,
} from '@/server/db/queries/payment-finalize.js';
import { expireVouchersForLine, upsertVoucherQr } from '@/server/db/queries/vouchers.js';

// ─── Types for composable steps ────────────────────────────────────────────

type DbClient = DrizzleDb;
// T7: PurchaseRow is the full orderLine-based shape from findById (not the stub getPurchaseByProviderPaymentId).
type PurchaseRow = NonNullable<Awaited<ReturnType<typeof purchaseQueries.findById>>>;
type InvoiceResults = {
  vendorDoc: Awaited<ReturnType<ReturnType<typeof getInvoiceProvider>['issueTaxDoc']>>;
  platformDoc: Awaited<ReturnType<ReturnType<typeof getInvoiceProvider>['issueTaxDoc']>>;
};

export async function ensureOrderFulfilled(
  db: DbClient,
  fulfillmentPorts: Parameters<typeof fulfillOrder>[0],
  platformOrder: Parameters<typeof fulfillOrder>[1],
  lineId: string,
) {
  const lines = platformOrder.lines.filter((candidate) => candidate.kind === 'voucher');
  const anchor = lines.find((candidate) => candidate.id === lineId);
  if (!anchor) throw new Error(`Voucher line not found for purchase ${lineId}`);
  const vouchersByLine = new Map<string, Awaited<ReturnType<typeof loadVouchersForLine>>>();
  for (const line of lines) vouchersByLine.set(line.id, await loadVouchersForLine(db, line.id));
  if (lines.some((line) => (vouchersByLine.get(line.id)?.length ?? 0) < line.qty)) {
    const fulfillResult = await fulfillOrder(fulfillmentPorts, platformOrder);
    if (fulfillResult.overall !== 'fulfilled') {
      throw new Error(`Fulfillment failed for purchase ${lineId}: ${fulfillResult.overall}`);
    }
    for (const line of lines) vouchersByLine.set(line.id, await loadVouchersForLine(db, line.id));
  }
  for (const line of lines) {
    const vouchers = vouchersByLine.get(line.id) ?? [];
    if (vouchers.length < line.qty) {
      await (db as unknown as DrizzleDb).transaction(async (tx) => {
        const locked = await tx
          .select({ id: orderLine.id })
          .from(orderLine)
          .where(eq(orderLine.id, line.id))
          .for('update');
        if (locked.length !== 1) throw new Error(`Voucher line not found for issuance ${line.id}`);
        const existing = await tx
          .select({ unitIndex: voucher.unitIndex })
          .from(voucher)
          .where(eq(voucher.lineId, line.id))
          .for('update');
        const existingIndexes = new Set(existing.map((row) => row.unitIndex));
        const missingIndexes = Array.from({ length: line.qty }, (_, unitIndex) => unitIndex).filter(
          (unitIndex) => !existingIndexes.has(unitIndex),
        );
        if (missingIndexes.length > 0) {
          await tx
            .insert(voucher)
            .values(
              missingIndexes.map((unitIndex) => ({
                id: randomUUID(),
                orderId: platformOrder.id,
                lineId: line.id,
                unitIndex,
                vendorId: line.vendorId,
                state: 'UNREDEEMED' as const,
                expiresAt: null,
              })),
            )
            .onConflictDoNothing();
        }
      });
      vouchersByLine.set(line.id, await loadVouchersForLine(db, line.id));
    }
    const count = vouchersByLine.get(line.id)?.length ?? 0;
    if (count !== line.qty)
      throw new Error(`Fulfillment produced ${count}/${line.qty} vouchers for purchase ${line.id}`);
  }
  const result = [...(vouchersByLine.get(lineId) ?? [])] as Awaited<
    ReturnType<typeof loadVouchersForLine>
  > & {
    anchorVouchers: Awaited<ReturnType<typeof loadVouchersForLine>>;
    lines: Array<{
      lineId: string;
      required: number;
      count: number;
      vouchers: Awaited<ReturnType<typeof loadVouchersForLine>>;
    }>;
  };
  Object.defineProperty(result, 'anchorVouchers', { value: result });
  Object.defineProperty(result, 'lines', {
    value: lines.map((line) => ({
      lineId: line.id,
      required: line.qty,
      count: vouchersByLine.get(line.id)?.length ?? 0,
      vouchers: vouchersByLine.get(line.id) ?? [],
    })),
  });
  return result;
}

class PaymentFinalizeFailure extends Error {
  constructor(readonly failure: PaymentFailure) {
    super(failure.message);
    this.name = 'PaymentFinalizeFailure';
  }
}

export async function runCheckpoint<T>(
  db: DbClient,
  purchaseId: string,
  orderId: string,
  providerPaymentId: string,
  effectKey: string,
  effect: () => Promise<T>,
): Promise<T | undefined> {
  const leaseOwner = randomUUID();
  const leaseMs = 60_000;
  const claimed = await (db as unknown as TxDrizzleClient).transaction(async (tx) => {
    const [row] = await tx
      .insert(paymentFinalizeCheckpoints)
      .values({
        purchaseId,
        orderId,
        providerPaymentId,
        effectKey,
        status: 'running',
        attempt: 1,
        leaseOwner,
        leaseExpiresAt: new Date(Date.now() + leaseMs),
      })
      .onConflictDoNothing({
        target: [paymentFinalizeCheckpoints.purchaseId, paymentFinalizeCheckpoints.effectKey],
      })
      .returning({ id: paymentFinalizeCheckpoints.id });
    if (row) return { run: true as const };
    const [existing] = await tx
      .select({
        status: paymentFinalizeCheckpoints.status,
        leaseExpiresAt: paymentFinalizeCheckpoints.leaseExpiresAt,
        result: paymentFinalizeCheckpoints.result,
        providerPaymentId: paymentFinalizeCheckpoints.providerPaymentId,
      })
      .from(paymentFinalizeCheckpoints)
      .where(
        and(
          eq(paymentFinalizeCheckpoints.purchaseId, purchaseId),
          eq(paymentFinalizeCheckpoints.effectKey, effectKey),
        ),
      )
      .for('update');
    if (!existing) return { run: false as const };
    if (existing.providerPaymentId !== providerPaymentId) {
      throw new Error(`Payment finalization ownership mismatch: ${purchaseId}:${effectKey}`);
    }
    if (existing.status === 'completed')
      return { run: false as const, result: existing.result as T };
    if (existing.status === 'running') {
      if (existing.leaseExpiresAt && existing.leaseExpiresAt > new Date()) {
        throw new Error(`Payment finalization already running: ${purchaseId}:${effectKey}`);
      }
      throw new Error(
        `Payment finalization outcome ambiguous; reconcile before retry: ${purchaseId}:${effectKey}`,
      );
    }
    const takeover = await tx
      .update(paymentFinalizeCheckpoints)
      .set({
        status: 'running',
        attempt: sql`${paymentFinalizeCheckpoints.attempt} + 1`,
        leaseOwner,
        leaseExpiresAt: new Date(Date.now() + leaseMs),
        updatedAt: new Date(),
      })
      .where(
        and(
          eq(paymentFinalizeCheckpoints.purchaseId, purchaseId),
          eq(paymentFinalizeCheckpoints.orderId, orderId),
          eq(paymentFinalizeCheckpoints.effectKey, effectKey),
          eq(paymentFinalizeCheckpoints.providerPaymentId, providerPaymentId),
          sql`${paymentFinalizeCheckpoints.status} <> 'running'`,
        ),
      )
      .returning({ id: paymentFinalizeCheckpoints.id });
    if (takeover.length !== 1) return { run: false as const };
    return { run: true as const };
  });
  if (!claimed.run) return claimed.result;
  const fenced = await fencePaymentFinalizeCheckpoint(db, {
    purchaseId,
    orderId,
    providerPaymentId,
    effectKey,
    leaseOwner,
  });
  if (!fenced)
    throw new Error(`Payment finalization lease lost before effect: ${purchaseId}:${effectKey}`);
  let leaseLost = false;
  const heartbeat = setInterval(() => {
    void heartbeatPaymentFinalizeCheckpoint(db, {
      purchaseId,
      orderId,
      providerPaymentId,
      effectKey,
      leaseOwner,
      leaseExpiresAt: new Date(Date.now() + leaseMs),
    })
      .then((alive) => {
        if (!alive) leaseLost = true;
      })
      .catch((error) => {
        leaseLost = true;
        captureCaught(error as Error, {
          scope: 'payments.finalize.heartbeat',
          severity: 'error',
        });
      });
  }, 20_000);
  try {
    const result = await effect();
    if (leaseLost) throw new Error(`Payment finalization lease lost: ${purchaseId}:${effectKey}`);
    if (result && typeof result === 'object' && 'ok' in result && result.ok === false) {
      throw new PaymentFinalizeFailure(result as unknown as PaymentFailure);
    }
    const completed = await completePaymentFinalizeCheckpoint(db, {
      purchaseId,
      orderId,
      providerPaymentId,
      effectKey,
      leaseOwner,
      result,
    });
    if (!completed) throw new Error(`Payment finalization lease lost: ${purchaseId}:${effectKey}`);
    return result;
  } catch (error) {
    const failed = await failPaymentFinalizeCheckpoint(db, {
      purchaseId,
      orderId,
      providerPaymentId,
      effectKey,
      leaseOwner,
    });
    if (!failed)
      captureCaught(
        new Error(`Payment finalization failure lease lost: ${purchaseId}:${effectKey}`),
        { scope: 'payments.finalize.failure', severity: 'error' },
      );
    if (!failed)
      throw new Error(`Payment finalization lease lost during effect: ${purchaseId}:${effectKey}`, {
        cause: error,
      });
    throw error;
  } finally {
    clearInterval(heartbeat);
  }
}

export function hasAuthoritativeLegacyPaymentBinding(input: {
  purchaseId: string;
  orderId: string;
  metadata?: { purchaseId?: string; orderId?: string } | null;
  providerPaymentId: string;
}): boolean {
  return (
    input.metadata?.purchaseId === input.purchaseId && input.metadata.orderId === input.orderId
  );
}

export function isLegacyPaymentDestinationBound(input: {
  destinationAccountId: string | null;
  vendorStripeAccountId: string | null;
}): boolean {
  return (
    input.destinationAccountId !== null &&
    input.vendorStripeAccountId !== null &&
    input.destinationAccountId === input.vendorStripeAccountId
  );
}

type PaymentExpectedContractRow = typeof paymentExpectedContracts.$inferSelect;

export function paymentIntentMatchesExpectedContract(input: {
  expected: PaymentExpectedContractRow;
  orderId: string;
  piAmount: number;
  piCurrency: string;
  piCustomer: string | null;
  destination: string | null;
  applicationFeeAgorot: number;
  isMockPaymentIntent: boolean;
}): boolean {
  const {
    expected,
    orderId,
    piAmount,
    piCurrency,
    piCustomer,
    destination,
    applicationFeeAgorot,
    isMockPaymentIntent,
  } = input;
  if (
    expected.orderId !== orderId ||
    expected.amountAgorot !== piAmount ||
    expected.currency !== piCurrency ||
    expected.applicationFeeAgorot !== applicationFeeAgorot
  ) {
    return false;
  }
  if (isMockPaymentIntent) {
    // Mock charge() synthesizes a PI without Stripe Connect fields; trust the
    // persisted checkout contract for customer/destination when PI omits them.
    if (destination !== null && expected.destinationAccountId !== destination) return false;
    if (piCustomer !== null && expected.customerId !== piCustomer) return false;
    return true;
  }
  return expected.customerId === piCustomer && expected.destinationAccountId === destination;
}

async function claimPaidOrder(
  db: DbClient,
  purchaseId: string,
  orderId: string,
  providerPaymentId: string,
): Promise<PaymentFailure | null> {
  try {
    await (db as unknown as TxDrizzleClient).transaction(async (tx) => {
      const [existing] = await tx
        .select({ chargeRef: order.chargeRef, status: order.status })
        .from(order)
        .where(eq(order.id, orderId))
        .for('update');
      if (!existing) throw new Error(`Order ${orderId} not found`);
      if (existing.chargeRef === providerPaymentId) return;
      // Checkout intent already transitions pending → charging via claimForCharge.
      // Finalize must only markPaid from charging; re-claiming throws OrderNotChargeableError.
      if (existing.status === 'pending') {
        await claimForCharge(tx as unknown as Transaction<OrdersSchema>, orderId);
      } else if (existing.status !== 'charging') {
        throw new Error(`order not chargeable: ${orderId}`);
      }
      await markPaid(tx as unknown as Transaction<OrdersSchema>, orderId, providerPaymentId);
      const [claim] = await tx
        .insert(paymentFinalizeCheckpoints)
        .values({
          purchaseId,
          orderId,
          providerPaymentId,
          effectKey: 'payment-claim',
          status: 'completed',
          completedAt: new Date(),
        })
        .onConflictDoNothing({
          target: paymentFinalizeCheckpoints.providerPaymentId,
          where: sql`${paymentFinalizeCheckpoints.effectKey} = 'payment-claim'`,
        })
        .returning({
          purchaseId: paymentFinalizeCheckpoints.purchaseId,
          orderId: paymentFinalizeCheckpoints.orderId,
          providerPaymentId: paymentFinalizeCheckpoints.providerPaymentId,
        });
      if (!claim) {
        const [owner] = await tx
          .select({
            purchaseId: paymentFinalizeCheckpoints.purchaseId,
            orderId: paymentFinalizeCheckpoints.orderId,
            providerPaymentId: paymentFinalizeCheckpoints.providerPaymentId,
          })
          .from(paymentFinalizeCheckpoints)
          .where(
            and(
              eq(paymentFinalizeCheckpoints.providerPaymentId, providerPaymentId),
              eq(paymentFinalizeCheckpoints.effectKey, 'payment-claim'),
            ),
          )
          .limit(1);
        if (!owner || owner.purchaseId !== purchaseId || owner.orderId !== orderId) {
          throw new Error('payment claim ownership mismatch');
        }
      }
    });
    return null;
  } catch (error) {
    captureCaught(error as Error, {
      scope: 'payments.finalize.payment-claim',
      severity: 'error',
    });
    return {
      ok: false,
      code: PaymentErrorCode.PROVIDER_ERROR,
      message: `Failed to claim payment ${providerPaymentId}`,
    };
  }
}

// ─── Step 1: Issue vendor + platform tax documents ──────────────────────────
// Failures are non-fatal — logged at error severity; caller decides how to handle.

export async function issueInvoices(
  env: MultidealEnv,
  db: DbClient,
  purchaseId: string,
  pi: Stripe.PaymentIntent,
  vendorAgorot: number,
  platformAgorot: number,
  buyerName: string,
  vendorId: string,
  vendorAccountId: string,
): Promise<InvoiceResults> {
  const invoiceProvider = getInvoiceProvider(env);
  const vatPct = await getCurrentVatRate(db);

  const dealTitle = (pi.metadata?.dealTitle ?? 'Deal') as string;
  const authoritativeVendorId = toModuleRef(asVendorId(vendorId));

  async function issueForSide(
    side: 'vendor' | 'platform',
    amountAgorot: number,
    lineItems: IssueTaxDocInput['lineItems'],
  ): Promise<IssueTaxDocOutcome> {
    const existing = await getTaxDoc(db, purchaseId, side);
    if (existing) {
      const pdfUrl = await invoiceProvider.getTaxDocPdfUrl(existing.providerTaxDocId);
      return {
        ok: true,
        taxDocId: existing.providerTaxDocId,
        taxDocNumber: existing.providerTaxDocId,
        pdfUrl: pdfUrl ?? '',
      };
    }

    const idempotencyKey = `taxdoc:${purchaseId}:${side}`;
    const expectedInput: IssueTaxDocInput = {
      side,
      amountAgorot,
      vatPct,
      buyer: { name: buyerName },
      vendor: { vendorId: authoritativeVendorId, providerAccountId: vendorAccountId },
      lineItems,
      providerPaymentId: pi.id,
      docType: 'invoice_receipt',
      idempotencyKey,
    };
    const recovered = await invoiceProvider.findTaxDocByIdempotencyKey(idempotencyKey);
    if (recovered) {
      const bindingMatches =
        recovered.side === expectedInput.side &&
        recovered.amountAgorot === expectedInput.amountAgorot &&
        recovered.vatPct === expectedInput.vatPct &&
        recovered.vendorId === expectedInput.vendor.vendorId &&
        recovered.vendorAccountId === expectedInput.vendor.providerAccountId &&
        recovered.providerPaymentId === expectedInput.providerPaymentId &&
        recovered.docType === expectedInput.docType &&
        recovered.lineItems.length === expectedInput.lineItems.length &&
        recovered.lineItems.every((item, index) => {
          const expected = expectedInput.lineItems[index];
          return (
            expected !== undefined &&
            item.description === expected.description &&
            item.quantity === expected.quantity &&
            item.unitPriceAgorot === expected.unitPriceAgorot
          );
        });
      if (!bindingMatches) {
        throw new Error(`Recovered tax document binding mismatch: ${purchaseId}:${side}`);
      }
      await upsertTaxDoc(db, {
        orderLineId: toModuleRef(asOrderLineId(purchaseId)),
        side,
        providerTaxDocId: recovered.taxDocId,
        idempotencyKey,
      });
      const pdfUrl = await invoiceProvider.getTaxDocPdfUrl(recovered.taxDocId);
      return {
        ok: true,
        taxDocId: recovered.taxDocId,
        taxDocNumber: recovered.taxDocNumber,
        pdfUrl: pdfUrl ?? '',
      };
    }
    const result = await invoiceProvider.issueTaxDoc(expectedInput);

    if (result.ok) {
      await upsertTaxDoc(db, {
        orderLineId: toModuleRef(asOrderLineId(purchaseId)), // T7: purchaseId IS the orderLine.id
        side,
        providerTaxDocId: result.taxDocId,
        idempotencyKey,
      });
    }

    return result;
  }

  const vendorDoc = await issueForSide('vendor', vendorAgorot, [
    { description: dealTitle, quantity: 1, unitPriceAgorot: vendorAgorot },
  ]);

  const platformDoc = await issueForSide('platform', platformAgorot, [
    {
      description: `Platform fee: ${dealTitle}`,
      quantity: 1,
      unitPriceAgorot: platformAgorot,
    },
  ]);

  if (!vendorDoc.ok) {
    captureCaught(new Error('invoice_vendor_failed'), {
      scope: 'payments.finalize.issueInvoices',
      severity: 'error',
      extra: { code: vendorDoc.code },
    });
  }
  if (!platformDoc.ok) {
    captureCaught(new Error('invoice_platform_failed'), {
      scope: 'payments.finalize.issueInvoices',
      severity: 'error',
      extra: { code: platformDoc.code },
    });
  }
  if (!vendorDoc.ok || !platformDoc.ok) {
    throw new Error('invoice issuance incomplete');
  }

  return { vendorDoc, platformDoc };
}

// ─── Build ChargeOk result ──────────────────────────────────────────────────

function buildChargeOk(
  pi: Stripe.PaymentIntent,
  vendorAgorot: number,
  platformAgorot: number,
  invoices?: InvoiceResults,
): ChargeOk {
  return {
    ok: true,
    status: 'succeeded',
    providerPaymentId: pi.id,
    vendorAgorot,
    platformAgorot,
    vendorTaxDocId: invoices?.vendorDoc.ok ? invoices.vendorDoc.taxDocId : null,
    platformTaxDocId: invoices?.platformDoc.ok ? invoices.platformDoc.taxDocId : null,
    vendorTaxDocPdfUrl: invoices?.vendorDoc.ok ? invoices.vendorDoc.pdfUrl : null,
    platformTaxDocPdfUrl: invoices?.platformDoc.ok ? invoices.platformDoc.pdfUrl : null,
  };
}

// ─── Stripe processing fee (balance_transaction.fee, agorot) ────────────────

async function resolveStripeProcessingFeeAgorot(
  env: MultidealEnv,
  pi: Stripe.PaymentIntent,
): Promise<number | null> {
  try {
    const stripe = getStripe(env);
    const latest = pi.latest_charge;
    const chargeId =
      typeof latest === 'string' ? latest : latest && typeof latest === 'object' ? latest.id : null;
    if (!chargeId) return null;

    let balanceTransaction: Stripe.BalanceTransaction | string | null | undefined;
    if (typeof latest === 'object' && latest?.balance_transaction != null) {
      balanceTransaction = latest.balance_transaction;
    } else {
      const charge = await stripe.charges.retrieve(chargeId, {
        expand: ['balance_transaction'],
      });
      balanceTransaction = charge.balance_transaction;
    }

    if (balanceTransaction == null) return null;
    if (typeof balanceTransaction === 'string') {
      const bt = await stripe.balanceTransactions.retrieve(balanceTransaction);
      return bt.fee ?? null;
    }
    return balanceTransaction.fee ?? null;
  } catch (e) {
    captureCaught(e, {
      scope: 'payments.finalize.stripeFee',
      severity: 'warning',
      extra: { paymentIntentId: pi.id },
    });
    return null;
  }
}

async function persistStripeFeeAgorot(
  db: DbClient,
  orderId: string | undefined,
  stripeFeeAgorot: number | null,
  providerPaymentId: string,
): Promise<void> {
  if (!orderId) return;
  await upsertOrderPaymentFee(db as Parameters<typeof upsertOrderPaymentFee>[0], {
    orderId,
    stripeFeeAgorot,
    providerPaymentId,
  });
}

async function applyFinalizeEffects(
  env: MultidealEnv,
  db: DbClient,
  purchase: PurchaseRow,
  effects: PaymentsFinalizeEffect[],
): Promise<PaymentFailure | null> {
  let outboxId: string | null = null;
  let skipPostConfirmEffects = false;

  for (const effect of effects) {
    switch (effect.kind) {
      case 'consume-inventory-reservations': {
        await consumeInventoryReservationsForHolders(env, effect.holderRefs);
        break;
      }
      case 'confirm-purchase': {
        const confirmResult = await purchaseQueries.confirmCheckout(db, {
          purchaseId: toModuleRef(effect.purchaseId),
          userId: effect.userId ? toModuleRef(effect.userId) : undefined,
          dealId: effect.dealId ? toModuleRef(effect.dealId) : undefined,
        });
        if ('alreadyCompleted' in confirmResult) {
          skipPostConfirmEffects = true;
          break;
        }
        outboxId = confirmResult.outboxId ?? null;
        break;
      }
      case 'check-sold-out': {
        if (skipPostConfirmEffects) break;
        try {
          await checkAndMarkSoldOut(db, toModuleRef(effect.dealId));
        } catch (e) {
          captureCaught(e, {
            scope: 'payments.finalize.soldOutCheck',
            severity: 'warning',
            extra: { purchaseId: purchase.id },
          });
        }
        break;
      }
      case 'dispatch-outbox': {
        if (skipPostConfirmEffects) break;
        try {
          if (env.OUTBOX_QUEUE && outboxId) await env.OUTBOX_QUEUE.send({ outboxId });
        } catch (e) {
          captureCaught(e, { scope: 'payments.finalize', severity: 'warning' });
        }
        break;
      }
      case 'persist-stripe-fee': {
        await persistStripeFeeAgorot(
          db,
          toModuleRef(effect.orderId),
          effect.stripeFeeAgorot,
          effect.providerPaymentId,
        );
        break;
      }
      case 'mark-order-paid': {
        const orderId = toModuleRef(effect.orderId);
        const failure = await claimPaidOrder(db, purchase.id, orderId, effect.providerPaymentId);
        if (failure) return failure;
        break;
      }
    }
  }

  return null;
}

// ─── Step 3: Write promo redemption ledger ───────────────────────────────────
// Idempotent: ON CONFLICT DO NOTHING inside finalizePromoRedemption.
// Guest checkout (userId null) cannot hold a promo code — skip safely.
// Promo economics come from Stripe PI metadata (set server-side at charge time);
// identity is bound to the persisted purchase row inside finalizePromoRedemption.

async function redeemPromo(
  db: DbClient,
  purchase: PurchaseRow,
  pi: Stripe.PaymentIntent,
): Promise<void> {
  if (!purchase.userId) return;

  const reservationId = pi.metadata?.promoReservationId;
  if (!reservationId || typeof reservationId !== 'string') return;

  try {
    const result = await finalizePromoRedemption(db, {
      purchaseId: purchase.id,
      userId: purchase.userId,
      vendorId: purchase.vendorId,
      reservationId,
    });
    if (!result.ok) throw new Error(`promo finalization rejected: ${result.reason}`);
  } catch (e) {
    captureCaught(e, { scope: 'payments.finalize.promo', severity: 'error' });
    throw e;
  }
}

async function writeMockSettlementReleasesOnFinalize(
  db: DrizzleDb,
  purchaseId: string,
  pi: Stripe.PaymentIntent,
  vendorAgorot: number,
  platformAgorot: number,
): Promise<void> {
  const [row] = await db
    .select({
      vendorId: orderLine.vendorId,
      dealType: deals.dealType,
      stripeAccountId: vendors.stripeAccountId,
    })
    .from(orderLine)
    .innerJoin(dealSkus, eq(dealSkus.id, orderLine.variantId))
    .innerJoin(deals, eq(deals.id, dealSkus.dealId))
    .leftJoin(vendors, eq(sql`${vendors.id}::text`, orderLine.vendorId))
    .where(eq(orderLine.id, purchaseId))
    .limit(1);
  if (!row?.vendorId || !row.stripeAccountId) return;

  await writeReleasesOnChargeSucceeded({
    db,
    paymentIntentId: pi.id,
    chargeId: `mock_ch_${purchaseId}`,
    transferId: `mock_transfer_${purchaseId}`,
    purchaseIds: [purchaseId],
    vendorId: row.vendorId,
    vendorAcctId: row.stripeAccountId,
    netAmountAgorot: vendorAgorot,
    applicationFeeAgorot: platformAgorot,
    dealType: row.dealType as DealType,
  });
}

/**
 * Finalize a successful PaymentIntent into a COMPLETED purchase.
 * - Idempotent: if purchase already COMPLETED, returns the existing ChargeOk.
 * - Generates QR + uploads PNG.
 * - Calls InvoiceProvider for vendor + platform docs (VAT rate from DB schedule).
 * - Persists confirmation + dispatches outbox message.
 * Called from: webhook payment_intent.succeeded, /api/checkout/finalize (redirect poll fallback), StripeReconcileDO.
 */
export async function finalizePurchase(
  env: MultidealEnv,
  pi: Stripe.PaymentIntent,
): Promise<ChargeOk | PaymentFailure> {
  return finalizePurchaseInternal(env, pi, false);
}

export async function finalizeMockPurchase(
  env: MultidealEnv,
  pi: Stripe.PaymentIntent,
): Promise<ChargeOk | PaymentFailure> {
  if (env.PAYMENT_PROVIDER !== 'mock' || !/^mock_pay_[0-9a-f-]{36}$/i.test(pi.id)) {
    return {
      ok: false,
      code: PaymentErrorCode.PROVIDER_ERROR,
      message: 'Invalid mock PaymentIntent contract',
    };
  }
  return finalizePurchaseInternal(env, pi, true);
}

async function finalizePurchaseInternal(
  env: MultidealEnv,
  pi: Stripe.PaymentIntent,
  isMockPaymentIntent: boolean,
): Promise<ChargeOk | PaymentFailure> {
  if (!isMockPaymentIntent) {
    try {
      pi = await getStripe(env).paymentIntents.retrieve(pi.id);
    } catch (error: unknown) {
      captureCaught(new Error('Stripe PaymentIntent retrieval failed'), {
        scope: 'payments.finalize.payment-intent-retrieval',
        severity: 'error',
        extra: { paymentIntentId: pi.id, errorType: typeof error },
      });
      return {
        ok: false,
        code: PaymentErrorCode.PROVIDER_ERROR,
        message: 'PaymentIntent verification failed',
      };
    }
  }
  if (
    (!isMockPaymentIntent && !/^pi_[A-Za-z0-9]+$/.test(pi.id)) ||
    pi.currency !== 'ils' ||
    !Number.isInteger(pi.amount) ||
    pi.amount <= 0
  ) {
    return {
      ok: false,
      code: PaymentErrorCode.PROVIDER_ERROR,
      message: 'Invalid PaymentIntent contract',
    };
  }
  const applicationFeeAgorot = pi.application_fee_amount ?? 0;
  if (
    !Number.isInteger(applicationFeeAgorot) ||
    applicationFeeAgorot < 0 ||
    applicationFeeAgorot > pi.amount
  ) {
    return {
      ok: false,
      code: PaymentErrorCode.PROVIDER_ERROR,
      message: 'Invalid PaymentIntent application fee',
    };
  }
  if (pi.status !== 'succeeded') {
    return {
      ok: false,
      code: PaymentErrorCode.PROVIDER_ERROR,
      message: `PI not succeeded: ${pi.status}`,
    };
  }

  const db = createDbService({ DATABASE_URL: env.DATABASE_URL });
  // PI metadata.purchaseId = orderLineId (set at charge time in stripe-provider.ts).
  // chargeRef on the order is only set by markPaid — using it as primary lookup
  // creates a circular dependency (markPaid can't run if we can't find the order).
  // Provider payment ownership must be carried by the durable PI contract.
  const purchaseIdFromMeta = pi.metadata?.purchaseId as string | undefined;
  const piPurchaseRef = purchaseIdFromMeta ? { id: purchaseIdFromMeta } : null;
  if (!piPurchaseRef) {
    return {
      ok: false,
      code: PaymentErrorCode.PROVIDER_ERROR,
      message: `Purchase not found for ${pi.id}`,
    };
  }
  const purchase = await purchaseQueries.findById(db, piPurchaseRef.id);
  if (!purchase) {
    return {
      ok: false,
      code: PaymentErrorCode.PROVIDER_ERROR,
      message: `Purchase row missing for id ${piPurchaseRef.id}`,
    };
  }

  // Resolve orderId via the orderLine join — the ONLY authoritative source.
  // pi.metadata.reservationId is NOT usable: on the group-deal path it holds a
  // stock-reservation id, not an orderId, so trusting it stamps paid/fees onto
  // a non-existent (or wrong) order.
  const [orderRow] = await db
    .select({
      id: order.id,
      total: order.total,
      buyerUserId: order.buyerUserId,
      buyerDisplayName: users.displayName,
      vendorStripeAccountId: vendors.stripeAccountId,
      chargeRef: order.chargeRef,
    })
    .from(orderLine)
    .innerJoin(order, eq(order.id, orderLine.orderId))
    .leftJoin(users, eq(users.id, order.buyerUserId))
    .leftJoin(vendors, eq(vendors.id, purchase.vendorId))
    .where(eq(orderLine.id, purchase.id))
    .limit(1);
  if (!orderRow) {
    return {
      ok: false,
      code: PaymentErrorCode.PROVIDER_ERROR,
      message: 'PaymentIntent amount binding mismatch',
    };
  }
  const destination =
    typeof pi.transfer_data?.destination === 'string' ? pi.transfer_data.destination : null;
  const piCustomer = (typeof pi.customer === 'string' ? pi.customer : pi.customer?.id) ?? null;
  const [contract] = await db
    .select()
    .from(paymentExpectedContracts)
    .where(eq(paymentExpectedContracts.purchaseId, purchase.id))
    .limit(1);
  let expected = contract;
  if (!expected && isMockPaymentIntent) {
    if (!orderRow.vendorStripeAccountId) {
      return {
        ok: false,
        code: PaymentErrorCode.PAYMENT_CONTRACT_MISMATCH,
        message: 'Mock payment vendor account missing',
      };
    }
    const [mockContract] = await insertExpectedPaymentContract(db, {
      purchaseId: purchase.id,
      orderId: orderRow.id,
      amountAgorot: pi.amount,
      currency: pi.currency,
      customerId: piCustomer,
      destinationAccountId: destination ?? orderRow.vendorStripeAccountId,
      applicationFeeAgorot,
      effectKey: `payment-contract:${purchase.id}`,
      bindingSource: 'mock_sync',
    });
    expected =
      mockContract ??
      (
        await db
          .select()
          .from(paymentExpectedContracts)
          .where(eq(paymentExpectedContracts.purchaseId, purchase.id))
          .limit(1)
      )[0];
  }
  if (!expected) {
    const hasAuthoritativeBinding = hasAuthoritativeLegacyPaymentBinding({
      purchaseId: purchase.id,
      orderId: orderRow.id,
      metadata: pi.metadata,
      providerPaymentId: pi.id,
    });
    if (!hasAuthoritativeBinding) {
      return {
        ok: false,
        code: PaymentErrorCode.PAYMENT_CONTRACT_MISMATCH,
        message: 'Missing authoritative legacy purchase/order binding',
      };
    }
    if (!Number.isInteger(Number(orderRow.total)) || Number(orderRow.total) !== pi.amount) {
      return {
        ok: false,
        code: PaymentErrorCode.PAYMENT_CONTRACT_MISMATCH,
        message: 'Legacy payment amount binding mismatch',
      };
    }
    const [buyer] = orderRow.buyerUserId
      ? await db
          .select({ stripeCustomerId: users.stripeCustomerId })
          .from(users)
          .where(eq(users.id, orderRow.buyerUserId))
          .limit(1)
      : [];
    if (!buyer?.stripeCustomerId || !piCustomer || buyer.stripeCustomerId !== piCustomer) {
      return {
        ok: false,
        code: PaymentErrorCode.PAYMENT_CONTRACT_MISMATCH,
        message: 'Missing authoritative legacy buyer binding',
      };
    }
    if (
      !destination ||
      !orderRow.vendorStripeAccountId ||
      !isLegacyPaymentDestinationBound({
        destinationAccountId: destination,
        vendorStripeAccountId: orderRow.vendorStripeAccountId,
      })
    ) {
      return {
        ok: false,
        code: PaymentErrorCode.PAYMENT_CONTRACT_MISMATCH,
        message: 'Legacy payment destination binding mismatch',
      };
    }
    const [legacy] = await insertExpectedPaymentContract(db, {
      purchaseId: purchase.id,
      orderId: orderRow.id,
      amountAgorot: pi.amount,
      currency: pi.currency,
      customerId: buyer.stripeCustomerId,
      destinationAccountId: destination,
      applicationFeeAgorot,
      effectKey: `payment-contract:${purchase.id}`,
      bindingSource: 'legacy_backfill',
    });
    expected =
      legacy ??
      (
        await db
          .select()
          .from(paymentExpectedContracts)
          .where(eq(paymentExpectedContracts.purchaseId, purchase.id))
          .limit(1)
      )[0];
  }
  if (
    !expected ||
    !paymentIntentMatchesExpectedContract({
      expected,
      orderId: orderRow.id,
      piAmount: pi.amount,
      piCurrency: pi.currency,
      piCustomer,
      destination,
      applicationFeeAgorot,
      isMockPaymentIntent,
    })
  ) {
    return {
      ok: false,
      code: PaymentErrorCode.PAYMENT_CONTRACT_MISMATCH,
      message: 'PaymentIntent payment contract mismatch',
    };
  }
  const orderId: string | undefined = orderRow?.id
    ? toModuleRef(asOrderId(orderRow.id))
    : undefined;

  // Idempotent — already finalized.
  // Still attempt the referral earn here: it is idempotent (qualify guards on
  // status='pending'→'qualified'; accrue keyed on purchaseId), so any later
  // finalize call on an already-COMPLETED purchase retries a previously-failed
  // earn. The current purchase is COMPLETED in this branch, so qualify's
  // paidCount includes it → first-paid semantics stay correct.
  // T7: status is now lowercase 'paid' (order.status) not 'COMPLETED'.
  // Any post-paid status counts as already-finalized — re-running the full
  // finalize on a fulfilled/refunded order would regenerate QRs and re-mark paid.
  const stripeFeeAgorot = await resolveStripeProcessingFeeAgorot(env, pi);
  const finalizeDecision = decidePaymentsFinalize({
    ctxId: asPaymentsFinalizeCtxId(`payments-finalize:${purchase.id}:${pi.id}`),
    paymentStatus: purchase.paymentStatus,
    purchaseId: asOrderLineId(purchase.id),
    orderId: orderId ? asOrderId(orderId) : undefined,
    dealId: purchase.dealId ? asDealId(purchase.dealId) : undefined,
    userId: purchase.userId ? asUserId(purchase.userId) : undefined,
    stripeFeeAgorot,
    providerPaymentId: pi.id,
  });
  if (!finalizeDecision.ok) {
    return {
      ok: false,
      code: PaymentErrorCode.PROVIDER_ERROR,
      message: `${finalizeDecision.message} — cannot mark paid`,
    };
  }

  const platformAgorot = applicationFeeAgorot;
  const vendorAgorot = pi.amount - platformAgorot;
  if (finalizeDecision.alreadyFinalized) {
    await persistStripeFeeAgorot(db, orderId, stripeFeeAgorot, pi.id);
    if (purchase.userId) await dispatchReferralEarnForPurchase(db, env, purchase.id);
    return buildChargeOk(pi, vendorAgorot, platformAgorot);
  }

  const claimFailure = await claimPaidOrder(db, purchase.id, orderRow.id, pi.id);
  if (claimFailure) return claimFailure;

  let reconciliationLines: Awaited<ReturnType<typeof ensureOrderFulfilled>>['lines'] = [];
  if (orderId) {
    const fulfillmentPorts = makeFulfillmentPorts(db as unknown as DrizzleDb, {
      QR_SECRET: env.QR_SECRET,
      R2: env.R2_BUCKET,
    });
    const platformOrder = await getOrderById(db as unknown as Querier<OrdersSchema>, orderId, {
      userId: purchase.userId ? toModuleRef(asUserId(purchase.userId)) : undefined,
    });
    if (!platformOrder) {
      return {
        ok: false,
        code: PaymentErrorCode.PROVIDER_ERROR,
        message: `Order not found for purchase ${purchase.id}`,
      };
    }
    try {
      const reconciliation = await ensureOrderFulfilled(
        db,
        fulfillmentPorts,
        platformOrder,
        purchase.id,
      );
      reconciliationLines = reconciliation.lines;
    } catch (error) {
      return {
        ok: false,
        code: PaymentErrorCode.PROVIDER_ERROR,
        message:
          error instanceof Error ? error.message : `Fulfillment failed for purchase ${purchase.id}`,
      };
    }
  }

  // Step 0: Issue one voucher-scoped QR credential per purchased unit.
  try {
    for (const outcome of reconciliationLines) {
      for (const v of outcome.vouchers) {
        await runCheckpoint(
          db,
          purchase.id,
          orderRow.id,
          pi.id,
          `qr-fulfillment:${v.voucherId}`,
          async () => {
            const generated = await generateVoucherQrToken(
              v.voucherId,
              env.QR_SECRET,
              180 * 24 * 60 * 60,
            );
            const qrPngUrl = await uploadPurchaseQr(
              env.R2_BUCKET,
              v.voucherId,
              generated.token,
              env.PUBLIC_SITE_URL,
            );
            const lineId = toModuleRef(asOrderLineId(outcome.lineId));
            if (!purchase.userId && v.voucherId === outcome.vouchers[0]?.voucherId) {
              await (db as unknown as TxDrizzleClient).transaction(async (tx) => {
                const txDb = tx as unknown as DrizzleDb;
                const guestAccessCarry = await consumePendingGuestAccessToken(txDb, lineId);
                await txDb
                  .insert(orderLineVoucherExt)
                  .values({
                    voucherId: v.voucherId,
                    lineId,
                    qrTokenHash: generated.tokenHash,
                    qrPngUrl,
                    ...(guestAccessCarry
                      ? {
                          guestAccessTokenHash: guestAccessCarry.hash,
                          guestAccessTokenExpiresAt: guestAccessCarry.expiresAt,
                        }
                      : {}),
                  })
                  .onConflictDoUpdate({
                    target: orderLineVoucherExt.voucherId,
                    set: {
                      qrTokenHash: generated.tokenHash,
                      qrPngUrl,
                      ...(guestAccessCarry
                        ? {
                            guestAccessTokenHash: guestAccessCarry.hash,
                            guestAccessTokenExpiresAt: guestAccessCarry.expiresAt,
                          }
                        : {}),
                    },
                  });
              });
            } else {
              await upsertVoucherQr(db, {
                voucherId: v.voucherId,
                lineId,
                qrTokenHash: generated.tokenHash,
                qrPngUrl,
              });
            }
            return { tokenHash: generated.tokenHash, qrPngUrl };
          },
        );
      }
      if (outcome.vouchers.length > 0) {
        await expireVouchersForLine(
          db,
          toModuleRef(asOrderLineId(outcome.lineId)),
          purchase.expiresAt ?? new Date(Date.now() + 180 * 86400000),
        );
      }
    }
  } catch (error) {
    if (error instanceof PaymentFinalizeFailure) return error.failure;
    throw error;
  }

  // Step 1: Issue vendor + platform tax documents (VAT from DB schedule)
  const invoices = await runCheckpoint(db, purchase.id, orderRow.id, pi.id, 'invoices', () =>
    issueInvoices(
      env,
      db,
      purchase.id,
      pi,
      vendorAgorot,
      platformAgorot,
      orderRow.buyerDisplayName ?? 'Customer',
      purchase.vendorId,
      expected.destinationAccountId,
    ),
  );
  const resolvedInvoices =
    invoices ??
    (await issueInvoices(
      env,
      db,
      purchase.id,
      pi,
      vendorAgorot,
      platformAgorot,
      orderRow.buyerDisplayName ?? 'Customer',
      purchase.vendorId,
      expected.destinationAccountId,
    ));

  // Step 2: Persist confirmation, sold-out check, outbox dispatch
  const finalizeFailure = await runCheckpoint(
    db,
    purchase.id,
    orderRow.id,
    pi.id,
    'confirmation-outbox-fees',
    () => applyFinalizeEffects(env, db, purchase, finalizeDecision.effects),
  );
  if (finalizeFailure) {
    return finalizeFailure;
  }

  // Step 3: Write promo redemption ledger
  await runCheckpoint(db, purchase.id, orderRow.id, pi.id, 'promo', () =>
    redeemPromo(db, purchase, pi),
  );

  // ── Referral earn on first durable COMPLETED transition (spec §8) ──────────
  // This is the single COMPLETED chokepoint: both the webhook
  // (payment_intent.succeeded) and the /api/checkout/finalize redirect-poll
  // fallback funnel through finalizePurchase, so placing the earn hooks here
  // covers both paths. Both service calls are idempotent on (entryType,
  // sourceType, sourceId), so a double-fire (webhook + poll) is a safe no-op.
  // Durability for webhook-only failures is guaranteed by the referral.earn
  // outbox row inserted atomically in confirmCheckout's COMPLETED tx.
  //
  // platformNetAgorot = the order's platform take net of any platform-funded
  // promo discount. That is exactly the Stripe application_fee_amount: at
  // checkout the platform-funded discount is already subtracted from the
  // commission before it becomes the application fee (cart-checkout.ts:
  // commissionAgorot = feeOnOriginal − lineDiscountAgorot), and wallet-credit
  // redemption above reduces it further. So platformAgorot === platformNet.
  //
  // Only authenticated buyers earn — guest purchases (userId null) are skipped.
  if (purchase.userId) {
    await runCheckpoint(db, purchase.id, orderRow.id, pi.id, 'referral', () =>
      dispatchReferralEarnForPurchase(db, env, purchase.id),
    );
  }

  if (isMockPaymentIntent) {
    await runCheckpoint(db, purchase.id, orderRow.id, pi.id, 'mock-settlement-release', () =>
      writeMockSettlementReleasesOnFinalize(db, purchase.id, pi, vendorAgorot, platformAgorot),
    );
  }

  return buildChargeOk(pi, vendorAgorot, platformAgorot, resolvedInvoices);
}
