const express = require('express')
const cors = require('cors')
const dotenv = require('dotenv')
const midtransClient = require('midtrans-client')
const crypto = require('crypto')
const https = require('https')

dotenv.config()

const app = express()
app.use(cors())
app.use(express.json())

const isProduction = process.env.MIDTRANS_IS_PRODUCTION === 'true'
const serverKey = process.env.MIDTRANS_SERVER_KEY || ''
const clientKey = process.env.MIDTRANS_CLIENT_KEY || ''

if (!serverKey || !clientKey) {
  console.warn('[Midtrans] Missing server/client keys in environment variables')
}

const core = new midtransClient.CoreApi({
  isProduction,
  serverKey,
  clientKey,
})

// --- ERP Notification Config
function parseEnable(v) {
  if (typeof v === 'string') {
    const s = v.trim().toLowerCase()
    return s === 'true' || s === '1' || s === 'yes' || s === 'on'
  }
  if (typeof v === 'boolean') return v
  if (typeof v === 'number') return v === 1
  return true
}
const ERP_NOTIFICATION_URL = process.env.ERP_NOTIFICATION_URL || ''
const ERP_ENABLE_NOTIF = parseEnable(process.env.ERP_ENABLE_NOTIF)
// Gunakan secret untuk signature; fallback ke CLIENT_ID bila SECRET belum ada
const ERP_CLIENT_SECRET = process.env.ERP_CLIENT_SECRET || process.env.ERP_CLIENT_ID || ''
const notifiedOrders = new Set()

// Mendukung banyak endpoint ERP (comma-separated) via env ERP_NOTIFICATION_URLS
function parseList(value) {
  if (!value) return []
  return String(value)
    .split(',')
    .map(s => s.trim())
    .filter(Boolean)
}

const ERP_NOTIFICATION_URLS = (() => {
  const multi = parseList(process.env.ERP_NOTIFICATION_URLS)
  if (multi.length > 0) return multi
  return ERP_NOTIFICATION_URL ? [ERP_NOTIFICATION_URL] : []
})()

// --- Logger utilities
const LOG_LEVEL = (process.env.LOG_LEVEL || 'info').toLowerCase()
const levelOrder = { debug: 0, info: 1, warn: 2, error: 3 }
const LOG_EXPOSE_API = parseEnable(process.env.LOG_EXPOSE_API)
const LOG_BUFFER_SIZE = parseInt(process.env.LOG_BUFFER_SIZE || '1000', 10)
const recentLogs = []
function shouldLog(level) { return (levelOrder[level] ?? 1) >= (levelOrder[LOG_LEVEL] ?? 1) }
function ts() { return new Date().toISOString() }
function sanitize(obj) {
  try { return JSON.parse(JSON.stringify(obj)) } catch { return obj }
}
function maskPayload(obj) {
  const o = sanitize(obj)
  const maskKeys = ['card_number', 'cvv', 'token_id', 'server_key']
  function mask(o2) {
    if (!o2 || typeof o2 !== 'object') return o2
    for (const k of Object.keys(o2)) {
      const v = o2[k]
      if (maskKeys.includes(k)) o2[k] = v ? '***' : v
      else if (typeof v === 'object') o2[k] = mask(v)
    }
    return o2
  }
  return mask(o)
}
function log(level, msg, meta) {
  if (!shouldLog(level)) return
  const line = `[${ts()}] [${level}] ${msg}`
  try {
    const entry = { ts: ts(), level, msg, meta: sanitize(meta) }
    recentLogs.push(entry)
    if (recentLogs.length > LOG_BUFFER_SIZE) recentLogs.shift()
  } catch {}
  if (meta) {
    const data = typeof meta === 'string' ? meta : JSON.stringify(meta)
    console.log(line, data)
  } else {
    console.log(line)
  }
}
const logDebug = (m, meta) => log('debug', m, meta)
const logInfo = (m, meta) => log('info', m, meta)
const logWarn = (m, meta) => log('warn', m, meta)
const logError = (m, meta) => log('error', m, meta)

// Request ID + basic request/response logging
function newReqId() {
  return Math.random().toString(36).slice(2) + Date.now().toString(36)
}
app.use((req, res, next) => {
  req.id = newReqId()
  logInfo('req.start', { id: req.id, method: req.method, url: req.originalUrl })
  res.on('finish', () => {
    logInfo('req.end', { id: req.id, statusCode: res.statusCode })
  })
  next()
})

const ENABLE = {
  bank_transfer: parseEnable(process.env.ENABLE_BANK_TRANSFER),
  credit_card: parseEnable(process.env.ENABLE_CREDIT_CARD),
  gopay: parseEnable(process.env.ENABLE_GOPAY),
  cstore: parseEnable(process.env.ENABLE_CSTORE),
}

// --- Payment Link Config
const EXTERNAL_API_KEY = process.env.EXTERNAL_API_KEY || ''
const PAYMENT_LINK_SECRET = process.env.PAYMENT_LINK_SECRET || ''
const PAYMENT_LINK_TTL_MINUTES = parseInt(process.env.PAYMENT_LINK_TTL_MINUTES || '1440', 10)
const PAYMENT_LINK_BASE = process.env.PAYMENT_LINK_BASE || 'http://localhost:5174/pay'
const activeOrders = new Map() // order_id -> expire_at
// Map untuk menyimpan mercant_id per order_id agar notifikasi ERP bisa dinamis
const orderMerchantId = new Map() // order_id -> mercant_id

function isDevEnv() { return (process.env.NODE_ENV || '').toLowerCase() !== 'production' }
function verifyExternalKey(req) {
  const key = (req.headers['x-api-key'] || req.headers['X-API-KEY'] || '').toString()
  if (EXTERNAL_API_KEY) return key === EXTERNAL_API_KEY
  // Allow if not configured only in dev for easier local testing
  return isDevEnv()
}

function base64UrlEncode(buf) {
  return Buffer.from(buf).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
}
function base64UrlDecode(str) {
  const pad = str.length % 4 === 0 ? '' : '='.repeat(4 - (str.length % 4))
  const s = str.replace(/-/g, '+').replace(/_/g, '/') + pad
  return Buffer.from(s, 'base64').toString('utf8')
}
function computeTokenSignature(orderId, nominal, expireAt) {
  const canonical = `${String(orderId)}|${String(nominal)}|${String(expireAt)}`
  return crypto.createHmac('sha256', PAYMENT_LINK_SECRET || 'dev-secret').update(canonical).digest('hex')
}
function createPaymentLinkToken({ order_id, nominal, expire_at, customer, allowed_methods }) {
  const v = 1
  const sig = computeTokenSignature(order_id, nominal, expire_at)
  const payload = { v, order_id, nominal, expire_at, sig, customer, allowed_methods }
  return base64UrlEncode(JSON.stringify(payload))
}
function resolvePaymentLinkToken(token) {
  try {
    const json = JSON.parse(base64UrlDecode(token))
    const { order_id, nominal, expire_at, sig } = json || {}
    if (!order_id || !nominal || !expire_at || !sig) return { error: 'INVALID_TOKEN' }
    const expected = computeTokenSignature(order_id, nominal, expire_at)
    if (String(sig) !== String(expected)) return { error: 'INVALID_SIGNATURE' }
    if (Date.now() > Number(expire_at)) return { error: 'TOKEN_EXPIRED', payload: { order_id, nominal, expire_at } }
    return { payload: json }
  } catch (e) {
    return { error: 'TOKEN_PARSE_ERROR', message: e?.message }
  }
}

// Health check
app.get('/api/health', (_req, res) => {
  logDebug('health.check', { isProduction, hasServerKey: !!serverKey, hasClientKey: !!clientKey })
  res.json({ ok: true, env: { isProduction, hasServerKey: !!serverKey, hasClientKey: !!clientKey } })
})

// Runtime config (feature toggles)
app.get('/api/config', (_req, res) => {
  const payload = {
    paymentToggles: { ...ENABLE },
    midtransEnv: isProduction ? 'production' : 'sandbox',
    clientKey,
  }
  logDebug('config.get', payload)
  res.json(payload)
})

// Logs endpoint (dev/debug): GET /api/logs?limit=100&level=debug|info|warn|error&q=keyword
app.get('/api/logs', (req, res) => {
  if (!LOG_EXPOSE_API) {
    return res.status(403).json({ error: 'FORBIDDEN', message: 'Log API disabled. Set LOG_EXPOSE_API=true to enable.' })
  }
  const limit = Math.max(1, Math.min(1000, parseInt(String(req.query.limit || '100'), 10)))
  const level = String(req.query.level || '').toLowerCase()
  const q = String(req.query.q || '').toLowerCase()
  let items = recentLogs
  if (level) items = items.filter((e) => e.level === level)
  if (q) items = items.filter((e) => {
    try {
      const m = e.meta ? JSON.stringify(e.meta).toLowerCase() : ''
      return e.msg.toLowerCase().includes(q) || m.includes(q)
    } catch { return false }
  })
  const sliced = items.slice(Math.max(0, items.length - limit))
  res.json({ count: sliced.length, items: sliced })
})

// Dev-only: allow updating toggles at runtime without restart
app.post('/api/config', (req, res) => {
  const isDev = process.env.NODE_ENV !== 'production'
  if (!isDev) return res.status(403).json({ error: 'FORBIDDEN', message: 'Runtime config updates disabled in production.' })
  const t = req?.body?.paymentToggles
  if (t && typeof t === 'object') {
    if (typeof t.bank_transfer !== 'undefined') ENABLE.bank_transfer = parseEnable(t.bank_transfer)
    if (typeof t.credit_card !== 'undefined') ENABLE.credit_card = parseEnable(t.credit_card)
    if (typeof t.gopay !== 'undefined') ENABLE.gopay = parseEnable(t.gopay)
    if (typeof t.cstore !== 'undefined') ENABLE.cstore = parseEnable(t.cstore)
  }
  const result = { paymentToggles: { ...ENABLE } }
  logInfo('config.post', result)
  res.json(result)
})

// Payment Link Resolver: GET /api/payment-links/:token
app.get('/api/payment-links/:token', (req, res) => {
  const { token } = req.params
  const result = resolvePaymentLinkToken(token)
  if (result.error === 'TOKEN_EXPIRED') {
    logWarn('paymentlink.expired', { order_id: result.payload?.order_id })
    return res.status(410).json({ error: result.error, ...result.payload })
  }
  if (result.error) {
    logWarn('paymentlink.invalid', { error: result.error })
    if (isDevEnv()) {
      const ttlMin = PAYMENT_LINK_TTL_MINUTES > 0 ? PAYMENT_LINK_TTL_MINUTES : 1440
      const fallback = { order_id: token, nominal: 150000, expire_at: Date.now() + ttlMin * 60 * 1000 }
      logInfo('paymentlink.dev.fallback', { order_id: fallback.order_id })
      return res.json(fallback)
    }
    return res.status(400).json({ error: result.error })
  }
  const p = result.payload
  logInfo('paymentlink.resolve.success', { order_id: p.order_id, expire_at: p.expire_at })
  res.json({ order_id: p.order_id, nominal: p.nominal, customer: p.customer, expire_at: p.expire_at, allowed_methods: p.allowed_methods })
})

// Charge endpoint (pass-through to Midtrans Core API)
app.post('/api/payments/charge', async (req, res) => {
  try {
    const pt = req?.body?.payment_type
    logInfo('charge.request', { id: req.id, payment_type: pt })
    logDebug('charge.payload', maskPayload(req.body))

    // Idempotency guard: if an order is already pending in Midtrans, block re-charge for the same order_id
    const orderId = req?.body?.transaction_details?.order_id || req?.body?.order_id || ''
    if (orderId) {
      try {
        const st = await core.transaction.status(orderId)
        const ts = (st?.transaction_status || '').toLowerCase()
        if (ts === 'pending') {
          logWarn('charge.blocked.pending_exists', { id: req.id, order_id: orderId })
          return res.status(409).json({
            error: 'ORDER_ACTIVE',
            message: 'Order sudah memiliki transaksi pending di Midtrans; tidak dapat membuat ulang. Gunakan instruksi pembayaran yang ada atau buat order baru.',
            status: st,
          })
        }
      } catch (e) {
        const msg = (e?.message || '').toLowerCase()
        if (msg.includes('not found') || msg.includes('404')) {
          logDebug('charge.status_not_found', { order_id: orderId })
        } else {
          logDebug('charge.status_check_error', { order_id: orderId, message: e?.message })
        }
      }
    }
    const isBankType = pt === 'bank_transfer' || pt === 'echannel' || pt === 'permata'
    if (isBankType && !ENABLE.bank_transfer) {
      logWarn('charge.blocked', { id: req.id, reason: 'bank_transfer disabled' })
      return res.status(403).json({ error: 'PAYMENT_TYPE_DISABLED', message: 'Bank transfer is disabled by environment configuration.' })
    }
    if (pt === 'credit_card' && !ENABLE.credit_card) {
      logWarn('charge.blocked', { id: req.id, reason: 'credit_card disabled' })
      return res.status(403).json({ error: 'PAYMENT_TYPE_DISABLED', message: 'Credit card is disabled by environment configuration.' })
    }
    if (pt === 'gopay' && !ENABLE.gopay) {
      logWarn('charge.blocked', { id: req.id, reason: 'gopay disabled' })
      return res.status(403).json({ error: 'PAYMENT_TYPE_DISABLED', message: 'GoPay/QRIS is disabled by environment configuration.' })
    }
    if (pt === 'cstore' && !ENABLE.cstore) {
      logWarn('charge.blocked', { id: req.id, reason: 'cstore disabled' })
      return res.status(403).json({ error: 'PAYMENT_TYPE_DISABLED', message: 'Convenience Store is disabled by environment configuration.' })
    }
    const chargeResponse = await core.charge(req.body)
    logInfo('charge.success', { id: req.id, order_id: chargeResponse?.order_id, status_code: chargeResponse?.status_code })
    res.json(chargeResponse)
  } catch (e) {
    const msg = e?.message || 'Charge failed'
    logError('charge.error', { id: req.id, message: msg })
    res.status(400).json({ error: 'CHARGE_ERROR', message: msg })
  }
})

// Status endpoint (by order_id)
app.get('/api/payments/:orderId/status', async (req, res) => {
  const { orderId } = req.params
  try {
    // midtrans-client exposes transaction helper via CoreApi
    logInfo('status.request', { id: req.id, orderId })
    const status = await core.transaction.status(orderId)
    logInfo('status.success', { id: req.id, orderId, transaction_status: status?.transaction_status })
    // Respond immediately with status
    res.json(status)

    // Fallback: selain webhook, jika status di sini sudah sukses (settlement/capture+accept), kirim notifikasi ke ERP
    setImmediate(async () => {
      try {
        if (isSuccessfulMidtransStatus(status)) {
          const nominal = String(status?.gross_amount || '')
          if (!notifiedOrders.has(orderId)) {
            activeOrders.delete(orderId)
            logInfo('status.notify.erp.trigger', { orderId, transaction_status: status?.transaction_status })
            const ok = await notifyERP({ orderId, nominal })
            if (ok) {
              notifiedOrders.add(orderId)
            } else {
              logWarn('erp.notify.defer', { orderId, reason: 'post_failed_or_missing_data' })
            }
          } else {
            logInfo('erp.notify.skip', { orderId, reason: 'already_notified' })
          }
        }
      } catch (e) {
        logError('status.notify.error', { orderId, message: e?.message })
      }
    })
  } catch (e) {
    const msg = e?.message || 'Status check failed'
    logError('status.error', { id: req.id, orderId, message: msg })
    res.status(400).json({ error: 'STATUS_ERROR', message: msg })
  }
})

// External ERP Create Transaction → issue payment link
app.post('/createtransaksi', async (req, res) => {
  try {
    if (!verifyExternalKey(req)) {
      logWarn('createtransaksi.unauthorized', { id: req.id })
      return res.status(401).json({ error: 'UNAUTHORIZED', message: 'X-API-KEY invalid' })
    }

    // Skema baru:
    // {
    //   mercant_id, timestamp, deskripsi, nominal,
    //   nama, no_telepon, email,
    //   item: [{ item_id, nama, harga, qty }, ...]
    // }
    const mercantId = req?.body?.mercant_id
    const nominalRaw = req?.body?.nominal
    const items = Array.isArray(req?.body?.item) ? req.body.item : []
    const primaryItemId = items?.[0]?.item_id
    // Mapping order_id: gunakan "mercant_id:item_id" bila keduanya tersedia,
    // jika tidak, fallback ke item_id atau mercant_id atau field order_id/item_id yang disediakan.
    const order_id = String(
      (primaryItemId && mercantId) ? `${mercantId}:${primaryItemId}` :
      (primaryItemId || mercantId || req?.body?.order_id || req?.body?.item_id || '')
    )
    // Simpan mercant_id per order agar dapat digunakan saat notifikasi ERP
    if (mercantId) {
      try { orderMerchantId.set(order_id, mercantId) } catch {}
    }

    // Bentuk customer dari field nama/no_telepon/email
    const customer = {
      name: req?.body?.nama,
      phone: req?.body?.no_telepon,
      email: req?.body?.email,
    }
    const allowed_methods = req?.body?.allowed_methods

    if (!order_id || typeof nominalRaw === 'undefined') {
      logWarn('createtransaksi.bad_request', { id: req.id, hasOrderId: !!order_id, hasNominal: typeof nominalRaw !== 'undefined' })
      return res.status(400).json({ error: 'BAD_REQUEST', message: 'order_id (mercant_id atau item[0].item_id) dan nominal wajib ada' })
    }
    const nominal = Number(nominalRaw)
    const now = Date.now()
    const ttlMin = PAYMENT_LINK_TTL_MINUTES > 0 ? PAYMENT_LINK_TTL_MINUTES : 1440
    const expire_at = now + ttlMin * 60 * 1000

    // Block jika sudah selesai
    if (notifiedOrders.has(order_id)) {
      logWarn('createtransaksi.completed', { order_id })
      return res.status(409).json({ error: 'ORDER_COMPLETED', message: 'Order already completed' })
    }
    // Block jika ada link aktif belum expired
    const existing = activeOrders.get(order_id)
    if (existing && existing > now) {
      logWarn('createtransaksi.active_exists', { order_id })
      return res.status(409).json({ error: 'ORDER_ACTIVE', message: 'Active payment link exists' })
    }

    // Guard tambahan: cek ke Midtrans apakah order_id sudah memiliki transaksi pending
    try {
      const status = await core.transaction.status(order_id)
      const s = (status?.transaction_status || '').toLowerCase()
      if (s === 'pending') {
        logWarn('createtransaksi.midtrans_pending', { order_id })
        return res.status(409).json({
          error: 'ORDER_ACTIVE',
          message: 'Order sudah memiliki transaksi pending di Midtrans; gunakan instruksi pembayaran yang ada atau buat order baru.',
          status: { order_id: status?.order_id, status_code: status?.status_code, status_message: status?.status_message, payment_type: status?.payment_type },
        })
      }
    } catch (e) {
      // Jika 404/not found, lanjut membuat payment link; error lain tetap diteruskan
      const msg = (e?.message || '').toLowerCase()
      if (msg.includes('not found') || msg.includes('404')) {
        logDebug('createtransaksi.midtrans_status_not_found', { order_id })
      } else {
        logDebug('createtransaksi.midtrans_status_check_error', { order_id, message: e?.message })
      }
    }

    const token = createPaymentLinkToken({ order_id, nominal, expire_at, customer, allowed_methods })
    const url = `${PAYMENT_LINK_BASE}/${token}`
    activeOrders.set(order_id, expire_at)
    logInfo('createtransaksi.issued', { order_id, expire_at })

    // Respons mengikuti format yang diminta
    res.json({ status: '200', messages: 'SUCCESS', data: { url } })
  } catch (e) {
    logError('createtransaksi.error', { id: req.id, message: e?.message })
    res.status(500).json({ error: 'CREATE_ERROR', message: e?.message || 'Internal error' })
  }
})

// --- Helpers: Midtrans signature verification & ERP notify
function computeMidtransSignature(orderId, statusCode, grossAmount, secretKey) {
  try {
    const raw = String(orderId) + String(statusCode) + String(grossAmount) + String(secretKey)
    return crypto.createHash('sha512').update(raw).digest('hex')
  } catch {
    return ''
  }
}

function isSuccessfulMidtransStatus(body) {
  const s = (body?.transaction_status || '').toLowerCase()
  const fraud = (body?.fraud_status || '').toLowerCase()
  // Success for most methods: settlement; Card: capture with fraud_status=accept also success
  if (s === 'settlement') return true
  if (s === 'capture' && fraud === 'accept') return true
  return false
}

class HttpError extends Error {
  constructor(statusCode, body) {
    super(`HTTP ${statusCode}`)
    this.statusCode = statusCode
    this.body = body
  }
}

function postJson(url, data, extraHeaders = {}) {
  return new Promise((resolve, reject) => {
    try {
      const u = new URL(url)
      const body = JSON.stringify(data)
      const headers = {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body),
        ...extraHeaders,
      }
      logDebug('http.post', {
        url,
        headers: sanitize(headers),
        body_length: Buffer.byteLength(body),
        body_preview: body.slice(0, 256),
      })
      const opts = {
        method: 'POST',
        hostname: u.hostname,
        path: u.pathname + (u.search || ''),
        port: u.port || (u.protocol === 'https:' ? 443 : 80),
        headers,
      }
      const req = (u.protocol === 'https:' ? https : require('http')).request(opts, (res) => {
        let chunks = ''
        res.on('data', (d) => { chunks += d.toString() })
        res.on('end', () => {
          const info = {
            url,
            statusCode: res.statusCode,
            body_length: chunks.length,
            body_preview: chunks.slice(0, 256),
          }
          if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
            logDebug('http.response', info)
            resolve({ status: res.statusCode, body: chunks })
          } else {
            logWarn('http.response.error', info)
            reject(new HttpError(res.statusCode, chunks))
          }
        })
      })
      req.on('error', (e) => {
        logError('http.request.error', { url, message: e?.message })
        reject(e)
      })
      req.write(body)
      req.end()
    } catch (e) {
      logError('http.build.error', { url, message: e?.message })
      reject(e)
    }
  })
}

async function computeErpSignature(mercantId, statusCode, nominal, clientId) {
  try {
    const raw = String(mercantId) + String(statusCode) + String(nominal) + String(clientId)
    const sign =  crypto.createHash('sha512').update(raw).digest('hex')
    return sign;
  } catch {
    return ''
  }
}

// Resolve mercant_id untuk sebuah order_id:
// 1) gunakan map yang tersimpan dari createtransaksi
// 2) jika order_id memakai skema "mercant_id:item_id", ambil prefix sebelum ':'
// 3) fallback ke ERP_MERCANT_ID dari env (untuk kasus lama)
function resolveMercantId(orderId) {
  try {
    if (orderMerchantId.has(orderId)) return orderMerchantId.get(orderId)
    if (typeof orderId === 'string' && orderId.includes(':')) {
      const [m] = orderId.split(':')
      if (m) return m
    }
  } catch {}
  return ''
}

async function notifyERP({ orderId, nominal, mercantId }) {
  if (!ERP_ENABLE_NOTIF) {
    logInfo('erp.notify.skip', { reason: 'disabled' })
    return false
  }
  // Untuk notifikasi dinamis, hanya URL dan client secret yang wajib
  if (ERP_NOTIFICATION_URLS.length === 0 || !ERP_CLIENT_SECRET) {
    logWarn('erp.notify.missing_config', { urlsCount: ERP_NOTIFICATION_URLS.length, hasClientSecret: !!ERP_CLIENT_SECRET })
    return false
  }
  const statusCode = '200'
  const mId = mercantId || resolveMercantId(orderId)
  if (!mId) {
    logWarn('erp.notify.skip', { orderId, reason: 'missing_mercant_id' })
    return false
  }
  const signature = await computeErpSignature(mId, statusCode, nominal, ERP_CLIENT_SECRET)
  
  const payload = {
    mercant_id: mId,
    status_code: statusCode,
    nominal: nominal,
    signature: signature,
  }
  // Tambahan debug: pastikan signature benar-benar ikut terkirim
  logDebug('erp.notify.payload.outgoing', {
    mercant_id: mId,
    status_code: statusCode,
    nominal: nominal,
    signature_present: typeof signature !== 'undefined',
    signature_length: (typeof signature === 'string') ? signature.length : -1,
  })
  logInfo('erp.notify.start', { orderId, urls: ERP_NOTIFICATION_URLS })
  const results = await Promise.allSettled(
    ERP_NOTIFICATION_URLS.map(async (url) => {
      try {
        const res = await postJson(url, payload)
        logInfo('erp.notify.success', { orderId, url, status: res.status })
        return true
      } catch (e) {
        logError('erp.notify.error', {
          orderId,
          url,
          status: e?.statusCode,
          message: e?.message,
          body: typeof e?.body === 'string' ? e.body.slice(0, 256) : undefined,
        })
        return false
      }
    })
  )
  const okCount = results.reduce((acc, r) => acc + (r.status === 'fulfilled' && r.value ? 1 : 0), 0)
  const failCount = ERP_NOTIFICATION_URLS.length - okCount
  logInfo('erp.notify.summary', { orderId, okCount, failCount, total: ERP_NOTIFICATION_URLS.length })
  return okCount > 0
}

// Webhook endpoint for Midtrans notifications
app.post('/api/payments/webhook', async (req, res) => {
  try {
    const body = req.body || {}
    const orderId = body?.order_id
    const statusCode = body?.status_code
    const grossAmount = body?.gross_amount
    const signatureKey = (body?.signature_key || '').toLowerCase()
    logInfo('webhook.receive', { order_id: orderId, transaction_status: body?.transaction_status })

    // Verify signature
    const expectedSig = computeMidtransSignature(orderId, statusCode, grossAmount, serverKey)
    if (!expectedSig || signatureKey !== expectedSig) {
      logWarn('webhook.signature.invalid', { order_id: orderId })
      return res.status(401).json({ error: 'INVALID_SIGNATURE' })
    }

    // Acknowledge quickly
    res.json({ ok: true })

    // Process success callbacks asynchronously
    if (isSuccessfulMidtransStatus(body)) {
      logInfo('webhook.success_status', { order_id: orderId, transaction_status: body?.transaction_status, fraud_status: body?.fraud_status })
      const nominal = String(grossAmount)
      if (!notifiedOrders.has(orderId)) {
        // Mark order inactive upon completion
        activeOrders.delete(orderId)
        const ok = await notifyERP({ orderId, nominal })
        if (ok) {
          notifiedOrders.add(orderId)
        } else {
          logWarn('erp.notify.defer', { orderId, reason: 'post_failed_or_missing_data' })
        }
      } else {
        logInfo('erp.notify.skip', { orderId, reason: 'already_notified' })
      }
    } else {
      logInfo('webhook.non_success', { order_id: orderId, transaction_status: body?.transaction_status })
    }
  } catch (e) {
    logError('webhook.error', { message: e?.message })
    try { res.status(500).json({ error: 'WEBHOOK_ERROR' }) } catch {}
  }
})

// Dev-only helpers: echo endpoint and manual ERP notify trigger
if (LOG_EXPOSE_API) {
  // Echo incoming JSON for local testing (can be used as ERP_NOTIFICATION_URL)
  app.post('/api/echo', async (req, res) => {
    try {
      const body = req.body || {}
      const sig = body?.signature
      logDebug('erp.mock.receive', {
        has_signature: typeof sig !== 'undefined',
        signature_length: (typeof sig === 'string') ? sig.length : -1,
        body_length: JSON.stringify(body).length,
      })
      return res.json({ ok: true, received: body })
    } catch (e) {
      logError('erp.mock.error', { message: e?.message })
      return res.status(500).json({ error: 'ECHO_ERROR', message: e?.message || 'Echo failed' })
    }
  })

  // Echo kedua untuk pengujian multi-URL lokal
  app.post('/api/echo2', async (req, res) => {
    try {
      const body = req.body || {}
      const sig = body?.signature
      logDebug('erp.mock.receive', {
        endpoint: 'echo2',
        has_signature: typeof sig !== 'undefined',
        signature_length: (typeof sig === 'string') ? sig.length : -1,
        body_length: JSON.stringify(body).length,
      })
      return res.json({ ok: true, received: body })
    } catch (e) {
      logError('erp.mock.error', { endpoint: 'echo2', message: e?.message })
      return res.status(500).json({ error: 'ECHO2_ERROR', message: e?.message || 'Echo2 failed' })
    }
  })

  // Manually trigger ERP notification for testing signature presence in body
  app.post('/api/test/notify-erp', async (req, res) => {
    try {
      const { orderId, nominal, mercant_id } = req.body || {}
      if (!orderId && !mercant_id) {
        return res.status(400).json({ error: 'BAD_REQUEST', message: 'Provide orderId or mercant_id and nominal' })
      }
      const ok = await notifyERP({ orderId, nominal: String(nominal || ''), mercantId: mercant_id })
      return res.json({ ok })
    } catch (e) {
      logError('test.notify.error', { message: e?.message })
      return res.status(500).json({ error: 'TEST_NOTIFY_ERROR', message: e?.message || 'Notify test failed' })
    }
  })
}

const port = process.env.PORT || 8000
app.listen(port, () => {
  console.log(`[server] listening on http://localhost:${port}/ (production=${isProduction})`)
})
