// Helcim API helper — centralized fetcher and typed wrappers for Helcim v2 endpoints.
// Auto-imported by Nitro under server/utils, but server API handlers in this codebase
// use explicit imports (matching the existing `../../utils/auth.js` pattern).

import { randomBytes } from 'node:crypto'

const HELCIM_API_BASE = 'https://api.helcim.com/v2'

const IDEMPOTENCY_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'

/**
 * Generic Helcim API fetcher.
 *
 * Reads the API token from runtime config on every call (do not hoist — Nitro
 * runtime config is not guaranteed to be available at module import time).
 * Throws `createError` with the upstream status on non-2xx responses. Network
 * errors (fetch itself rejecting) propagate naturally so callers can decide
 * how to handle them.
 *
 * @param {string} path - Path relative to the Helcim v2 base (must start with `/`).
 * @param {object} [options]
 * @param {string} [options.method='GET']
 * @param {object} [options.body] - JSON-serializable request body.
 * @param {string} [options.idempotencyKey] - Sent as `idempotency-key` header.
 * @param {string} [options.errorMessage] - statusMessage used when the upstream returns non-OK.
 * @returns {Promise<any>} Parsed JSON response.
 */
export async function helcimFetch(path, { method = 'GET', body, idempotencyKey, errorMessage } = {}) {
  const helcimToken = useRuntimeConfig().helcimApiToken

  if (!helcimToken) {
    throw createError({
      statusCode: 500,
      statusMessage: 'Helcim API token not configured'
    })
  }

  const headers = {
    accept: 'application/json',
    'api-token': helcimToken
  }

  if (body !== undefined) {
    headers['content-type'] = 'application/json'
  }

  if (idempotencyKey) {
    headers['idempotency-key'] = idempotencyKey
  }

  const response = await fetch(`${HELCIM_API_BASE}${path}`, {
    method,
    headers,
    body: body !== undefined ? JSON.stringify(body) : undefined
  })

  if (!response.ok) {
    const errorText = await response.text()
    console.error(`[helcim] ${method} ${path} ${response.status} ${errorText}`)
    throw createError({
      statusCode: response.status,
      statusMessage: errorMessage || 'Helcim API error'
    })
  }

  const text = await response.text()
  if (!text.trim()) return null
  try {
    return JSON.parse(text)
  } catch {
    return null
  }
}

// ---- Customers ----

export const getHelcimCustomer = (id) =>
  helcimFetch(`/customers/${id}`, { errorMessage: 'Customer lookup failed' })

export const findHelcimCustomerByEmail = (email) =>
  helcimFetch(`/customers?search=${encodeURIComponent(email)}`, { errorMessage: 'Customer search failed' })

export const createHelcimCustomer = (payload) =>
  helcimFetch('/customers', { method: 'POST', body: payload, errorMessage: 'Customer creation failed' })

export const updateHelcimCustomer = (id, payload) =>
  helcimFetch(`/customers/${id}`, { method: 'PATCH', body: payload, errorMessage: 'Billing update failed' })

export const listHelcimCustomerCards = (id) =>
  helcimFetch(`/customers/${id}/cards`, { errorMessage: 'Card lookup failed' })

/**
 * Set a customer's default payment method by card token.
 *
 * Helcim's "set default card" endpoint keys on the internal numeric card `id`,
 * not the public `cardToken` — so this helper resolves the token via
 * `/customers/{id}/cards` first, then PATCHes the default endpoint.
 *
 * Endpoint: PATCH /customers/{customerId}/cards/{cardId}/default
 * Docs: https://devdocs.helcim.com/reference/setcustomercarddefault
 *
 * @param {string|number} customerId - Helcim customer id.
 * @param {string} cardToken - Card token to mark as default.
 * @returns {Promise<any>} Helcim response.
 */
export async function updateHelcimCustomerDefaultPaymentMethod(customerId, cardToken) {
  const cards = await helcimFetch(
    `/customers/${customerId}/cards?cardToken=${encodeURIComponent(cardToken)}`,
    { errorMessage: 'Card lookup failed' }
  )

  const list = Array.isArray(cards) ? cards : (cards?.cards || cards?.data || [])
  const match = list.find((c) => c?.cardToken === cardToken)
  if (!match?.id) {
    throw createError({
      statusCode: 404,
      statusMessage: 'Card token not found on customer'
    })
  }

  return helcimFetch(`/customers/${customerId}/cards/${match.id}/default`, {
    method: 'PATCH',
    errorMessage: 'Default card update failed'
  })
}

// ---- Subscriptions ----

export const createHelcimSubscription = (subscription, idempotencyKey) =>
  helcimFetch('/subscriptions', {
    method: 'POST',
    body: { subscriptions: [subscription] },
    idempotencyKey,
    errorMessage: 'Subscription creation failed'
  })

export const getHelcimSubscription = (id) =>
  helcimFetch(`/subscriptions/${id}`, { errorMessage: 'Subscription lookup failed' })

export const cancelHelcimSubscription = (id) =>
  helcimFetch(`/subscriptions/${id}`, { method: 'DELETE', errorMessage: 'Subscription cancellation failed' })

export const updateHelcimSubscription = (id, payload) =>
  helcimFetch('/subscriptions', {
    method: 'PATCH',
    body: { subscriptions: [{ id: Number(id), ...payload }] },
    errorMessage: 'Subscription update failed'
  })

/**
 * Update the payment method (card token) on an existing subscription.
 *
 * Wraps `updateHelcimSubscription` with the card-token payload shape.
 * Helcim's subscription PATCH schema
 * (https://devdocs.helcim.com/reference/subscription-patch) does not
 * publicly document a cardToken field on the update body — subscriptions
 * normally bill against the customer's default card. We send `cardToken`
 * here as an explicit payment-method binding alongside the
 * `updateHelcimCustomerDefaultPaymentMethod` call; the latter is the
 * authoritative change, this is best-effort belt-and-suspenders.
 *
 * @param {string|number} subscriptionId
 * @param {string} cardToken
 * @returns {Promise<any>}
 */
export const updateHelcimSubscriptionPaymentMethod = (subscriptionId, cardToken) =>
  updateHelcimSubscription(subscriptionId, { cardToken })

// ---- Transactions ----

const TRANSACTION_LIMIT = 50

/**
 * List a customer's card transactions, sorted newest-first, capped at 50.
 *
 * Endpoint: GET /card-transactions/?customerCode={code}
 * Docs: https://devdocs.helcim.com/reference/getcardtransactions
 *
 * Helcim returns `status` as `APPROVED` / `DECLINED` and `type` as
 * `purchase` / `refund` / etc. We normalize to one of:
 *   'paid' | 'refunded' | 'failed' | 'other'
 *
 * @param {string} customerCode - Helcim customer code (e.g. "CST1044").
 * @returns {Promise<Array<{id: string, date: string, amount: number, status: string, currency: string}>>}
 */
export async function listHelcimCustomerTransactions(customerCode) {
  const path = `/card-transactions/?customerCode=${encodeURIComponent(customerCode)}&limit=${TRANSACTION_LIMIT}`
  const response = await helcimFetch(path, { errorMessage: 'Transaction lookup failed' })

  const rows = Array.isArray(response)
    ? response
    : (response?.transactions || response?.data || [])

  const filtered = rows.filter((t) => {
    const type = String(t?.type || '').toLowerCase()
    const amount = typeof t?.amount === 'number' ? t.amount : Number(t?.amount) || 0
    return type !== 'verify' && amount > 0
  })

  const sorted = [...filtered].sort((a, b) => {
    const da = Date.parse(a?.dateCreated || '') || 0
    const db = Date.parse(b?.dateCreated || '') || 0
    return db - da
  }).slice(0, TRANSACTION_LIMIT)

  return sorted.map(normalizeTransaction)
}

function normalizeTransaction(t) {
  return {
    id: String(t?.transactionId ?? t?.id ?? ''),
    date: t?.dateCreated || '',
    amount: typeof t?.amount === 'number' ? t.amount : Number(t?.amount) || 0,
    status: normalizeTransactionStatus(t?.status, t?.type),
    currency: t?.currency || ''
  }
}

function normalizeTransactionStatus(status, type) {
  const s = String(status || '').toUpperCase()
  const t = String(type || '').toLowerCase()
  if (t === 'refund' || s === 'REFUNDED') return 'refunded'
  if (s === 'APPROVED') return 'paid'
  if (s === 'DECLINED' || s === 'FAILED') return 'failed'
  return 'other'
}

// ---- Payment plans (admin) ----

export const listHelcimPlans = () =>
  helcimFetch('/payment-plans', { errorMessage: 'Failed to fetch payment plans' })

export const createHelcimPlan = (payload) =>
  helcimFetch('/payment-plans', { method: 'POST', body: payload, errorMessage: 'Payment plan creation failed' })

export const listHelcimSubscriptions = () =>
  helcimFetch('/subscriptions', { errorMessage: 'Failed to fetch subscriptions' })

// ---- HelcimPay.js ----

export const initializeHelcimPaySession = (payload) =>
  helcimFetch('/helcim-pay/initialize', {
    method: 'POST',
    body: payload,
    errorMessage: 'Payment initialization failed'
  })

/**
 * Generate a 25-character idempotency key using `crypto.randomBytes`.
 *
 * Each byte is mapped to the 62-character alphabet via modulo. The modulo
 * introduces a slight bias (256 % 62 = 8), but entropy from `randomBytes` —
 * not perfect uniformity — is what matters for an idempotency key.
 *
 * @returns {string} 25-character key.
 */
export function generateIdempotencyKey() {
  const bytes = randomBytes(25)
  let key = ''
  for (let i = 0; i < 25; i++) {
    key += IDEMPOTENCY_ALPHABET[bytes[i] % IDEMPOTENCY_ALPHABET.length]
  }
  return key
}

/**
 * Legacy stub — kept alive ONLY so `server/api/events/[id]/payment.post.js`
 * still imports cleanly. The direct purchase API was never implemented.
 * Always returns `{ success: false }`; callers surface the message to the user.
 */
export async function processHelcimPayment(_paymentData) {
  return {
    success: false,
    message: 'Direct purchase API not implemented; use HelcimPay.js flow'
  }
}
