'use server';
import { redirect } from 'next/navigation';
import { revalidatePath } from 'next/cache';
import { db } from './db';
import crypto from 'node:crypto';
import {
  getAdById,
  getAdOwnerId,
  getOwnerBusiness,
  getBusinessByIdOrFirst,
  getBusinessOwnerId,
  getReviewStats,
  canUserReview,
  BIZ_ID_BASE,
} from './queries';
import { CATS } from './data';
import { saveImages, saveImage } from './uploads';
import { notifyInbox } from './bus';
import { notify, appUrl } from './notify';
import { sendEmail, emailLayout, APP_URL } from './email';
import {
  createUser,
  endSession,
  findUserByEmail,
  getCurrentUser,
  hashPassword,
  startSession,
  verifyPassword,
} from './auth';

const slugify = (s: string) =>
  s
    .toLowerCase()
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '');

export type AuthState = { error?: string };

export async function authenticate(_prev: AuthState, formData: FormData): Promise<AuthState> {
  const mode = String(formData.get('mode') || 'login');
  const email = String(formData.get('email') || '').trim();
  const password = String(formData.get('password') || '');

  if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) return { error: 'Adresse e-mail invalide.' };
  if (password.length < 6) return { error: 'Le mot de passe doit contenir au moins 6 caractères.' };

  let destination = '/compte';

  if (mode === 'signup') {
    const account = (String(formData.get('account') || 'client') === 'pro' ? 'pro' : 'client') as
      | 'client'
      | 'pro';
    const first = String(formData.get('first') || '').trim();
    const last = String(formData.get('last') || '').trim();
    const businessName = String(formData.get('business_name') || '').trim();
    if (account === 'pro' && !businessName) return { error: 'Indiquez le nom de votre commerce.' };
    if (findUserByEmail(email)) return { error: 'Un compte existe déjà avec cet e-mail.' };

    const id = createUser({ email, password, first, last, account, business_name: businessName });
    await startSession(id);
    destination = account === 'pro' ? '/pro' : '/compte';
  } else {
    const user = findUserByEmail(email);
    if (!user || !verifyPassword(password, user.password_hash)) {
      return { error: 'E-mail ou mot de passe incorrect.' };
    }
    await startSession(user.id);
    destination = user.account === 'pro' ? '/pro' : '/compte';
  }

  redirect(destination);
}

export async function logout(): Promise<void> {
  await endSession();
  revalidatePath('/', 'layout');
  redirect('/');
}

export async function createReservation(input: {
  bizId: number;
  day: number;
  slot: string;
  covers: number;
}): Promise<{ ok: boolean; ref?: string }> {
  const user = await getCurrentUser();
  if (!user) return { ok: false };
  const ref = 'DSK-' + Math.floor(4900 + Math.random() * 900);
  const dayNum = 12 + (input.day || 0);
  // Real (pro-owned) businesses require the merchant to accept → "En attente".
  const status = input.bizId >= BIZ_ID_BASE ? 'En attente' : 'Confirmée';
  db.prepare(
    `INSERT INTO reservations (user_id, biz_id, day_num, month, time, covers, status, ref, created_at)
     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
  ).run(user.id, input.bizId, dayNum, 'Mai', input.slot, input.covers, status, ref, Date.now());

  const biz = getBusinessByIdOrFirst(input.bizId);
  const customerName = [user.first, user.last].filter(Boolean).join(' ') || user.email;
  // Notify the customer (confirmation / request sent).
  notify(user.id, {
    type: 'reservation',
    title: status === 'Confirmée' ? 'Réservation confirmée' : 'Demande de réservation envoyée',
    body: `${biz.name} · ${dayNum} ${'Mai'} à ${input.slot} · ${input.covers} pers. · réf ${ref}`,
    link: '/compte?tab=resas',
    email: true,
    prefKey: 'resa',
    emailCta: { label: 'Voir ma réservation', url: appUrl('/compte?tab=resas') },
  });
  // Notify the merchant if this is a real (pro-owned) business.
  const ownerId = getBusinessOwnerId(input.bizId);
  if (ownerId && ownerId !== user.id) {
    notify(ownerId, {
      type: 'reservation',
      title: 'Nouvelle réservation',
      body: `${customerName} · ${input.covers} pers. · ${dayNum} Mai à ${input.slot}`,
      link: '/pro',
      email: true,
      prefKey: 'resa',
      emailCta: { label: 'Gérer la réservation', url: appUrl('/pro') },
    });
  }
  revalidatePath('/compte');
  revalidatePath('/pro');
  return { ok: true, ref };
}

/** Pro accepts/refuses a reservation made on one of their businesses. */
export async function setReservationStatus(
  id: number,
  status: 'Confirmée' | 'Refusée'
): Promise<void> {
  const user = await getCurrentUser();
  if (!user) return;
  const owned = db
    .prepare(
      `SELECT r.id FROM reservations r JOIN businesses b ON b.id = r.biz_id
       WHERE r.id = ? AND b.owner_id = ?`
    )
    .get(id, user.id);
  if (!owned) return;
  db.prepare('UPDATE reservations SET status = ? WHERE id = ?').run(status, id);
  const r = db.prepare('SELECT user_id, biz_id, time, day_num FROM reservations WHERE id = ?').get(id) as
    | { user_id: number; biz_id: number; time: string; day_num: number }
    | undefined;
  if (r) {
    const biz = getBusinessByIdOrFirst(r.biz_id);
    notify(r.user_id, {
      type: 'reservation',
      title: status === 'Confirmée' ? 'Réservation confirmée ✓' : 'Réservation refusée',
      body: `${biz.name} · ${r.day_num} Mai à ${r.time}`,
      link: '/compte?tab=resas',
      email: true,
      prefKey: 'resa',
      emailCta: { label: 'Voir ma réservation', url: appUrl('/compte?tab=resas') },
    });
  }
  revalidatePath('/pro');
  revalidatePath('/compte');
}

/** Create or update the current pro's business listing (one per owner). */
export async function saveBusiness(form: FormData): Promise<{ ok: boolean; id?: number; error?: string }> {
  const user = await getCurrentUser();
  if (!user) return { ok: false, error: 'auth' };
  if (user.account !== 'pro') return { ok: false, error: 'Compte commerçant requis.' };
  const name = String(form.get('name') || '').trim();
  if (!name) return { ok: false, error: 'Le nom du commerce est obligatoire.' };

  const cat = String(form.get('cat') || 'restaurants');
  const catName = CATS.find((c) => c.key === cat)?.name || 'Commerce';
  const amenities = form.getAll('amenities').map((a) => String(a));
  const files = form.getAll('photos').filter((f): f is File => f instanceof File);

  let kept: string[] = [];
  try {
    kept = JSON.parse(String(form.get('keepPhotos') || '[]'));
  } catch {
    kept = [];
  }
  const added = await saveImages(files, Math.max(0, 8 - kept.length));
  const photos = [...kept, ...added];

  const fields = {
    name,
    slug: slugify(name),
    cat,
    cat_name: catName,
    city: String(form.get('city') || 'Alger'),
    area: String(form.get('area') || '').trim(),
    address: String(form.get('address') || '').trim(),
    phone: String(form.get('phone') || '').trim(),
    descr: String(form.get('descr') || '').trim(),
    price: String(form.get('price') || '').trim(),
    unit: String(form.get('unit') || '/ pers.').trim(),
    status: String(form.get('status') || 'open') === 'soon' ? 'soon' : 'open',
    amenities: JSON.stringify(amenities),
    photos: JSON.stringify(photos),
  };

  const existing = getOwnerBusiness(user.id);
  let id: number;
  if (existing) {
    db.prepare(
      `UPDATE businesses SET name=@name, slug=@slug, cat=@cat, cat_name=@cat_name, city=@city,
         area=@area, address=@address, phone=@phone, descr=@descr, price=@price, unit=@unit,
         status=@status, amenities=@amenities, photos=@photos
       WHERE owner_id=@owner_id`
    ).run({ ...fields, owner_id: user.id });
    id = existing.id;
  } else {
    const info = db
      .prepare(
        `INSERT INTO businesses (owner_id, name, slug, cat, cat_name, city, area, address, phone,
           descr, price, unit, status, amenities, photos, rating, reviews, created_at)
         VALUES (@owner_id, @name, @slug, @cat, @cat_name, @city, @area, @address, @phone,
           @descr, @price, @unit, @status, @amenities, @photos, 0, 0, @created_at)`
      )
      .run({ ...fields, owner_id: user.id, created_at: Date.now() });
    id = Number(info.lastInsertRowid);
  }
  revalidatePath('/', 'layout');
  revalidatePath('/pro');
  revalidatePath('/explorer');
  revalidatePath(`/commerce/${id}`);
  return { ok: true, id };
}

export async function cancelReservation(id: number): Promise<void> {
  const user = await getCurrentUser();
  if (!user) return;
  db.prepare('DELETE FROM reservations WHERE id = ? AND user_id = ?').run(id, user.id);
  revalidatePath('/compte');
}

/** Leave a review on a business the user has booked. */
export async function addReview(form: FormData): Promise<{ ok: boolean; error?: string }> {
  const user = await getCurrentUser();
  if (!user) return { ok: false, error: 'auth' };
  const businessId = Number(form.get('businessId'));
  const rating = Math.max(1, Math.min(5, Number(form.get('rating')) || 5));
  const body = String(form.get('body') || '').trim();
  if (!canUserReview(user.id, businessId)) {
    return { ok: false, error: 'Vous devez avoir réservé ici pour laisser un avis.' };
  }
  const author = [user.first, user.last].filter(Boolean).join(' ') || user.email;
  db.prepare(
    `INSERT INTO reviews (business_id, user_id, author, rating, body, created_at)
     VALUES (?, ?, ?, ?, ?, ?)`
  ).run(businessId, user.id, author, rating, body, Date.now());

  // Keep the business card rating in sync for DB businesses.
  if (businessId >= BIZ_ID_BASE) {
    const s = getReviewStats(businessId);
    db.prepare('UPDATE businesses SET rating = ?, reviews = ? WHERE id = ?').run(s.avg, s.count, businessId);
  }
  // Notify the merchant of the new review.
  const ownerId = getBusinessOwnerId(businessId);
  if (ownerId && ownerId !== user.id) {
    notify(ownerId, {
      type: 'review',
      title: 'Nouvel avis client',
      body: `${author} a laissé un avis ${rating}★`,
      link: '/pro',
    });
  }
  revalidatePath(`/commerce/${businessId}`);
  revalidatePath('/pro');
  revalidatePath('/explorer');
  revalidatePath('/');
  return { ok: true };
}

/** Business owner replies to a review on their establishment. */
export async function replyToReview(reviewId: number, text: string): Promise<{ ok: boolean }> {
  const user = await getCurrentUser();
  if (!user) return { ok: false };
  const reply = text.trim();
  if (!reply) return { ok: false };
  const row = db
    .prepare(
      `SELECT r.business_id FROM reviews r JOIN businesses b ON b.id = r.business_id
       WHERE r.id = ? AND b.owner_id = ?`
    )
    .get(reviewId, user.id) as { business_id: number } | undefined;
  if (!row) return { ok: false };
  db.prepare('UPDATE reviews SET reply = ?, reply_at = ? WHERE id = ?').run(reply, Date.now(), reviewId);
  revalidatePath(`/commerce/${row.business_id}`);
  revalidatePath('/pro');
  return { ok: true };
}

export async function createAd(
  form: FormData
): Promise<{ ok: boolean; id?: number; error?: string }> {
  const user = await getCurrentUser();
  if (!user) return { ok: false, error: 'auth' };
  const title = String(form.get('title') || '').trim();
  if (!title) return { ok: false, error: 'Le titre est obligatoire.' };

  const files = form.getAll('photos').filter((f): f is File => f instanceof File);
  const photos = await saveImages(files);
  const city = String(form.get('city') || 'Alger');

  const info = db
    .prepare(
      `INSERT INTO ads (user_id, title, cat, price, city, area, cond, nego, phone, descr, photos, status, created_at)
       VALUES (@user_id, @title, @cat, @price, @city, @area, @cond, @nego, @phone, @descr, @photos, 'active', @created_at)`
    )
    .run({
      user_id: user.id,
      title,
      cat: String(form.get('cat') || 'tel'),
      price: String(form.get('price') || '0').trim(),
      city,
      area: city,
      cond: String(form.get('cond') || ''),
      nego: form.get('nego') === '1' ? 1 : 0,
      phone: String(form.get('phone') || '').trim(),
      descr: String(form.get('descr') || '').trim(),
      photos: JSON.stringify(photos),
      created_at: Date.now(),
    });
  revalidatePath('/annonces');
  revalidatePath('/compte');
  return { ok: true, id: Number(info.lastInsertRowid) };
}

export async function updateAd(
  form: FormData
): Promise<{ ok: boolean; id?: number; error?: string }> {
  const user = await getCurrentUser();
  if (!user) return { ok: false, error: 'auth' };
  const id = Number(form.get('id'));
  const owned = db.prepare('SELECT photos FROM ads WHERE id = ? AND user_id = ?').get(id, user.id) as
    | { photos: string }
    | undefined;
  if (!owned) return { ok: false, error: 'introuvable' };
  const title = String(form.get('title') || '').trim();
  if (!title) return { ok: false, error: 'Le titre est obligatoire.' };

  let kept: string[] = [];
  try {
    kept = JSON.parse(String(form.get('keepPhotos') || '[]'));
  } catch {
    kept = [];
  }
  const files = form.getAll('photos').filter((f): f is File => f instanceof File);
  const added = await saveImages(files, Math.max(0, 8 - kept.length));
  const photos = [...kept, ...added];
  const city = String(form.get('city') || 'Alger');

  db.prepare(
    `UPDATE ads SET title=@title, cat=@cat, price=@price, city=@city, area=@area,
       cond=@cond, nego=@nego, phone=@phone, descr=@descr, photos=@photos
     WHERE id=@id AND user_id=@user_id`
  ).run({
    id,
    user_id: user.id,
    title,
    cat: String(form.get('cat') || 'tel'),
    price: String(form.get('price') || '0').trim(),
    city,
    area: city,
    cond: String(form.get('cond') || ''),
    nego: form.get('nego') === '1' ? 1 : 0,
    phone: String(form.get('phone') || '').trim(),
    descr: String(form.get('descr') || '').trim(),
    photos: JSON.stringify(photos),
  });
  revalidatePath('/annonces');
  revalidatePath('/compte');
  revalidatePath(`/annonces/${id}`);
  return { ok: true, id };
}

export async function updateProfile(form: FormData): Promise<{ ok: boolean; error?: string }> {
  const user = await getCurrentUser();
  if (!user) return { ok: false, error: 'auth' };
  const email = String(form.get('email') || '').trim().toLowerCase();
  if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) return { ok: false, error: 'E-mail invalide.' };
  const clash = findUserByEmail(email);
  if (clash && clash.id !== user.id) return { ok: false, error: 'Cet e-mail est déjà utilisé.' };
  db.prepare(
    `UPDATE users SET first=@first, last=@last, email=@email, phone=@phone, city=@city WHERE id=@id`
  ).run({
    id: user.id,
    first: String(form.get('first') || '').trim(),
    last: String(form.get('last') || '').trim(),
    email,
    phone: String(form.get('phone') || '').trim(),
    city: String(form.get('city') || 'Alger'),
  });
  revalidatePath('/', 'layout');
  return { ok: true };
}

export async function changePassword(form: FormData): Promise<{ ok: boolean; error?: string }> {
  const user = await getCurrentUser();
  if (!user) return { ok: false, error: 'auth' };
  const current = String(form.get('current') || '');
  const next = String(form.get('new') || '');
  const row = findUserByEmail(user.email);
  if (!row || !verifyPassword(current, row.password_hash)) {
    return { ok: false, error: 'Mot de passe actuel incorrect.' };
  }
  if (next.length < 6) return { ok: false, error: 'Le nouveau mot de passe doit faire au moins 6 caractères.' };
  db.prepare('UPDATE users SET password_hash = ? WHERE id = ?').run(hashPassword(next), user.id);
  return { ok: true };
}

export async function deleteAccount(form: FormData): Promise<{ ok: boolean; error?: string }> {
  const user = await getCurrentUser();
  if (!user) return { ok: false, error: 'auth' };
  const pwd = String(form.get('password') || '');
  const row = findUserByEmail(user.email);
  if (!row || !verifyPassword(pwd, row.password_hash)) {
    return { ok: false, error: 'Mot de passe incorrect.' };
  }
  db.prepare('DELETE FROM users WHERE id = ?').run(user.id); // cascades sessions, ads, resas, etc.
  await endSession();
  revalidatePath('/', 'layout');
  redirect('/');
}

export async function updateNotifPrefs(prefs: Record<string, boolean>): Promise<void> {
  const user = await getCurrentUser();
  if (!user) return;
  db.prepare('UPDATE users SET notif_prefs = ? WHERE id = ?').run(JSON.stringify(prefs), user.id);
}

export async function updateAvatar(form: FormData): Promise<{ ok: boolean; avatar?: string; error?: string }> {
  const user = await getCurrentUser();
  if (!user) return { ok: false, error: 'auth' };
  const file = form.get('avatar');
  if (!(file instanceof File)) return { ok: false, error: 'Fichier invalide.' };
  const path = await saveImage(file);
  if (!path) return { ok: false, error: 'Image invalide (JPG/PNG/WebP, 6 Mo max).' };
  db.prepare('UPDATE users SET avatar = ? WHERE id = ?').run(path, user.id);
  revalidatePath('/', 'layout');
  revalidatePath('/compte');
  return { ok: true, avatar: path };
}

export async function markNotificationsRead(): Promise<void> {
  const user = await getCurrentUser();
  if (!user) return;
  db.prepare('UPDATE notifications SET read = 1 WHERE user_id = ? AND read = 0').run(user.id);
  revalidatePath('/', 'layout');
}

/** Step 1 of password reset: e-mail a tokenized link. Never reveals if the e-mail exists. */
export async function requestPasswordReset(form: FormData): Promise<{ ok: boolean }> {
  const email = String(form.get('email') || '').trim().toLowerCase();
  const user = findUserByEmail(email);
  if (user && !user.email.endsWith('@darsouk.local')) {
    const token = crypto.randomBytes(32).toString('hex');
    const now = Date.now();
    db.prepare(
      'INSERT INTO password_resets (token, user_id, expires_at, used, created_at) VALUES (?, ?, ?, 0, ?)'
    ).run(token, user.id, now + 3_600_000, now);
    const url = `${APP_URL}/reinitialiser?token=${token}`;
    await sendEmail({
      to: user.email,
      subject: 'Réinitialisation de votre mot de passe Darsouk',
      html: emailLayout(
        'Réinitialisez votre mot de passe',
        '<p>Vous avez demandé à réinitialiser votre mot de passe. Ce lien expire dans 1 heure. Si vous n’êtes pas à l’origine de cette demande, ignorez cet e-mail.</p>',
        { label: 'Choisir un nouveau mot de passe', url }
      ),
    });
  }
  return { ok: true };
}

/** Step 2 of password reset: consume the token and set the new password. */
export async function resetPassword(form: FormData): Promise<{ ok: boolean; error?: string }> {
  const token = String(form.get('token') || '');
  const next = String(form.get('new') || '');
  if (next.length < 6) return { ok: false, error: 'Le mot de passe doit faire au moins 6 caractères.' };
  const row = db
    .prepare('SELECT user_id, expires_at, used FROM password_resets WHERE token = ?')
    .get(token) as { user_id: number; expires_at: number; used: number } | undefined;
  if (!row || row.used || row.expires_at < Date.now()) {
    return { ok: false, error: 'Lien invalide ou expiré. Refaites une demande.' };
  }
  db.prepare('UPDATE users SET password_hash = ? WHERE id = ?').run(hashPassword(next), row.user_id);
  db.prepare('UPDATE password_resets SET used = 1 WHERE token = ?').run(token);
  redirect('/connexion?mode=login&reset=1');
}

/** Upgrade the current client account to a merchant (pro) account. */
export async function becomePro(form: FormData): Promise<{ ok: boolean; error?: string }> {
  const user = await getCurrentUser();
  if (!user) return { ok: false, error: 'auth' };
  const name = String(form.get('business_name') || '').trim();
  if (!name) return { ok: false, error: 'Indiquez le nom de votre commerce.' };
  db.prepare(
    `UPDATE users SET account = 'pro', business_name = @name, business_category = @cat WHERE id = @id`
  ).run({
    id: user.id,
    name,
    cat: String(form.get('business_category') || '').trim(),
  });
  revalidatePath('/', 'layout');
  revalidatePath('/pro');
  redirect('/pro');
}

export async function updateProProfile(form: FormData): Promise<{ ok: boolean; error?: string }> {
  const user = await getCurrentUser();
  if (!user) return { ok: false, error: 'auth' };
  db.prepare(
    `UPDATE users SET business_name=@name, business_category=@cat, business_address=@addr, business_desc=@desc WHERE id=@id`
  ).run({
    id: user.id,
    name: String(form.get('business_name') || '').trim(),
    cat: String(form.get('business_category') || '').trim(),
    addr: String(form.get('business_address') || '').trim(),
    desc: String(form.get('business_desc') || '').trim(),
  });
  revalidatePath('/pro');
  revalidatePath('/', 'layout');
  return { ok: true };
}

export async function setAdStatus(id: number, status: 'active' | 'paused' | 'sold'): Promise<void> {
  const user = await getCurrentUser();
  if (!user) return;
  db.prepare('UPDATE ads SET status = ? WHERE id = ? AND user_id = ?').run(status, id, user.id);
  revalidatePath('/compte');
  revalidatePath('/annonces');
}

export async function deleteAd(id: number): Promise<void> {
  const user = await getCurrentUser();
  if (!user) return;
  db.prepare('DELETE FROM ads WHERE id = ? AND user_id = ?').run(id, user.id);
  revalidatePath('/compte');
  revalidatePath('/annonces');
}

/** Open (or reuse) a conversation with the seller of an ad. Returns the conversation id. */
export async function startConversation(
  adId: number
): Promise<{ ok: boolean; convId?: number; error?: string }> {
  const user = await getCurrentUser();
  if (!user) return { ok: false, error: 'auth' };
  const ad = getAdById(adId);
  if (!ad) return { ok: false, error: 'introuvable' };

  const sellerId = getAdOwnerId(adId);
  if (!sellerId) return { ok: false, error: 'introuvable' };
  if (sellerId === user.id) return { ok: false, error: 'own' };

  const adRef = (adId < 1000 ? 'seed:' : 'db:') + adId;
  const existing = db
    .prepare('SELECT id FROM conversations WHERE ad_ref = ? AND buyer_id = ?')
    .get(adRef, user.id) as { id: number } | undefined;
  if (existing) return { ok: true, convId: existing.id };

  const buyerName = [user.first, user.last].filter(Boolean).join(' ') || user.email;
  const now = Date.now();
  const info = db
    .prepare(
      `INSERT INTO conversations
         (ad_ref, ad_id, ad_title, ad_cat, ad_icon, ad_price, buyer_id, buyer_name, seller_id, seller_name, created_at, last_at)
       VALUES (@ad_ref, @ad_id, @ad_title, @ad_cat, @ad_icon, @ad_price, @buyer_id, @buyer_name, @seller_id, @seller_name, @now, @now)`
    )
    .run({
      ad_ref: adRef,
      ad_id: adId,
      ad_title: ad.title,
      ad_cat: ad.cat,
      ad_icon: ad.icon ?? '',
      ad_price: ad.price,
      buyer_id: user.id,
      buyer_name: buyerName,
      seller_id: sellerId,
      seller_name: ad.seller.n,
      now,
    });
  revalidatePath('/compte');
  revalidatePath('/pro');
  return { ok: true, convId: Number(info.lastInsertRowid) };
}

export async function sendMessage(convId: number, body: string): Promise<{ ok: boolean }> {
  const user = await getCurrentUser();
  if (!user) return { ok: false };
  const text = body.trim();
  if (!text) return { ok: false };
  const conv = db
    .prepare('SELECT buyer_id, seller_id FROM conversations WHERE id = ?')
    .get(convId) as { buyer_id: number; seller_id: number } | undefined;
  if (!conv || (conv.buyer_id !== user.id && conv.seller_id !== user.id)) return { ok: false };

  const now = Date.now();
  db.prepare('INSERT INTO messages (conv_id, sender_id, body, created_at) VALUES (?, ?, ?, ?)').run(
    convId,
    user.id,
    text,
    now
  );
  db.prepare('UPDATE conversations SET last_at = ? WHERE id = ?').run(now, convId);
  db.prepare(
    `INSERT INTO conv_reads (conv_id, user_id, last_read_at) VALUES (?, ?, ?)
     ON CONFLICT(conv_id, user_id) DO UPDATE SET last_read_at = excluded.last_read_at`
  ).run(convId, user.id, now);
  revalidatePath('/compte');
  revalidatePath('/pro');
  // Push to both participants' open SSE streams.
  notifyInbox(conv.buyer_id);
  notifyInbox(conv.seller_id);
  // Notify the recipient (in-app + email).
  const recipient = conv.buyer_id === user.id ? conv.seller_id : conv.buyer_id;
  const senderName = [user.first, user.last].filter(Boolean).join(' ') || user.email;
  notify(recipient, {
    type: 'message',
    title: `Nouveau message de ${senderName}`,
    body: text.length > 90 ? text.slice(0, 90) + '…' : text,
    link: `/compte?tab=messages&conv=${convId}`,
    email: true,
    prefKey: 'msg',
    emailCta: { label: 'Répondre', url: appUrl(`/compte?tab=messages&conv=${convId}`) },
  });
  return { ok: true };
}

export async function markRead(convId: number): Promise<void> {
  const user = await getCurrentUser();
  if (!user) return;
  db.prepare(
    `INSERT INTO conv_reads (conv_id, user_id, last_read_at) VALUES (?, ?, ?)
     ON CONFLICT(conv_id, user_id) DO UPDATE SET last_read_at = excluded.last_read_at`
  ).run(convId, user.id, Date.now());
  revalidatePath('/compte');
  revalidatePath('/pro');
}

export async function toggleFavorite(kind: 'biz' | 'ad', itemId: number): Promise<{ faved: boolean }> {
  const user = await getCurrentUser();
  if (!user) return { faved: false };
  const existing = db
    .prepare('SELECT 1 FROM favorites WHERE user_id = ? AND kind = ? AND item_id = ?')
    .get(user.id, kind, itemId);
  if (existing) {
    db.prepare('DELETE FROM favorites WHERE user_id = ? AND kind = ? AND item_id = ?').run(
      user.id,
      kind,
      itemId
    );
    revalidatePath('/compte');
    return { faved: false };
  }
  db.prepare('INSERT INTO favorites (user_id, kind, item_id, created_at) VALUES (?, ?, ?, ?)').run(
    user.id,
    kind,
    itemId,
    Date.now()
  );
  revalidatePath('/compte');
  return { faved: true };
}
