            "sent_at": cr.sent_at.isoformat() if cr.sent_at else None,
            "last_error": (cr.last_error or "")[:200],
        })
    return {"recipients": out, "total": total, "page": page, "page_size": page_size}


@router.get("/{cid}/export")
async def export_campaign(cid: int, db: AsyncSession = Depends(get_db),
                          user: User = Depends(get_current_user)):
    """CSV export of campaign results (one row per campaign recipient)."""
    c = await _get_own(db, user, cid)
    rows = (await db.execute(
        select(CampaignRecipient).where(CampaignRecipient.campaign_id == cid)
        .order_by(CampaignRecipient.seq))).scalars().all()

    def generate():
        buf = io.StringIO()
        w = csv.writer(buf)
        w.writerow(["seq", "email", "status", "account", "subject", "template",
                    "invoice_number", "amount", "shipping", "attempts",
                    "message_id", "thread_id", "sent_at", "last_error"])
        yield buf.getvalue()
        buf.seek(0); buf.truncate(0)
        for cr in rows:
            w.writerow([cr.seq, cr.email, cr.status,
                        acc_map.get(cr.assigned_account_id, ""),
                        rendered_map.get(cr.email) or subj_map.get(cr.generated_subject_id, ""),
                        tpl_map.get(cr.generated_template_id, ""),
                        cr.generated_invoice_number, cr.generated_amount or "",
                        cr.generated_shipping or "", cr.attempt_count,
                        cr.msg_id, cr.thread_id,
                        cr.sent_at.isoformat() if cr.sent_at else "",
                        (cr.last_error or "")[:300]])
            yield buf.getvalue()
            buf.seek(0); buf.truncate(0)

        return StreamingResponse(generate(), media_type="text/csv",
                             headers={"Content-Disposition":
                                      f'attachment; filename="{fname}.csv"'})


# ---------------------------------------------------------------------------
# Wizard options: what lists/subjects/templates/accounts/phones are selectable
# ---------------------------------------------------------------------------

@router.get("/wizard/options")
async def wizard_options(db: AsyncSession = Depends(get_db),
                         user: User = Depends(get_current_user)):
    """All selectable entities for the 12-step wizard in one call."""
    lists = (await db.execute(select(RecipientList).where(RecipientList.user_id == user.id)
                              .order_by(RecipientList.id.desc()))).scalars().all()
