'use strict'
/**
 * Portal passwordless authentication.
 *
 * Exposes POST /portal/request-link — the customer types their email or phone,
 * we look them up in ERPNext, mint a 24 h customer JWT, and send the portal
 * URL via SMS + email. The portal's useMagicToken() composable reads the
 * token from the URL and hydrates the customer store.
 *
 *   identifier  ─► Customer (email_id | email_billing | cell_phone | tel_home
 *                            | legacy_customer_id | Customer.name)
 *                  │
 *                  ├─► SMS: "Gigafibre — Accédez à votre portail: {link}"
 *                  └─► Email: HTML template with CTA button + full URL
 *
 * Security:
 * - **Anti-enumeration**: we always return 200 OK, whether the identifier
 *   matched a customer or not. Prevents "is this email in your system?"
 *   probes. The internal log still records the miss.
 * - **Rate limit**: 3 requests per 15 min per identifier (case-insensitive).
 *   Uses an in-memory Map with a cleanup timer — survives restart loss
 *   gracefully (worst case, a throttled user gets to try again after a
 *   restart, which is fine).
 * - **Token TTL**: 24 h. After that, the user requests a new link.
 * - **JWT signature**: HS256 with cfg.JWT_SECRET. The portal never has the
 *   secret, so it trusts the server's signature implicitly (the token
 *   arrives over HTTPS from the user's own email/SMS).
 *
 * This endpoint replaces ERPNext's /login for portal customers. Staff still
 * authenticate via Authentik forwardAuth on ops.gigafibre.ca and erp.*;
 * customers never touch Authentik.
 */

const { log, json, parseBody, erpFetch, lookupCustomerByPhone } = require('./helpers')
const { generateCustomerToken } = require('./magic-link')
const cfg = require('./config')

const PORTAL_URL = cfg.CLIENT_PORTAL_URL || 'https://portal.gigafibre.ca'
const RATE_LIMIT_MAX = 3
const RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000
const LINK_TTL_HOURS = 24

// identifier (lower-cased) → { count, windowStart }
const rateLimits = new Map()

function checkRateLimit (key) {
  const now = Date.now()
  const entry = rateLimits.get(key)
  if (!entry || now - entry.windowStart > RATE_LIMIT_WINDOW_MS) {
    rateLimits.set(key, { count: 1, windowStart: now })
    return { ok: true }
  }
  if (entry.count >= RATE_LIMIT_MAX) {
    return { ok: false, retryAfterMs: RATE_LIMIT_WINDOW_MS - (now - entry.windowStart) }
  }
  entry.count++
  return { ok: true }
}

// Cull expired windows every 15 min so the Map doesn't grow unbounded.
setInterval(() => {
  const now = Date.now()
  for (const [k, v] of rateLimits) {
    if (now - v.windowStart > RATE_LIMIT_WINDOW_MS) rateLimits.delete(k)
  }
}, RATE_LIMIT_WINDOW_MS).unref()

function erpQuery (doctype, filters, fields, limit = 1) {
  const url = `/api/resource/${encodeURIComponent(doctype)}?filters=${encodeURIComponent(JSON.stringify(filters))}&fields=${encodeURIComponent(JSON.stringify(fields))}&limit_page_length=${limit}`
  return erpFetch(url)
}

const CUSTOMER_FIELDS = ['name', 'customer_name', 'cell_phone', 'tel_home', 'email_id', 'email_billing']

async function lookupCustomer (raw) {
  const cleaned = String(raw).trim()
  if (!cleaned) return null

  // Email — try both email_id and email_billing
  if (cleaned.includes('@')) {
    const email = cleaned.toLowerCase()
    for (const field of ['email_id', 'email_billing']) {
      const r = await erpQuery('Customer', [[field, '=', email]], CUSTOMER_FIELDS)
      if (r.status === 200 && r.data?.data?.length) return r.data.data[0]
    }
    return null
  }

  // Explicit customer ID (ERPNext name or legacy id)
  if (/^(C-|CUST-|GI-)/i.test(cleaned) || /^\d{4,7}$/.test(cleaned)) {
    // Try legacy_customer_id
    const legacy = await erpQuery('Customer', [['legacy_customer_id', '=', cleaned]], CUSTOMER_FIELDS)
    if (legacy.status === 200 && legacy.data?.data?.length) return legacy.data.data[0]
    // Try direct Customer.name
    try {
      const r = await erpFetch(`/api/resource/Customer/${encodeURIComponent(cleaned)}?fields=${encodeURIComponent(JSON.stringify(CUSTOMER_FIELDS))}`)
      if (r.status === 200 && r.data?.data) return r.data.data
    } catch (e) { /* fall through */ }
    return null
  }

  // Phone — lookupCustomerByPhone returns minimal fields; re-fetch for email
  const match = await lookupCustomerByPhone(cleaned)
  if (!match) return null
  try {
    const r = await erpFetch(`/api/resource/Customer/${encodeURIComponent(match.name)}?fields=${encodeURIComponent(JSON.stringify(CUSTOMER_FIELDS))}`)
    if (r.status === 200 && r.data?.data) return r.data.data
  } catch (e) { /* use minimal match */ }
  return match
}

function redactEmail (email) {
  if (!email) return ''
  const [user, domain] = email.split('@')
  if (!domain) return '***'
  const u = user.length <= 2
    ? user[0] + '*'
    : user[0] + '*'.repeat(Math.max(1, user.length - 2)) + user.slice(-1)
  return `${u}@${domain}`
}

function redactPhone (phone) {
  if (!phone) return ''
  const digits = phone.replace(/\D/g, '')
  if (digits.length < 4) return '***'
  return `***-***-${digits.slice(-4)}`
}

function loginEmailHtml (customerName, link) {
  return `
    <div style="font-family:sans-serif;max-width:500px;margin:0 auto;padding:20px">
      <img src="https://store.targo.ca/clients/images/logo_trans_noir.png" width="160" alt="Gigafibre" /><br><br>
      <p>Bonjour ${customerName || ''},</p>
      <p>Voici votre lien d'accès au portail client Gigafibre&nbsp;:</p>
      <p style="text-align:center;margin:24px 0">
        <a href="${link}" style="display:inline-block;padding:12px 32px;background:#4f46e5;color:#fff;border-radius:8px;text-decoration:none;font-weight:bold">
          Accéder à mon portail
        </a>
      </p>
      <p style="color:#888;font-size:13px">Ce lien est valide pour 24&nbsp;heures. Si vous n'avez pas demandé cet accès, vous pouvez ignorer ce courriel.</p>
      <p style="color:#888;font-size:12px;word-break:break-all">Lien complet&nbsp;:<br><code>${link}</code></p>
    </div>`
}

async function handle (req, res, method, path) {
  if (path === '/portal/request-link' && method === 'POST') {
    const body = await parseBody(req)
    const rawId = body && typeof body.identifier === 'string' ? body.identifier : ''
    if (!rawId.trim()) return json(res, 400, { error: 'identifier_required' })

    const rateKey = rawId.trim().toLowerCase()
    const rl = checkRateLimit(rateKey)
    if (!rl.ok) {
      const retryMin = Math.ceil(rl.retryAfterMs / 60000)
      return json(res, 429, {
        error: 'rate_limit',
        message: `Trop de tentatives. Réessayez dans ${retryMin} min.`,
        retry_after_sec: Math.ceil(rl.retryAfterMs / 1000),
      })
    }

    const customer = await lookupCustomer(rawId)
    if (!customer) {
      // Anti-enumeration: pretend success. Log internally for ops visibility.
      log(`Portal request-link: no match for "${rawId}"`)
      return json(res, 200, { ok: true, sent: [] })
    }

    const email = customer.email_id || customer.email_billing || ''
    const token = generateCustomerToken(customer.name, customer.customer_name, email, LINK_TTL_HOURS)
    const link = `${PORTAL_URL}/#/?token=${token}`
    const sent = []
    const redacted = []

    const phone = customer.cell_phone || customer.tel_home
    if (phone) {
      try {
        const { sendSmsInternal } = require('./twilio')
        await sendSmsInternal(phone, `Gigafibre — Accédez à votre portail:\n${link}\n(Valide 24h)`)
        sent.push('sms')
        redacted.push(redactPhone(phone))
      } catch (e) { log('Portal link SMS error:', e.message) }
    }

    if (email) {
      try {
        const { sendEmail } = require('./email')
        await sendEmail({
          to: email,
          subject: "Gigafibre — Votre lien d'accès au portail",
          html: loginEmailHtml(customer.customer_name, link),
        })
        sent.push('email')
        redacted.push(redactEmail(email))
      } catch (e) { log('Portal link email error:', e.message) }
    }

    log(`Portal link for ${customer.name}: sent via ${sent.join(',') || 'NONE'}`)
    return json(res, 200, { ok: true, sent, redacted })
  }

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

module.exports = { handle, lookupCustomer, redactEmail, redactPhone }
