/**
 * Shared schedule builder — used by both simul.js and investissements.js
 */

function addMonths(isoDate, months) {
  const [y, m, d] = isoDate.split('-').map(Number);
  const dt = new Date(Date.UTC(y, m - 1 + months, d));
  return dt.toISOString().slice(0, 10);
}

/**
 * Retourne le dernier jour du mois de isoDate.
 * Ex : '2024-01-15' → '2024-01-31', '2024-02-01' → '2024-02-29'
 */
function lastDayOfMonth(isoDate) {
  const [y, m] = isoDate.split('-').map(Number);
  const dt = new Date(Date.UTC(y, m, 0)); // jour 0 du mois suivant = dernier jour du mois courant
  return dt.toISOString().slice(0, 10);
}

/**
 * Ajoute n mois à isoDate et positionne le résultat sur le dernier jour du mois cible.
 * Évite les débordements de JavaScript (ex : 31 jan + 1 mois = 29 fév, pas 3 mar).
 * Ex : addMonthsEOM('2024-01-31', 1) → '2024-02-29'
 */
function addMonthsEOM(isoDate, months) {
  const [y, m] = isoDate.split('-').map(Number);
  const dt = new Date(Date.UTC(y, m - 1 + months + 1, 0)); // jour 0 = dernier du mois cible
  return dt.toISOString().slice(0, 10);
}

const round2 = n => Math.round(n * 100) / 100;

/**
 * Builds a payment schedule.
 *
 * type:
 *   - in_fine     : intérêts périodiques selon freq, capital à l'échéance
 *   - amortissable: mensualité/trimestrialité constante (modèle bancaire)
 *   - differe     : aucun versement intermédiaire, tout en une fois à l'échéance
 *
 * freq:
 *   - mensuel     : une échéance par mois  (offset i-1)
 *   - trimestriel : une échéance tous les 3 mois
 *   - in_fine     : forcé pour differe
 *
 * startDate = date_premiere_echeance (1ère échéance tombe sur cette date, offset 0)
 */
export function buildSchedule({ montant, taux, duree, type, freq, startDate, finDeMois = false }) {
  const step = freq === 'trimestriel' ? 3 : 1;
  const rPer = (taux / 100 / 12) * step;
  const nPer = freq === 'in_fine' ? 1 : Math.round(duree / step);
  const out  = [];

  // Calcule la date d'une échéance selon le mode fin-de-mois ou non.
  const dateAt = (base, offsetMonths) =>
    finDeMois ? addMonthsEOM(base, offsetMonths) : addMonths(base, offsetMonths);

  if (type === 'differe') {
    const interets = round2(montant * (taux / 100 / 12) * duree);
    // Pour un prêt différé, date_premiere_echeance == date_cible (versement unique).
    // startDate est déjà la bonne date ; en mode fin-de-mois on s'assure qu'elle
    // correspond bien au dernier jour (idempotent si déjà positionné par le frontend).
    out.push({
      n: 1,
      date: finDeMois ? lastDayOfMonth(startDate) : startDate,
      capital: round2(montant),
      interets,
      total: round2(montant + interets),
    });

  } else if (type === 'in_fine') {
    const interetsPer = round2(montant * rPer);
    for (let i = 1; i <= nPer; i++) {
      const isLast = i === nPer;
      out.push({
        n: i,
        date: dateAt(startDate, (i - 1) * step),
        capital: isLast ? round2(montant) : 0,
        interets: interetsPer,
        total: round2(interetsPer + (isLast ? montant : 0)),
      });
    }

  } else {
    // amortissable
    const a = rPer === 0
      ? montant / nPer
      : montant * rPer / (1 - Math.pow(1 + rPer, -nPer));
    let restant = montant;
    for (let i = 1; i <= nPer; i++) {
      const interets = round2(restant * rPer);
      const capital  = round2(a - interets);
      restant = round2(restant - capital);
      out.push({
        n: i,
        date: dateAt(startDate, (i - 1) * step),
        capital,
        interets,
        total: round2(a),
      });
    }
  }

  return out;
}

/** Nombre de mois entiers entre deux dates ISO */
function monthsDiff(isoA, isoB) {
  const a = new Date(isoA);
  const b = new Date(isoB);
  return (b.getFullYear() - a.getFullYear()) * 12 + (b.getMonth() - a.getMonth());
}

/**
 * Recalcule les simul_remboursements FUTURS en tenant compte des remboursements
 * de capital déjà enregistrés (remboursement anticipé partiel) ET des réinvestissements.
 *
 * - Les entrées dont date_prevue <= dernière date de remboursement capital sont laissées intactes.
 * - Les entrées suivantes sont recalculées sur la base du capital restant dû.
 * - Si aucun capital n'a encore été remboursé → régénération complète.
 * - Les réinvestissements planifiés après la dernière date de remboursement sont pris en compte
 *   pour gonfler progressivement le capital des périodes futures.
 */
export function adjustSimulForActuals(db, investissementId) {
  const inv = db.prepare(`
    SELECT id, montant_investi, taux_interet, duree_mois, type_remb, freq_interets,
           date_premiere_echeance, date_debut_simul, date_souscription, echeance_fin_de_mois
    FROM investissements WHERE id = ?
  `).get(investissementId);

  if (!inv || !inv.taux_interet || !inv.duree_mois) return;

  // Réinvestissements triés par date (peuvent être vides)
  const reinvests = db.prepare(
    'SELECT montant, date_reinvestissement FROM reinvestissements WHERE investissement_id = ? ORDER BY date_reinvestissement'
  ).all(investissementId);

  const { total_capital } = db.prepare(
    'SELECT COALESCE(SUM(capital), 0) AS total_capital FROM remboursements WHERE investissement_id = ?'
  ).get(investissementId);

  // Aucun capital remboursé → régénération complète (avec réinvestissements si présents)
  if (total_capital <= 0) {
    if (reinvests.length) {
      generateSimulWithReinvestissements(db, investissementId);
    } else {
      generateSimul(db, inv);
    }
    adjustFirstPartialPeriod(db, investissementId);
    return;
  }

  // Date du dernier remboursement comportant du capital
  const { last_date } = db.prepare(
    'SELECT MAX(date_remb) AS last_date FROM remboursements WHERE investissement_id = ? AND capital > 0'
  ).get(investissementId);

  // Capital effectivement investi jusqu'à la date du dernier remboursement
  // (initial + réinvestissements survenus avant ou à cette date)
  const capitalAtLastDate = round2(
    inv.montant_investi +
    reinvests.filter(r => r.date_reinvestissement <= last_date).reduce((s, r) => s + r.montant, 0)
  );

  const remainingCapital = round2(capitalAtLastDate - total_capital);

  // Entrées de simulation à recalculer (strictement après la date du dernier remb capital)
  const futureEntries = db.prepare(`
    SELECT * FROM simul_remboursements
    WHERE investissement_id = ? AND date_prevue > ?
    ORDER BY numero_echeance
  `).all(investissementId, last_date);

  if (futureEntries.length === 0) return;

  // Capital restant soldé → on met tout à zéro, et on porte le capital sur l'échéance courante
  if (remainingCapital <= 0) {
    // Entrée simul qui couvre la période du dernier remboursement capital
    const lastPaidEntry = db.prepare(`
      SELECT id, interets_prevus, capital_prevu
      FROM simul_remboursements
      WHERE investissement_id = ? AND date_prevue <= ?
      ORDER BY date_prevue DESC
      LIMIT 1
    `).get(investissementId, last_date);

    db.transaction(() => {
      // Supprimer les échéances futures devenues caduques
      const stmtDel = db.prepare('DELETE FROM simul_remboursements WHERE id=?');
      for (const e of futureEntries) stmtDel.run(e.id);

      // Mettre à jour l'échéance courante pour qu'elle reflète le capital soldé
      if (lastPaidEntry) {
        db.prepare(`
          UPDATE simul_remboursements
          SET capital_prevu=?, total_prevu=?
          WHERE id=?
        `).run(
          capitalAtLastDate,
          round2(capitalAtLastDate + lastPaidEntry.interets_prevus),
          lastPaidEntry.id,
        );
      }
    })();
    return;
  }

  const step    = inv.freq_interets === 'trimestriel' ? 3 : 1;
  const rPer    = (inv.taux_interet / 100 / 12) * step;
  const nFuture = futureEntries.length;
  const type    = inv.type_remb || 'in_fine';

  // Réinvestissements encore à venir (après la date du dernier remboursement)
  const futureReinvests = reinvests.filter(r => r.date_reinvestissement > last_date);

  const updates = [];

  if (type === 'differe') {
    // Versement unique : recalcul sur capital restant + réinvestissements futurs
    const entry       = futureEntries[0];
    const extraCapital = futureReinvests
      .filter(r => r.date_reinvestissement <= entry.date_prevue)
      .reduce((s, r) => s + r.montant, 0);
    const totalCap    = round2(remainingCapital + extraCapital);
    const moisRestants = monthsDiff(last_date, entry.date_prevue);
    const interets    = round2(totalCap * (inv.taux_interet / 100 / 12) * moisRestants);
    updates.push({ id: entry.id, capital: totalCap, interets, total: round2(totalCap + interets) });

  } else if (type === 'in_fine') {
    // Intérêts sur capital courant (augmente à chaque réinvestissement futur)
    let capital     = remainingCapital;
    let reinvestIdx = 0;
    futureEntries.forEach((entry, i) => {
      while (reinvestIdx < futureReinvests.length &&
             futureReinvests[reinvestIdx].date_reinvestissement <= entry.date_prevue) {
        capital += futureReinvests[reinvestIdx].montant;
        reinvestIdx++;
      }
      const isLast  = i === nFuture - 1;
      const interets = round2(capital * rPer);
      updates.push({
        id:      entry.id,
        capital: isLast ? capital : 0,
        interets,
        total:   round2(interets + (isLast ? capital : 0)),
      });
    });

  } else {
    // Amortissable : recalcul période par période avec capital courant
    let restant     = remainingCapital;
    let reinvestIdx = 0;
    futureEntries.forEach((entry, i) => {
      while (reinvestIdx < futureReinvests.length &&
             futureReinvests[reinvestIdx].date_reinvestissement <= entry.date_prevue) {
        restant += futureReinvests[reinvestIdx].montant;
        reinvestIdx++;
      }
      const periodsLeft = nFuture - i;
      const a = rPer === 0
        ? restant / periodsLeft
        : restant * rPer / (1 - Math.pow(1 + rPer, -periodsLeft));
      const interets = round2(restant * rPer);
      const capital  = round2(a - interets);
      restant        = round2(restant - capital);
      updates.push({ id: entry.id, capital, interets, total: round2(a) });
    });
  }

  db.transaction(() => {
    const stmt = db.prepare(`
      UPDATE simul_remboursements
      SET capital_prevu = ?, interets_prevus = ?, total_prevu = ?
      WHERE id = ?
    `);
    for (const u of updates) stmt.run(u.capital, u.interets, u.total, u.id);
  })();

  // Ajustement de la première période partielle (mois incomplet)
  adjustFirstPartialPeriod(db, investissementId);
}

/**
 * Détecte et corrige la première période partielle (mois incomplet au démarrage du prêt).
 *
 * Pour les prêts in_fine et amortissables, la première échéance avec intérêts correspond
 * parfois à un mois incomplet (souscription en milieu de mois). Si le premier remboursement
 * réel avec intérêts est inférieur au montant simulé, on met à jour la ligne simul
 * correspondante avec la valeur réelle et on reporte la différence sur la dernière échéance.
 *
 * L'ajustement est idempotent : si les valeurs sont déjà cohérentes, rien n'est modifié.
 */
function adjustFirstPartialPeriod(db, investissementId) {
  const inv = db.prepare(
    'SELECT type_remb FROM investissements WHERE id = ?'
  ).get(investissementId);

  if (!inv || inv.type_remb === 'differe') return;

  // Premier remboursement enregistré avec des intérêts (exclure cashback-only)
  const firstRemb = db.prepare(`
    SELECT date_remb, interets_bruts
    FROM remboursements
    WHERE investissement_id = ? AND interets_bruts > 0
    ORDER BY date_remb ASC
    LIMIT 1
  `).get(investissementId);

  if (!firstRemb) return;

  const firstRembMonth = firstRemb.date_remb.slice(0, 7); // YYYY-MM

  // Entrée simul du même mois YYYY-MM
  const simulEntry = db.prepare(`
    SELECT id, interets_prevus, capital_prevu, total_prevu
    FROM simul_remboursements
    WHERE investissement_id = ? AND substr(date_prevue, 1, 7) = ?
  `).get(investissementId, firstRembMonth);

  if (!simulEntry) return;

  const diff = round2(simulEntry.interets_prevus - firstRemb.interets_bruts);
  if (diff <= 0.001) return; // Pas d'écart significatif, aucune correction nécessaire

  // Dernière échéance simul (celle qui absorbera la différence)
  const lastEntry = db.prepare(`
    SELECT id, interets_prevus, capital_prevu, total_prevu
    FROM simul_remboursements
    WHERE investissement_id = ?
    ORDER BY numero_echeance DESC
    LIMIT 1
  `).get(investissementId);

  if (!lastEntry || lastEntry.id === simulEntry.id) return;

  const newFirstInterets = round2(firstRemb.interets_bruts);
  const newFirstTotal    = round2(simulEntry.capital_prevu + newFirstInterets);
  const newLastInterets  = round2(lastEntry.interets_prevus + diff);
  const newLastTotal     = round2(lastEntry.capital_prevu + newLastInterets);

  db.transaction(() => {
    db.prepare(
      'UPDATE simul_remboursements SET interets_prevus=?, total_prevu=? WHERE id=?'
    ).run(newFirstInterets, newFirstTotal, simulEntry.id);
    db.prepare(
      'UPDATE simul_remboursements SET interets_prevus=?, total_prevu=? WHERE id=?'
    ).run(newLastInterets, newLastTotal, lastEntry.id);
  })();
}

/**
 * Génère (ou régénère) le tableau d'amortissement en tenant compte des réinvestissements.
 *
 * Pour chaque période, le capital pris en compte est :
 *   montant_investi + SUM(reinvestissements dont date <= date_période)
 *
 * - in_fine    : intérêts recalculés sur le capital cumulé à chaque période,
 *                capital final = capital total cumulé
 * - differe    : versement unique recalculé sur le capital total à l'échéance
 * - amortissable : intérêts de base + quote-part du réinvestissement sur les périodes restantes
 */
export function generateSimulWithReinvestissements(db, investissementId) {
  const inv = db.prepare(`
    SELECT id, montant_investi, taux_interet, duree_mois, type_remb, freq_interets,
           date_premiere_echeance, date_debut_simul, date_souscription, echeance_fin_de_mois
    FROM investissements WHERE id = ?
  `).get(investissementId);

  if (!inv) return;

  const reinvests = db.prepare(
    'SELECT montant, date_reinvestissement FROM reinvestissements WHERE investissement_id = ? ORDER BY date_reinvestissement'
  ).all(investissementId);

  // Pas de réinvestissement → génération standard
  if (!reinvests.length) {
    generateSimul(db, inv);
    return;
  }

  if (!inv.taux_interet || !inv.duree_mois) return;

  const startDate = inv.date_debut_simul || inv.date_premiere_echeance || inv.date_souscription;
  if (!startDate) return;

  const finDeMois  = !!inv.echeance_fin_de_mois;
  const type       = inv.type_remb || 'in_fine';
  const freq       = inv.freq_interets || 'mensuel';
  const step       = freq === 'trimestriel' ? 3 : 1;
  const rPer       = (inv.taux_interet / 100 / 12) * step;

  // Durée effective (tient compte d'une éventuelle restructuration)
  let effectiveDuree = inv.duree_mois;
  if (inv.date_debut_simul && inv.date_premiere_echeance && inv.date_debut_simul > inv.date_premiere_echeance) {
    const elapsed = monthsDiff(inv.date_premiere_echeance, inv.date_debut_simul);
    effectiveDuree = Math.max(1, inv.duree_mois - elapsed);
  }

  // Calendrier de base pour obtenir les dates de chaque échéance
  const baseSchedule = buildSchedule({
    montant:   inv.montant_investi,
    taux:      inv.taux_interet,
    duree:     effectiveDuree,
    type, freq, startDate, finDeMois,
  });

  let capital      = inv.montant_investi;
  let reinvestIdx  = 0;
  const schedule   = [];

  // Pour amortissable : on garde la structure du capital initial et ajoute l'impact
  // du réinvestissement sur les périodes restantes (quote-part d'intérêts supplémentaires).
  // Indexes des réinvestissements non encore traités dans le plan amorti.
  let pendingReinvests = [];

  for (let i = 0; i < baseSchedule.length; i++) {
    const entry = baseSchedule[i];

    // Réinvestissements dont la date tombe avant (ou sur) cette échéance
    while (reinvestIdx < reinvests.length &&
           reinvests[reinvestIdx].date_reinvestissement <= entry.date) {
      const r = reinvests[reinvestIdx];
      capital += r.montant;
      // Pour amortissable : mémoriser le montant réinvesti et le nb de périodes restantes
      if (type === 'amortissable') {
        pendingReinvests.push({ montant: r.montant, remainingPeriods: baseSchedule.length - i });
      }
      reinvestIdx++;
    }

    const isLast = i === baseSchedule.length - 1;

    let entryCapital, entryInterets, entryTotal;

    if (type === 'in_fine') {
      entryInterets = round2(capital * rPer);
      entryCapital  = isLast ? capital : 0;
      entryTotal    = round2(entryInterets + entryCapital);

    } else if (type === 'differe') {
      // Versement unique : intérêts recalculés sur le capital total depuis le début
      const totalMonths = inv.duree_mois;
      entryInterets = round2(capital * (inv.taux_interet / 100 / 12) * totalMonths);
      entryCapital  = capital;
      entryTotal    = round2(entryCapital + entryInterets);

    } else {
      // Amortissable : intérêts de base + quote-part des réinvestissements
      const baseInterets = entry.interets;
      const extraInterets = pendingReinvests.reduce((sum, pr) => {
        return sum + round2(pr.montant * rPer);
      }, 0);
      entryInterets = round2(baseInterets + extraInterets);
      entryCapital  = entry.capital;
      entryTotal    = round2(entryCapital + entryInterets);
    }

    schedule.push({ n: entry.n, date: entry.date, capital: entryCapital, interets: entryInterets, total: entryTotal });
  }

  db.transaction(() => {
    db.prepare('DELETE FROM simul_remboursements WHERE investissement_id=?').run(investissementId);
    const stmt = db.prepare(`
      INSERT INTO simul_remboursements
        (investissement_id, numero_echeance, date_prevue, capital_prevu, interets_prevus, total_prevu)
      VALUES (?,?,?,?,?,?)
    `);
    for (const e of schedule) {
      stmt.run(investissementId, e.n, e.date, e.capital, e.interets, e.total);
    }
  })();
}

/**
 * Génère (ou régénère) le tableau d'amortissement d'un investissement dans la DB.
 * Ne fait rien si taux_interet ou duree_mois est absent.
 *
 * Si date_debut_simul est renseigné (restructuration de prêt), la simulation
 * démarre à cette date et la durée effective est réduite du nombre de mois déjà
 * écoulés depuis date_premiere_echeance, afin de ne pas allonger artificiellement le prêt.
 */
export function generateSimul(db, inv) {
  const { id, montant_investi, taux_interet, duree_mois, type_remb, freq_interets,
          date_premiere_echeance, date_debut_simul, date_souscription, echeance_fin_de_mois } = inv;

  if (!taux_interet || !duree_mois) return;

  // date_debut_simul remplace le point de départ quand le prêt a été restructuré
  const startDate = date_debut_simul || date_premiere_echeance || date_souscription;
  if (!startDate) return;

  // Durée effective : si restructuration, on soustrait les mois déjà écoulés
  // pour que la simulation se termine bien à la date cible contractuelle d'origine.
  let effectiveDuree = duree_mois;
  if (date_debut_simul && date_premiere_echeance && date_debut_simul > date_premiere_echeance) {
    const elapsed = monthsDiff(date_premiere_echeance, date_debut_simul);
    effectiveDuree = Math.max(1, duree_mois - elapsed);
  }

  const echeances = buildSchedule({
    montant:   montant_investi,
    taux:      taux_interet,
    duree:     effectiveDuree,
    type:      type_remb  || 'in_fine',
    freq:      freq_interets || 'mensuel',
    startDate,
    finDeMois: !!echeance_fin_de_mois,
  });

  const tx = db.transaction(() => {
    if (date_debut_simul) {
      // ── Mode restructuration ──────────────────────────────────────────────
      // Conserver uniquement les échéances déjà payées avant la date de restructuration
      // (correspondance par mois YYYY-MM avec les remboursements réels enregistrés).
      // Les échéances de la période creuse (non payées entre fin de la phase initiale
      // et date_debut_simul) sont supprimées avec tout ce qui suit.
      const keptEntries = db.prepare(`
        SELECT sr.id, sr.numero_echeance FROM simul_remboursements sr
        WHERE sr.investissement_id = ?
          AND sr.date_prevue < ?
          AND EXISTS (
            SELECT 1 FROM remboursements r
            WHERE r.investissement_id = sr.investissement_id
              AND substr(r.date_remb, 1, 7) = substr(sr.date_prevue, 1, 7)
          )
        ORDER BY sr.numero_echeance
      `).all(id, date_debut_simul);

      if (keptEntries.length > 0) {
        // Supprime tout sauf les entrées payées conservées
        db.prepare(
          `DELETE FROM simul_remboursements WHERE investissement_id = ? AND id NOT IN (${keptEntries.map(() => '?').join(',')})`
        ).run(id, ...keptEntries.map(e => e.id));
      } else {
        // Rien à conserver → suppression totale
        db.prepare('DELETE FROM simul_remboursements WHERE investissement_id=?').run(id);
      }

      // Numérotation absolue : position dans le prêt total = mois écoulés depuis la 1ère échéance.
      // Ex : date_premiere_echeance = août 2024, date_debut_simul = juillet 2025
      //      → 11 mois écoulés → nouvelle échéance 1 = n° 12, dernière = n° 48 (sur 48 total).
      // On ne se base PAS sur le nombre d'entrées conservées (qui peut différer si certains
      // paiements in fine ne matchent pas exactement) mais sur le décalage calendaire réel.
      const elapsedMonths = (date_premiere_echeance && date_debut_simul > date_premiere_echeance)
        ? monthsDiff(date_premiere_echeance, date_debut_simul)
        : keptEntries.length; // fallback : nombre de lignes conservées

      const stmt = db.prepare(`
        INSERT INTO simul_remboursements
          (investissement_id, numero_echeance, date_prevue, capital_prevu, interets_prevus, total_prevu)
        VALUES (?,?,?,?,?,?)
      `);
      for (const e of echeances) {
        stmt.run(id, elapsedMonths + e.n, e.date, e.capital, e.interets, e.total);
      }

    } else {
      // ── Mode standard : régénération complète ────────────────────────────
      db.prepare('DELETE FROM simul_remboursements WHERE investissement_id=?').run(id);
      const stmt = db.prepare(`
        INSERT INTO simul_remboursements
          (investissement_id, numero_echeance, date_prevue, capital_prevu, interets_prevus, total_prevu)
        VALUES (?,?,?,?,?,?)
      `);
      for (const e of echeances) {
        stmt.run(id, e.n, e.date, e.capital, e.interets, e.total);
      }
    }
  });
  tx();
}
