@router.get("/list")
async def list_suppression(search: str = Query(""),
                           reason: str = Query(""),
                           page: int = Query(1, ge=1),
                           page_size: int = Query(100, ge=1, le=500),
                           db: AsyncSession = Depends(get_db),
                           user: User = Depends(get_current_user)):
    q = select(SuppressionEntry).where(SuppressionEntry.user_id == user.id)
    if search:
        q = q.where(SuppressionEntry.email.ilike(f"%{search}%"))
    if reason:
        q = q.where(SuppressionEntry.reason == reason)
    total = (await db.execute(
        select(func.count()).select_from(q.subquery()))).scalar() or 0
    rows = (await db.execute(
        q.order_by(SuppressionEntry.id.desc()).offset((page - 1) * page_size).limit(page_size)
    )).scalars().all()
    return {"entries": [_serialize(s) for s in rows],
            "total": total, "page": page, "page_size": page_size}


@router.post("/add")
async def add_suppression(payload: SuppressionIn, db: AsyncSession = Depends(get_db),
                          user: User = Depends(get_current_user),
                          _: None = Depends(require_csrf)):
    email = payload.email.strip().lower()
    payload.reason = _norm_reason(payload.reason)
    existing = await db.execute(
        select(SuppressionEntry).where(SuppressionEntry.email == email,
                                       SuppressionEntry.user_id == user.id))
    if existing.first():
        raise HTTPException(status_code=409, detail="Already suppressed")
    s = SuppressionEntry(user_id=user.id, email=email, reason=payload.reason,
                         source=payload.source.strip()[:200])
    db.add(s)
    await db.commit()
    await log_audit(db, user.id, "suppression.add", "suppression", s.id,
                    {"reason": s.reason})
    return _serialize(s)


@router.post("/import")
async def import_suppression(file: UploadFile = None,
                             reason: str = Query("manual"),
                             db: AsyncSession = Depends(get_db),
                             user: User = Depends(get_current_user),
                             _: None = Depends(require_csrf)):
    """Import suppressed emails from CSV/TXT (one email per line or email,reason)."""
    if file is None:
        raise HTTPException(status_code=400, detail="No file uploaded")
    raw = await file.read()
    if len(raw) > 2 * 1024 * 1024:
        raise HTTPException(status_code=413, detail="File exceeds 2MB")
    try:
        text = raw.decode("utf-8-sig")
    except UnicodeDecodeError:
        raise HTTPException(status_code=400, detail="File must be UTF-8 text")
    if text.lstrip().startswith(("{", "[")):
        raise HTTPException(status_code=400, detail="Only CSV or TXT files are accepted")

    entries, invalid = [], []
    lines = text.splitlines()
===LOGS===
@router.get("/list")
async def list_logs(campaign_id: int = Query(0),
                    outcome: str = Query("", description="submitted|accepted|temp_failed|perm_failed|skipped"),
                    search: str = Query("", description="email or message id fragment"),
                    page: int = Query(1, ge=1),
                    page_size: int = Query(50, ge=1, le=200),
                    db: AsyncSession = Depends(get_db),
                    user: User = Depends(get_current_user)):
    """Unified send log: SendAttempt rows joined via SendJob to CampaignRecipient."""
    q = (select(SendAttempt, SendJob)
         .join(SendJob, SendAttempt.send_job_id == SendJob.id)
         .join(Campaign, Campaign.id == SendAttempt.campaign_id)
         .where(Campaign.user_id == user.id))
    if campaign_id:
        q = q.where(SendAttempt.campaign_id == campaign_id)
    if outcome:
        q = q.where(SendAttempt.outcome == outcome)
    if search:
        like = f"%{search}%"
        q = q.where(or_(SendAttempt.recipient_email.ilike(like),
                        SendAttempt.message_id.ilike(like)))
    total = (await db.execute(
        select(func.count()).select_from(q.subquery()))).scalar() or 0
    rows = (await db.execute(
        q.order_by(SendAttempt.id.desc()).offset((page - 1) * page_size).limit(page_size)
    )).all()
    out = []
    for a, job in rows:
        d = _ser_attempt(a, cr_id=job.campaign_recipient_id)
        d["email"] = a.recipient_email
        d["recipient_status"] = None  # filled cheaply below for joined rows
        out.append(d)
    if out:
        cr_ids = [d["campaign_recipient_id"] for d in out if d["campaign_recipient_id"]]
        if cr_ids:
            crs = (await db.execute(
                select(CampaignRecipient.id, CampaignRecipient.status)
                .where(CampaignRecipient.id.in_(cr_ids)))).all()
            smap = {r[0]: r[1] for r in crs}
            for d in out:
                if d["campaign_recipient_id"]:
                    d["recipient_status"] = smap.get(d["campaign_recipient_id"])
    return {"logs": out, "total": total, "page": page, "page_size": page_size}


@router.get("/events")
async def list_events(campaign_id: int = Query(0),
                      event_type: str = Query(""),
                      page: int = Query(1, ge=1),
                      page_size: int = Query(50, ge=1, le=200),
                      db: AsyncSession = Depends(get_db),
                      user: User = Depends(get_current_user)):
    q = (select(EmailEvent, CampaignRecipient)
         .join(CampaignRecipient, EmailEvent.campaign_recipient_id == CampaignRecipient.id)
         .join(Campaign, Campaign.id == EmailEvent.campaign_id)
         .where(Campaign.user_id == user.id))
    if campaign_id:
        q = q.where(EmailEvent.campaign_id == campaign_id)
    if event_type:
        q = q.where(EmailEvent.event_type == event_type)
    total = (await db.execute(
        select(func.count()).select_from(q.subquery()))).scalar() or 0
    rows = (await db.execute(
        q.order_by(EmailEvent.id.desc()).offset((page - 1) * page_size).limit(page_size)
    )).all()
    out = []
    for e, cr in rows:
        out.append({
            "id": e.id,
            "campaign_id": e.campaign_id,
            "email": cr.email,
            "event_type": e.event_type,
            "detail": (e.detail or "")[:300],
            "message_id": e.message_id,
            "created_at": e.created_at.isoformat() if e.created_at else None,
        })
    return {"events": out, "total": total, "page": page, "page_size": page_size}


@router.get("/audit")
async def list_audit(action: str = Query(""),
                     page: int = Query(1, ge=1),
