import { Router } from 'express';
import multer from 'multer';
import xlsx from 'xlsx';
import path from 'node:path';
import fs from 'node:fs';
import db from '../db/index.js';
import { HttpError } from '../middleware/errorHandler.js';
import { requireInvestisseur } from '../middleware/investisseurScope.js';
import { generateSimul, generateSimulWithReinvestissements } from '../utils/schedule.js';

const router = Router();

const UPLOAD_DIR = process.env.UPLOAD_DIR || path.resolve('./uploads');
fs.mkdirSync(UPLOAD_DIR, { recursive: true });

const upload = multer({
  dest: UPLOAD_DIR,
  limits: { fileSize: 10 * 1024 * 1024 }, // 10 MB
});

/**
 * Step 1: POST /api/imports/preview
 *   multipart/form-data with `file` (xlsx/csv)
 *   -> returns: { headers: [...], sampleRows: [...], allRowCount, tempId }
 *
 * Step 2: POST /api/imports/apply
 *   body: { tempId, module, mapping, defaults }
 *   -> applies the mapping and inserts rows in the chosen module
 */

// Modules scoped to a specific investisseur (require X-Investisseur-Id)
const INVESTISSEUR_SCOPED = ['depots_retraits', 'investissements', 'remboursements'];

const MODULES = {
  depots_retraits: {
    requiredTargets: ['plateforme_id', 'date_operation', 'type', 'montant'],
    optionalTargets: ['libelle', 'reference', 'notes'],
  },
  investissements: {
    requiredTargets: ['plateforme_id', 'nom_projet', 'date_souscription', 'montant_investi'],
    optionalTargets: ['emetteur','date_premiere_echeance','date_cible','taux_interet','duree_mois','type_remb','freq_interets','statut','reference','notes'],
  },
  remboursements: {
    requiredTargets: ['investissement_id', 'date_remb'],
    optionalTargets: ['capital','cashback','interets_bruts','prelev_sociaux','prelev_forfaitaire','statut','notes'],
  },
  plateformes: {
    requiredTargets: ['nom'],
    optionalTargets: ['url', 'notes'],
  },
  taux_pfu: {
    requiredTargets: ['annee', 'pfu_total', 'impot_revenu', 'prelev_sociaux'],
    optionalTargets: [],
  },
};

/** Parse un fichier uploadé selon son extension → tableau d'objets */
function parseFile(filePath, originalName) {
  const ext = path.extname(originalName).toLowerCase();
  if (ext === '.json') {
    const content = fs.readFileSync(filePath, 'utf8');
    let parsed;
    try { parsed = JSON.parse(content); } catch {
      throw new HttpError(400, 'Fichier JSON invalide — vérifiez la syntaxe');
    }
    if (!Array.isArray(parsed)) {
      throw new HttpError(400, 'Le fichier JSON doit contenir un tableau d\'objets à la racine');
    }
    return { rows: parsed, sheetName: 'json' };
  }
  // Excel / CSV
  const wb = xlsx.readFile(filePath, { cellDates: true });
  const sheetName = wb.SheetNames[0];
  const ws = wb.Sheets[sheetName];
  return { rows: xlsx.utils.sheet_to_json(ws, { defval: null, raw: false }), sheetName };
}

router.post('/preview', upload.single('file'), (req, res, next) => {
  try {
    if (!req.file) throw new HttpError(400, 'No file uploaded');
    const { rows, sheetName } = parseFile(req.file.path, req.file.originalname);
    const headers = rows.length ? Object.keys(rows[0]) : [];

    res.json({
      tempId: path.basename(req.file.path),
      filename: req.file.originalname,
      sheetName,
      headers,
      sampleRows: rows.slice(0, 10),
      allRowCount: rows.length,
    });
  } catch (e) { next(e); }
});

router.post('/apply', (req, res, next) => {
  try {
    const { tempId, module, mapping, defaults = {} } = req.body || {};
    if (!tempId || !module || !mapping) {
      throw new HttpError(400, 'tempId, module and mapping are required');
    }
    const def = MODULES[module];
    if (!def) throw new HttpError(400, 'Unknown module');

    // Require a specific investisseur for transactional modules
    if (INVESTISSEUR_SCOPED.includes(module)) {
      requireInvestisseur(req, res, () => {});
    }

    const tempPath = path.join(UPLOAD_DIR, tempId);
    if (!fs.existsSync(tempPath)) throw new HttpError(404, 'Uploaded file expired');

    for (const target of def.requiredTargets) {
      if (!mapping[target] && defaults[target] === undefined) {
        throw new HttpError(400, `Missing mapping for required field: ${target}`);
      }
    }

    // Déterminer la source selon l'extension du fichier original (passé en body optionnel)
    const origName  = req.body.originalFilename || '';
    const isJson    = path.extname(origName).toLowerCase() === '.json';
    const srcLabel  = isJson ? 'import_json' : 'import_excel';

    const { rows } = parseFile(tempPath, origName || 'file.xlsx');

    let inserted = 0, skipped = 0;
    const errors = [];

    const tx = db.transaction(() => {
      for (let idx = 0; idx < rows.length; idx++) {
        const row = rows[idx];
        try {
          const v = (target) => {
            const col = mapping[target];
            if (col && row[col] !== undefined && row[col] !== null && row[col] !== '') {
              return row[col];
            }
            return defaults[target];
          };

          if (module === 'depots_retraits') {
            db.prepare(`
              INSERT INTO depots_retraits
                (investisseur_id, plateforme_id, date_operation, type, montant, libelle, reference, source)
              VALUES (?,?,?,?,?,?,?,?)
            `).run(
              req.investisseur.id,
              Number(v('plateforme_id')),
              normaliseDate(v('date_operation')),
              normaliseType(v('type')),
              num(v('montant')),
              v('libelle') || null,
              v('reference') || null,
              srcLabel,
            );

          } else if (module === 'investissements') {
            db.prepare(`
              INSERT INTO investissements
                (investisseur_id, plateforme_id, nom_projet, emetteur, date_souscription,
                 date_premiere_echeance, date_cible, montant_investi, taux_interet, duree_mois,
                 type_remb, freq_interets, statut, reference, source)
              VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
            `).run(
              req.investisseur.id,
              Number(v('plateforme_id')),
              String(v('nom_projet')),
              v('emetteur') || null,
              normaliseDate(v('date_souscription')),
              v('date_premiere_echeance') ? normaliseDate(v('date_premiere_echeance')) : null,
              v('date_cible') ? normaliseDate(v('date_cible')) : null,
              num(v('montant_investi')),
              v('taux_interet') ? Number(String(v('taux_interet')).replace(',', '.')) : null,
              v('duree_mois') ? parseInt(v('duree_mois'), 10) : null,
              v('type_remb') || null,
              v('freq_interets') || 'mensuel',
              v('statut') || 'en_cours',
              v('reference') || null,
              srcLabel,
            );

          } else if (module === 'remboursements') {
            const capital     = num(v('capital'));
            const cashback    = num(v('cashback'));
            const bruts       = num(v('interets_bruts'));
            const ps          = num(v('prelev_sociaux'));
            const pf          = num(v('prelev_forfaitaire'));
            const interets_nets = Math.round((bruts - ps - pf) * 100) / 100;
            const net_recu      = Math.round((capital + cashback + interets_nets) * 100) / 100;
            db.prepare(`
              INSERT INTO remboursements
                (investissement_id, date_remb, capital, cashback, interets_bruts, prelev_sociaux,
                 prelev_forfaitaire, interets_nets, net_recu, statut, source)
              VALUES (?,?,?,?,?,?,?,?,?,?,?)
            `).run(
              Number(v('investissement_id')),
              normaliseDate(v('date_remb')),
              capital, cashback, bruts, ps, pf, interets_nets, net_recu,
              v('statut') || 'paye',
              srcLabel,
            );

          } else if (module === 'plateformes') {
            const nom = String(v('nom') || '').trim();
            if (!nom) throw new Error('Le champ nom est vide');
            const r = db.prepare(`
              INSERT OR IGNORE INTO plateformes (user_id, nom, url, notes)
              VALUES (?, ?, ?, ?)
            `).run(
              req.user.id,
              nom,
              v('url') || null,
              v('notes') || null,
            );
            // changes = 0 means the row was ignored (nom already exists)
            if (r.changes === 0) throw new Error(`Plateforme "${nom}" existe déjà — ignorée`);

          } else if (module === 'taux_pfu') {
            const annee = parseInt(v('annee'), 10);
            if (!annee || annee < 2000 || annee > 2100) throw new Error('Année invalide');
            db.prepare(`
              INSERT INTO taux_pfu (annee, pfu_total, impot_revenu, prelev_sociaux)
              VALUES (?, ?, ?, ?)
              ON CONFLICT(annee) DO UPDATE SET
                pfu_total      = excluded.pfu_total,
                impot_revenu   = excluded.impot_revenu,
                prelev_sociaux = excluded.prelev_sociaux,
                updated_at     = datetime('now')
            `).run(
              annee,
              num(v('pfu_total')),
              num(v('impot_revenu')),
              num(v('prelev_sociaux')),
            );
          }

          inserted++;
        } catch (err) {
          skipped++;
          errors.push({ row: idx + 2, error: err.message });
        }
      }
    });
    tx();

    db.prepare(`
      INSERT INTO imports (user_id, investisseur_id, module, filename, rows_total, rows_inserted, rows_skipped, mapping_json)
      VALUES (?,?,?,?,?,?,?,?)
    `).run(
      req.user.id,
      req.investisseur?.id ?? null,
      module,
      tempId,
      rows.length,
      inserted,
      skipped,
      JSON.stringify(mapping),
    );

    // Clean up temp file
    try { fs.unlinkSync(tempPath); } catch { /* */ }

    res.json({ inserted, skipped, total: rows.length, errors: errors.slice(0, 50) });
  } catch (e) { next(e); }
});

/**
 * POST /api/imports/dossier
 * Importe un dossier investissement complet (format d'export natif).
 * Scénario CREATE : le dossier n'existe pas → création complète.
 * Scénario UPDATE : le dossier existe déjà → mise à jour des champs + remboursements manquants.
 * Identification : (investisseur_id, nom_projet, date_souscription) — clé naturelle portable.
 */
router.post('/dossier', (req, res, next) => {
  try {
    requireInvestisseur(req, res, () => {});

    const { dossier } = req.body || {};
    if (!dossier || dossier.type !== 'dossier_investissement') {
      throw new HttpError(400, 'Format invalide — attendu { dossier: { type: "dossier_investissement", ... } }');
    }
    const { investissement: inv, plateforme: platInfo, remboursements = [], reinvestissements = [], historique = [] } = dossier;
    if (!inv?.nom_projet || !inv?.date_souscription) {
      throw new HttpError(400, 'Champs obligatoires manquants : nom_projet, date_souscription');
    }

    let action, investissementId;

    const tx = db.transaction(() => {
      /* ── 1. Résoudre / créer la plateforme ─────────────────── */
      let platRow = db.prepare('SELECT id FROM plateformes WHERE user_id = ? AND nom = ?')
        .get(req.user.id, platInfo?.nom || '');
      if (!platRow && platInfo?.nom) {
        const r = db.prepare('INSERT INTO plateformes (user_id, nom, url) VALUES (?,?,?)')
          .run(req.user.id, platInfo.nom, platInfo.url || null);
        platRow = { id: r.lastInsertRowid };
      }
      if (!platRow) throw new HttpError(400, 'Plateforme introuvable et nom manquant dans le dossier');
      const plateformeId = platRow.id;

      /* ── 2. Chercher l'investissement existant (clé naturelle) */
      const existing = db.prepare(`
        SELECT id FROM investissements
        WHERE investisseur_id = ? AND nom_projet = ? AND date_souscription = ?
        LIMIT 1
      `).get(req.investisseur.id, inv.nom_projet, inv.date_souscription);

      if (!existing) {
        /* ────────────── SCÉNARIO CREATE ──────────────────────── */
        const r = db.prepare(`
          INSERT INTO investissements
            (investisseur_id, plateforme_id, nom_projet, emetteur, date_souscription,
             date_premiere_echeance, date_cible, date_debut_simul, montant_investi,
             taux_interet, duree_mois, type_remb, freq_interets, statut, reference, source, notes)
          VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, 'import_dossier', ?)
        `).run(
          req.investisseur.id, plateformeId,
          inv.nom_projet, inv.emetteur || null,
          inv.date_souscription,
          inv.date_premiere_echeance || null, inv.date_cible || null, inv.date_debut_simul || null,
          Number(inv.montant_investi), inv.taux_interet ?? null, inv.duree_mois ?? null,
          inv.type_remb || 'in_fine', inv.freq_interets || 'mensuel',
          inv.statut || 'en_cours', inv.reference || null, inv.notes || null,
        );
        investissementId = Number(r.lastInsertRowid);

        // Remboursements
        for (const rb of remboursements) {
          db.prepare(`
            INSERT INTO remboursements
              (investissement_id, date_remb, capital, cashback, interets_bruts,
               prelev_sociaux, prelev_forfaitaire, interets_nets, net_recu, statut, notes, source)
            VALUES (?,?,?,?,?,?,?,?,?,?,?, 'import_dossier')
          `).run(
            investissementId, rb.date_remb,
            rb.capital || 0, rb.cashback || 0, rb.interets_bruts || 0,
            rb.prelev_sociaux || 0, rb.prelev_forfaitaire || 0,
            rb.interets_nets || 0, rb.net_recu || 0,
            rb.statut || 'paye', rb.notes || null,
          );
        }

        // Réinvestissements
        for (const rv of reinvestissements) {
          if (!rv.date_reinvestissement || !rv.montant) continue;
          db.prepare(`
            INSERT INTO reinvestissements (investissement_id, montant, date_reinvestissement, note, source)
            VALUES (?, ?, ?, ?, ?)
          `).run(
            investissementId,
            Number(rv.montant),
            rv.date_reinvestissement,
            rv.note || null,
            rv.source || 'manuel',
          );
        }

        // Historique (lecture seule — on importe pour la traçabilité)
        for (const h of historique) {
          db.prepare(`
            INSERT INTO investissement_historique
              (investissement_id, type_evenement, changements, notes, created_at)
            VALUES (?,?,?,?,?)
          `).run(
            investissementId,
            h.type_evenement || 'import',
            typeof h.changements === 'string' ? h.changements : JSON.stringify(h.changements || []),
            h.notes || null,
            h.created_at || null,
          );
        }
        // Entrée d'historique de l'import lui-même
        db.prepare(`
          INSERT INTO investissement_historique (investissement_id, type_evenement, changements)
          VALUES (?, 'import', ?)
        `).run(investissementId, JSON.stringify([{
          champ: 'import', label: 'Import dossier',
          ancienne_valeur: null, nouvelle_valeur: dossier.exported_at || 'inconnu',
        }]));

        action = 'created';
      } else {
        /* ────────────── SCÉNARIO UPDATE ──────────────────────── */
        investissementId = existing.id;
        db.prepare(`
          UPDATE investissements SET
            plateforme_id = ?, emetteur = ?,
            date_premiere_echeance = ?, date_cible = ?, date_debut_simul = ?,
            montant_investi = ?, taux_interet = ?, duree_mois = ?,
            type_remb = ?, freq_interets = ?, statut = ?,
            reference = ?, notes = ?
          WHERE id = ?
        `).run(
          plateformeId, inv.emetteur || null,
          inv.date_premiere_echeance || null, inv.date_cible || null, inv.date_debut_simul || null,
          Number(inv.montant_investi), inv.taux_interet ?? null, inv.duree_mois ?? null,
          inv.type_remb || 'in_fine', inv.freq_interets || 'mensuel',
          inv.statut || 'en_cours',
          inv.reference || null, inv.notes || null,
          investissementId,
        );

        // Remboursements : ajouter les manquants (clé naturelle = date_remb + capital + interets_bruts)
        let rembInserted = 0;
        for (const rb of remboursements) {
          const exists = db.prepare(`
            SELECT id FROM remboursements
            WHERE investissement_id = ? AND date_remb = ?
              AND ABS(capital - ?) < 0.005 AND ABS(interets_bruts - ?) < 0.005
          `).get(investissementId, rb.date_remb, rb.capital || 0, rb.interets_bruts || 0);
          if (!exists) {
            db.prepare(`
              INSERT INTO remboursements
                (investissement_id, date_remb, capital, cashback, interets_bruts,
                 prelev_sociaux, prelev_forfaitaire, interets_nets, net_recu, statut, notes, source)
              VALUES (?,?,?,?,?,?,?,?,?,?,?, 'import_dossier')
            `).run(
              investissementId, rb.date_remb,
              rb.capital || 0, rb.cashback || 0, rb.interets_bruts || 0,
              rb.prelev_sociaux || 0, rb.prelev_forfaitaire || 0,
              rb.interets_nets || 0, rb.net_recu || 0,
              rb.statut || 'paye', rb.notes || null,
            );
            rembInserted++;
          }
        }

        // Réinvestissements : ajouter les manquants (clé = date + montant)
        let reinvInserted = 0;
        for (const rv of reinvestissements) {
          if (!rv.date_reinvestissement || !rv.montant) continue;
          const rvExists = db.prepare(`
            SELECT id FROM reinvestissements
            WHERE investissement_id = ? AND date_reinvestissement = ? AND ABS(montant - ?) < 0.005
          `).get(investissementId, rv.date_reinvestissement, Number(rv.montant));
          if (!rvExists) {
            db.prepare(`
              INSERT INTO reinvestissements (investissement_id, montant, date_reinvestissement, note, source)
              VALUES (?, ?, ?, ?, ?)
            `).run(
              investissementId,
              Number(rv.montant),
              rv.date_reinvestissement,
              rv.note || null,
              rv.source || 'manuel',
            );
            reinvInserted++;
          }
        }

        // Entrée d'historique de la mise à jour
        db.prepare(`
          INSERT INTO investissement_historique (investissement_id, type_evenement, changements)
          VALUES (?, 'import', ?)
        `).run(investissementId, JSON.stringify([{
          champ: 'import', label: 'Mise à jour dossier',
          ancienne_valeur: null,
          nouvelle_valeur: `${dossier.exported_at || 'inconnu'} — ${rembInserted} remb. ajouté(s), ${reinvInserted} réinvest. ajouté(s)`,
        }]));

        action = 'updated';
      }
    });
    tx();

    /* ── 3. Régénérer la simulation ─────────────────────────── */
    const fresh = db.prepare('SELECT * FROM investissements WHERE id = ?').get(investissementId);
    generateSimulWithReinvestissements(db, investissementId);

    /* ── 4. Log import ──────────────────────────────────────── */
    db.prepare(`
      INSERT INTO imports (user_id, investisseur_id, module, filename, rows_total, rows_inserted, rows_skipped, mapping_json)
      VALUES (?,?,?,?,?,?,?,?)
    `).run(
      req.user.id, req.investisseur.id,
      'dossier_investissement',
      `Dossier_${inv.nom_projet}`,
      1, 1, 0,
      JSON.stringify({ action, investissementId }),
    );

    res.json({ action, investissementId });
  } catch (e) { next(e); }
});

router.get('/history', (req, res) => {
  const rows = db.prepare(`
    SELECT * FROM imports WHERE user_id=? ORDER BY id DESC LIMIT 100
  `).all(req.user.id);
  res.json(rows);
});

// helpers

/** Supprime les accents/diacritiques d'une chaîne (ex. "Dépôt" → "Depot") */
function stripAccents(s) {
  return String(s).normalize('NFD').replace(/[̀-ͯ]/g, '');
}

/** Convertit une valeur monétaire en nombre (gère "€", espaces, virgule décimale) */
function num(v) {
  if (v === undefined || v === null || v === '') return 0;
  // Supprimer tout ce qui n'est pas chiffre, virgule, point ou signe moins
  const clean = String(v).replace(/[^\d,.-]/g, '').replace(',', '.');
  const n = Number(clean);
  return isNaN(n) ? 0 : n;
}

function normaliseType(v) {
  // Normalise les accents avant la comparaison : "Dépôt" → "depot"
  const s = stripAccents(String(v || '')).toLowerCase();
  if (s.startsWith('dep') || s === 'versement' || s === 'in') return 'depot';
  if (s.startsWith('ret') || s === 'withdrawal' || s === 'out') return 'retrait';
  return s; // CHECK constraint will reject if invalid
}
function normaliseDate(v) {
  if (!v) return null;
  if (v instanceof Date) return v.toISOString().slice(0, 10);
  const s = String(v).trim();
  // Already ISO
  if (/^\d{4}-\d{2}-\d{2}/.test(s)) return s.slice(0, 10);
  // dd/mm/yyyy
  const m = s.match(/^(\d{1,2})[/.-](\d{1,2})[/.-](\d{2,4})$/);
  if (m) {
    const [_, d, mo, y] = m;
    const yy = y.length === 2 ? '20' + y : y;
    return `${yy}-${mo.padStart(2, '0')}-${d.padStart(2, '0')}`;
  }
  return s;
}

export default router;
