// Moteur de PRORATA — crée les portions de facture selon le TYPE de chaque ligne.
//   • Récurrent (service / produit / rabais récurrent) → PRORATÉ : montant × jours_restants ÷ jours_période.
//   • Ponctuel / installation / frais unique → PLEIN montant (jamais proraté).
//   • Rabais → suit le type de la ligne qu'il réduit (l'appelant pose `recurring` en conséquence ; montants négatifs OK).
// Mappe le `price_recurr_type` de F (1=récurrent, 0=ponctuel) → lines[].recurring.
// PUR + lecture seule : calcule des lignes proratées, n'écrit AUCUNE facture.
const { json, parseBody, erpFetch } = require('./helpers')

function dayStart (v) { const d = new Date(v); if (isNaN(d)) throw new Error('date invalide: ' + v); d.setHours(0, 0, 0, 0); return d }
function daysInclusive (a, b) { return Math.floor((dayStart(b) - dayStart(a)) / 86400000) + 1 }

const ONE_TIME = new Set(['installation', 'one_time', 'setup', 'ponctuel'])

// Méthode du PLUS GRAND RESTE (cf. gem OSS `lare_round`, comme Lago/Kill Bill par ligne) :
// arrondit chaque montant au cent de sorte que la SOMME des arrondis == arrondi de la somme exacte
// → aucun résidu d'arrondi (ex. un net mensuel de 0 donne bien 0,00 et non 0,01). Gère les négatifs.
function largestRemainderRound (exacts) {
  const cents = exacts.map(v => v * 100)
  const floors = cents.map(c => Math.floor(c))
  let diff = Math.round(cents.reduce((s, c) => s + c, 0)) - floors.reduce((s, c) => s + c, 0)
  const byRemainderDesc = exacts.map((_, i) => i).sort((a, b) => (cents[b] - floors[b]) - (cents[a] - floors[a]))
  const out = floors.slice()
  for (let k = 0; diff > 0 && k < byRemainderDesc.length; k++) { out[byRemainderDesc[k]] += 1; diff-- }
  return out.map(c => c / 100)
}

// { activation_date, period_start, period_end, lines: [{ label, amount, recurring(bool), kind? }] }
function prorate ({ activation_date, period_start, period_end, lines = [] }) {
  const periodDays = Math.max(1, daysInclusive(period_start, period_end))
  const billFrom = dayStart(activation_date) > dayStart(period_start) ? activation_date : period_start
  const billedDays = Math.max(0, Math.min(periodDays, daysInclusive(billFrom, period_end)))
  const ratio = billedDays / periodDays
  // Classer chaque ligne + plein montant
  const norm = (lines || []).map(l => {
    const recurring = !!l.recurring && !ONE_TIME.has(l.kind)
    return { label: l.label || '', kind: l.kind || (recurring ? 'service' : 'one_time'), recurring, full: Math.round((Number(l.amount) || 0) * 100) / 100 }
  })
  // RÉCURRENT : prorata + arrondi « plus grand reste » (la somme des lignes == arrondi de la somme exacte)
  const recIdx = norm.map((l, i) => (l.recurring ? i : -1)).filter(i => i >= 0)
  const allocated = largestRemainderRound(recIdx.map(i => norm[i].full * ratio))
  recIdx.forEach((i, k) => { norm[i].prorated = allocated[k] })
  // PONCTUEL : plein montant (jamais proraté)
  norm.forEach(l => { if (!l.recurring) l.prorated = l.full })
  const out = norm.map(l => ({
    label: l.label,
    kind: l.kind,
    recurring: l.recurring,
    full_amount: l.full,
    factor: l.recurring ? +ratio.toFixed(4) : 1,
    prorated_amount: l.prorated
  }))
  const round2 = n => Math.round(n * 100) / 100
  return {
    period_start, period_end, activation_date,
    period_days: periodDays, billed_days: billedDays, ratio: +ratio.toFixed(4),
    lines: out,
    prorated_total: round2(out.reduce((s, l) => s + l.prorated_amount, 0)),
    full_total: round2(out.reduce((s, l) => s + l.full_amount, 0))
  }
}

const ENC = v => encodeURIComponent(JSON.stringify(v))
const pad2 = n => String(n).padStart(2, '0')
const ymd = d => `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`

// Rassemble les lignes RÉELLES d'activation d'un client (abonnements récurrents + items ponctuels du devis),
// les classe (récurrent vs ponctuel) et calcule la facture consolidée proratée.
// LECTURE SEULE — n'écrit AUCUNE facture (`written:false`). Reproduit la convention de dispatch.js :
// jour d'activation GRATUIT → 1re journée facturée = lendemain ; période = mois de l'activation.
async function buildActivationPreview ({ customer, service_location, quotation, activation_date, statuses }) {
  if (!customer) throw new Error('customer requis')
  const lines = []

  // 1) RÉCURRENT — Service Subscription (monthly_price ; négatif = rabais récurrent, conservé)
  const subFilters = [['customer', '=', customer]]
  if (service_location) subFilters.push(['service_location', '=', service_location])
  if (Array.isArray(statuses) && statuses.length) subFilters.push(['status', 'in', statuses])
  const subRes = await erpFetch(`/api/resource/Service Subscription?filters=${ENC(subFilters)}&fields=${ENC(['name', 'monthly_price', 'plan_name', 'status'])}&limit_page_length=100`)
  const subs = (subRes.status === 200 && Array.isArray(subRes.data?.data)) ? subRes.data.data : []
  for (const s of subs) {
    const amt = Number(s.monthly_price) || 0
    if (!amt) continue
    lines.push({ label: s.plan_name || s.name, amount: amt, recurring: true, kind: amt < 0 ? 'discount' : 'service', source: s.name })
  }

  // 2) PONCTUEL — items du devis SANS "$/mois" (installation, équipement, frais unique). Les items
  //    récurrents du devis sont déjà couverts par les abonnements ci-dessus → sautés (anti-double-compte).
  if (quotation) {
    const qRes = await erpFetch(`/api/resource/Quotation/${encodeURIComponent(quotation)}`)
    const items = (qRes.status === 200 && Array.isArray(qRes.data?.data?.items)) ? qRes.data.data.items : []
    for (const it of items) {
      if (/\$\s*\/\s*mois/i.test(it.description || '')) continue
      const amt = Number(it.amount != null ? it.amount : (Number(it.rate) || 0) * (Number(it.qty) || 1)) || 0
      if (!amt) continue
      const txt = `${it.item_code || ''} ${it.item_name || ''}`.toLowerCase()
      const kind = amt < 0 ? 'discount' : (/install|activ|frais|setup/.test(txt) ? 'installation' : 'one_time')
      lines.push({ label: it.item_name || it.item_code || '', amount: amt, recurring: false, kind, source: quotation })
    }
  }

  // 3) Période = mois de l'activation ; 1re journée facturée = lendemain (jour d'activation gratuit)
  const on = activation_date ? new Date(activation_date + 'T12:00:00') : new Date()
  const y = on.getFullYear(); const m = on.getMonth()
  const dim = new Date(y, m + 1, 0).getDate()
  const firstBilled = new Date(y, m, on.getDate() + 1)
  const result = prorate({ activation_date: ymd(firstBilled), period_start: `${y}-${pad2(m + 1)}-01`, period_end: `${y}-${pad2(m + 1)}-${pad2(dim)}`, lines })

  return {
    customer,
    service_location: service_location || null,
    quotation: quotation || null,
    activated_on: ymd(on),
    courtesy_note: `Jour d'activation (${ymd(on)}) gratuit — facturation dès le lendemain`,
    subscriptions: subs.map(s => s.name),
    written: false,
    ...result
  }
}

let _incomeAccount = null
async function defaultIncomeAccount (company) {
  if (_incomeAccount) return _incomeAccount
  try {
    const r = await erpFetch(`/api/resource/Company/${encodeURIComponent(company)}?fields=${ENC(['default_income_account'])}`)
    if (r.data?.data?.default_income_account) { _incomeAccount = r.data.data.default_income_account; return _incomeAccount }
  } catch (_) { /* repli */ }
  _incomeAccount = 'Ventes - T'
  return _incomeAccount
}

// ÉCRITURE GATÉE — crée la facture d'activation consolidée en BROUILLON (docstatus=0, JAMAIS soumise).
// Double verrou : `commit:true` (param) ET env PRORATION_WRITE=on. Sinon → aperçu (written:false).
// Un brouillon ne touche AUCUN grand livre, ne débite RIEN, et reste supprimable d'un clic.
async function commitActivationInvoice (body) {
  const preview = await buildActivationPreview(body)
  const armed = process.env.PRORATION_WRITE === 'on'
  if (!body.commit || !armed) {
    return { ...preview, written: false, gate: { commit: !!body.commit, env_PRORATION_WRITE: armed ? 'on' : 'off' }, note: 'écriture désarmée — il faut commit:true ET PRORATION_WRITE=on' }
  }
  if (!preview.lines.length) return { ...preview, written: false, note: 'aucune ligne à facturer' }

  const company = body.company || 'TARGO'
  const incomeAccount = await defaultIncomeAccount(company)
  const items = preview.lines.map(l => ({
    item_name: `${l.label} — prorata ${preview.billed_days}/${preview.period_days} j`.slice(0, 140),
    description: `${l.recurring ? 'Récurrent proraté' : 'Ponctuel — plein montant'} · ${l.full_amount.toFixed(2)}$ × ${l.factor} = ${l.prorated_amount.toFixed(2)}$ (${l.kind})`,
    qty: 1, rate: l.prorated_amount, amount: l.prorated_amount, income_account: incomeAccount
  }))
  const payload = {
    customer: preview.customer, company,
    posting_date: preview.activated_on, due_date: preview.activated_on,
    docstatus: 0, // BROUILLON — jamais soumis (aucune écriture comptable, aucun débit)
    remarks: `Facture d'activation consolidée proratée (BROUILLON, non soumise). ${preview.courtesy_note}. Période ${preview.period_start}→${preview.period_end} · ${preview.billed_days}/${preview.period_days} j.`,
    items
  }
  const inv = await erpFetch('/api/resource/Sales Invoice', { method: 'POST', body: JSON.stringify(payload) })
  if (inv.status === 200 && inv.data?.data?.name) {
    const d = inv.data.data
    return { ...preview, written: true, draft_invoice: d.name, docstatus: d.docstatus, submitted: d.docstatus === 1 }
  }
  throw new Error(`création brouillon échouée: HTTP ${inv.status} ${JSON.stringify(inv.data?.exception || inv.data).slice(0, 300)}`)
}

async function handle (req, res, method, path) {
  // POST /billing/prorate-preview — calcule l'aperçu des lignes proratées (aucune écriture). Gaté (staff).
  if (path === '/billing/prorate-preview' && method === 'POST') {
    const b = await parseBody(req)
    if (!b || !b.period_start || !b.period_end || !b.activation_date) return json(res, 400, { error: 'period_start, period_end, activation_date requis' })
    try { return json(res, 200, prorate(b)) } catch (e) { return json(res, 400, { error: e.message }) }
  }
  // POST /billing/activation-preview — facture d'activation consolidée d'un VRAI client (LECTURE SEULE).
  // body : { customer, service_location?, quotation?, activation_date?, statuses? }
  if (path === '/billing/activation-preview' && method === 'POST') {
    const b = await parseBody(req)
    if (!b || !b.customer) return json(res, 400, { error: 'customer requis' })
    try { return json(res, 200, b.commit ? await commitActivationInvoice(b) : await buildActivationPreview(b)) } catch (e) { return json(res, 400, { error: e.message }) }
  }

  // ── P1 : porte d'approbation de facturation (Ops). LECTURE SEULE + activation à l'approbation ; AUCUNE facturation ici. ──
  // GET /billing/approvals — activations en attente d'approbation (enrichies client/adresse).
  if (path === '/billing/approvals' && method === 'GET') {
    const pend = require('./billing-approvals').listPending()
    const out = []
    for (const p of pend) {
      const jr = await erpFetch(`/api/resource/Dispatch Job/${encodeURIComponent(p.job)}?fields=${ENC(['customer', 'service_location', 'customer_name'])}`)
      const d = (jr.status === 200 && jr.data?.data) ? jr.data.data : {}
      out.push({ ...p, customer: d.customer || null, customer_name: d.customer_name || null, service_location: d.service_location || null })
    }
    return json(res, 200, { pending: out })
  }
  // GET /billing/approvals/preview?job=… — aperçu prorata consolidé (LECTURE SEULE) pour un job.
  if (path === '/billing/approvals/preview' && method === 'GET') {
    const job = new URL(req.url, 'http://h').searchParams.get('job')
    if (!job) return json(res, 400, { error: 'job requis' })
    const jr = await erpFetch(`/api/resource/Dispatch Job/${encodeURIComponent(job)}?fields=${ENC(['customer', 'service_location'])}`)
    const d = (jr.status === 200 && jr.data?.data) ? jr.data.data : null
    if (!d || !d.customer) return json(res, 404, { error: 'job/client introuvable' })
    try { return json(res, 200, await buildActivationPreview({ customer: d.customer, service_location: d.service_location, statuses: ['En attente'] })) } catch (e) { return json(res, 400, { error: e.message }) }
  }
  // POST /billing/approvals/approve { job, approver } — approuve → ACTIVE les abonnements (facturation gérée par PRORATION_WRITE ; OFF en P1 → active SANS facturer).
  if (path === '/billing/approvals/approve' && method === 'POST') {
    const b = await parseBody(req)
    if (!b || !b.job) return json(res, 400, { error: 'job requis' })
    const approver = b.approver || req.headers['x-authentik-email'] || 'inconnu'
    try { return json(res, 200, await require('./billing-approvals').approve(b.job, approver)) } catch (e) { return json(res, 400, { error: e.message }) }
  }

  return json(res, 404, { error: 'not found' })
}

module.exports = { handle, prorate, buildActivationPreview, commitActivationInvoice }
