/**
 * /api/invitations
 *
 * Routes publiques (pas de requireAuth) :
 *   GET  /:token          — valide le token, retourne email + role
 *   POST /:token/register — finalise l'inscription
 *
 * Route admin :
 *   POST /               — envoie une invitation (requireAdmin dans server.js)
 *   GET  /               — liste les invitations en cours (requireAdmin)
 *   DELETE /:id          — révoque une invitation (supprime aussi le user en attente)
 */

import { Router } from 'express';
import crypto from 'crypto';
import bcrypt from 'bcryptjs';
import { z } from 'zod';
import db from '../db/index.js';
import { HttpError } from '../middleware/errorHandler.js';
import { sendMail, buildEmailHtml, getSmtpConfig } from '../utils/mailer.js';
import { audit } from '../utils/audit.js';

const router = Router();

// ── Helpers ───────────────────────────────────────────────────────────────────

/** Dérive un nom affiché depuis une adresse email. */
function nameFromEmail(email) {
  const local = email.split('@')[0];
  return local
    .replace(/[._\-+]+/g, ' ')
    .replace(/\b\w/g, c => c.toUpperCase())
    .trim();
}

// ── Schémas Zod ───────────────────────────────────────────────────────────────

const InviteSchema = z.object({
  email: z.string().email(),
  role:  z.enum(['user', 'admin']).default('user'),
});

const RegisterSchema = z.object({
  displayName: z.string().max(100).optional(),
  password:    z.string().min(8),
});

// ── Admin : envoyer une invitation ───────────────────────────────────────────

router.post('/', async (req, res, next) => {
  try {
    const { email, role } = InviteSchema.parse(req.body);

    // Compte existant et vérifié → refus
    const existingUser = db.prepare('SELECT id, email_verified FROM users WHERE LOWER(email) = LOWER(?)').get(email);
    if (existingUser?.email_verified) {
      throw new HttpError(409, 'Un compte actif avec cet email existe déjà.');
    }

    // Révoquer toute invitation en cours non utilisée pour cet email
    db.prepare("DELETE FROM invitations WHERE LOWER(email) = LOWER(?) AND used_at IS NULL").run(email);

    // Créer (ou mettre à jour) le user en attente
    if (existingUser) {
      // Compte déjà en attente d'une ancienne invitation → on met à jour le rôle
      db.prepare("UPDATE users SET role=?, updated_at=datetime('now') WHERE id=?").run(role, existingUser.id);
    } else {
      // Nouveau user : mot de passe inutilisable (token aléatoire hashé)
      const unusableHash = await bcrypt.hash(crypto.randomBytes(32).toString('hex'), 10);
      db.prepare(`
        INSERT INTO users (email, password_hash, display_name, role, email_verified, status, created_at, updated_at)
        VALUES (?, ?, ?, ?, 0, 'active', datetime('now'), datetime('now'))
      `).run(email, unusableHash, nameFromEmail(email), role);
    }

    // Créer le token d'invitation (7 jours)
    const token     = crypto.randomBytes(32).toString('hex');
    const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();

    db.prepare(`
      INSERT INTO invitations (token, email, role, invited_by, expires_at)
      VALUES (?, ?, ?, ?, ?)
    `).run(token, email, role, req.user.id, expiresAt);

    // Envoyer l'email
    const cfg       = getSmtpConfig();
    const appUrl    = cfg.appUrl || '';
    const inviteUrl = `${appUrl}/invitation/${token}`;
    const roleLabel = role === 'admin' ? 'Administrateur' : 'Utilisateur';

    await sendMail({
      to:      email,
      subject: `Invitation à rejoindre ${cfg.appName}`,
      html:    buildEmailHtml({
        title: 'Vous avez été invité',
        body: `
          <p>Vous avez reçu une invitation à rejoindre <strong>${cfg.appName}</strong>
          en tant que <strong>${roleLabel}</strong>.</p>
          <p>Cliquez sur le bouton ci-dessous pour créer votre compte.
          Ce lien est valable <strong>7 jours</strong>.</p>
        `,
        ctaLabel: 'Créer mon compte',
        ctaUrl:   inviteUrl,
      }),
    });

    audit(req, { action: 'invitation_sent', category: 'invitation', actorId: req.user.id, details: { email, role, expiresAt } });
    res.json({ ok: true, email, expiresAt });
  } catch (e) { next(e); }
});

// ── Admin : liste des invitations ─────────────────────────────────────────────

router.get('/', (req, res, next) => {
  try {
    const rows = db.prepare(`
      SELECT i.id, i.email, i.role, i.expires_at, i.used_at, i.created_at,
             u.display_name AS invited_by_name, u.email AS invited_by_email
      FROM invitations i
      LEFT JOIN users u ON u.id = i.invited_by
      ORDER BY i.created_at DESC
      LIMIT 200
    `).all();
    res.json(rows);
  } catch (e) { next(e); }
});

// ── Admin : révoquer une invitation ───────────────────────────────────────────

router.delete('/:id', (req, res, next) => {
  try {
    const inv = db.prepare('SELECT * FROM invitations WHERE id = ? AND used_at IS NULL').get(Number(req.params.id));
    if (!inv) throw new HttpError(404, 'Invitation introuvable ou déjà utilisée.');

    // Supprimer le user en attente associé (non vérifié)
    db.prepare('DELETE FROM users WHERE LOWER(email) = LOWER(?) AND email_verified = 0').run(inv.email);

    db.prepare('DELETE FROM invitations WHERE id = ?').run(inv.id);
    res.json({ ok: true });
  } catch (e) { next(e); }
});

// ── Public : valider un token ─────────────────────────────────────────────────

router.get('/:token', (req, res, next) => {
  try {
    const inv = db.prepare('SELECT * FROM invitations WHERE token = ?').get(req.params.token);
    if (!inv)        throw new HttpError(404, "Lien d'invitation invalide.");
    if (inv.used_at) throw new HttpError(410, 'Ce lien a déjà été utilisé.');
    if (new Date(inv.expires_at) < new Date()) throw new HttpError(410, 'Ce lien a expiré.');
    res.json({ email: inv.email, role: inv.role, expiresAt: inv.expires_at });
  } catch (e) { next(e); }
});

// ── Public : finaliser l'inscription ─────────────────────────────────────────

router.post('/:token/register', async (req, res, next) => {
  try {
    const { displayName, password } = RegisterSchema.parse(req.body);

    const inv = db.prepare('SELECT * FROM invitations WHERE token = ?').get(req.params.token);
    if (!inv)        throw new HttpError(404, "Lien d'invitation invalide.");
    if (inv.used_at) throw new HttpError(410, 'Ce lien a déjà été utilisé.');
    if (new Date(inv.expires_at) < new Date()) throw new HttpError(410, 'Ce lien a expiré.');

    // Récupérer le user en attente créé à l'invitation
    const pendingUser = db.prepare('SELECT id FROM users WHERE LOWER(email) = LOWER(?) AND email_verified = 0').get(inv.email);
    if (!pendingUser) throw new HttpError(409, 'Ce compte a déjà été activé ou supprimé.');

    const hash = await bcrypt.hash(password, 12);

    // Finaliser le compte : mot de passe, nom, vérification
    db.prepare(`
      UPDATE users
      SET password_hash  = ?,
          display_name   = ?,
          email_verified = 1,
          updated_at     = datetime('now')
      WHERE id = ?
    `).run(hash, displayName || nameFromEmail(inv.email), pendingUser.id);

    // Marquer l'invitation comme utilisée
    db.prepare("UPDATE invitations SET used_at = datetime('now') WHERE id = ?").run(inv.id);
    audit(req, { action: 'invitation_accepted', category: 'invitation', targetUserId: pendingUser.id, details: { email: inv.email, role: inv.role } });

    res.json({ ok: true, userId: pendingUser.id });
  } catch (e) { next(e); }
});

export default router;
