import { string } from 'alga-js'
import fetchHelper from '../../utils/fetchHelper'
import { AUTO_FINALIZE_SECRET } from '../../utils/autoFinalizeSecret'

/**
 * Privileged read for /api/idempiere-auth/login auto-finalize ONLY.
 *
 * Returns just the two ad_user defaults the auto-finalize flow needs to
 * pick the right org / warehouse from a multi-record list:
 *   { adOrgId, mWarehouseId }
 *
 * SECURITY:
 *   - Gated by an in-memory secret (autoFinalizeSecret.ts) generated per
 *     process via crypto.randomBytes(32). The login handler in this same
 *     Node process is the only thing that knows the value — it's never
 *     logged, never sent to the client, never persisted. External callers
 *     can't hit this endpoint.
 *   - Uses the iDempiere service token (config.api.idempieretoken). The
 *     blast radius is bounded: this endpoint never returns the full
 *     ad_user record, never returns the password, never returns anything
 *     beyond the two FK ids.
 *   - Never call this from anywhere else. If you need ad_user data for a
 *     different reason, write a different endpoint with its own scope.
 *
 *   (We considered also checking the password against ad_user.Password as
 *   defense in depth, but: anyone with valid credentials can call /login
 *   normally and get strictly more data, so the extra check wouldn't
 *   protect any privilege escalation that the in-memory secret doesn't
 *   already block. Skipped to keep the surface small.)
 */

const extractFkId = (raw: any): number => {
  if (raw == null) return 0
  if (typeof raw === 'number') return raw
  if (typeof raw === 'string') { const n = Number(raw); return Number.isFinite(n) ? n : 0 }
  if (typeof raw === 'object') {
    const id = raw.id ?? raw.ID
    if (typeof id === 'number') return id
    if (typeof id === 'string') { const n = Number(id); return Number.isFinite(n) ? n : 0 }
  }
  return 0
}

const pickField = (rec: any, names: string[]): any => {
  if (!rec || typeof rec !== 'object') return null
  for (const n of names) if (rec[n] != null) return rec[n]
  return null
}

export default defineEventHandler(async (event) => {
  const trace: any[] = []
  const log = (step: string, info?: any) => {
    const entry = info === undefined ? { step } : { step, ...info }
    trace.push(entry)
    console.log('[auto-defaults]', JSON.stringify(entry))
  }

  // Gate: only the in-process login handler knows the secret.
  const provided = getHeader(event, 'x-auto-finalize-secret')
  if (!provided || provided !== AUTO_FINALIZE_SECRET) {
    throw createError({ statusCode: 403, statusMessage: 'Forbidden' })
  }

  const config = useRuntimeConfig()
  const serviceToken: string | undefined = (config.api as any)?.idempieretoken

  const body = await readBody(event).catch(() => ({} as any))
  const userName = String(body?.userName ?? '').trim()
  const clientId = body?.clientId
  // Optional caller token (e.g. the just-issued resToken.token from /login).
  const callerToken = String(body?.callerToken ?? '').trim() || null

  log('input', { userName, clientId, hasCallerToken: !!callerToken, hasServiceToken: !!serviceToken })

  if (!userName || !clientId) {
    log('missing-input')
    return { adOrgId: 0, mWarehouseId: 0, _trace: trace }
  }

  const safe = userName.replace(/'/g, "''")
  const cIdNum = Number(clientId)
  // iDempiere REST filter fields are lowercase-first (same convention as
  // writes — see memory: writes require lowercase-first, PascalCase rejected).
  // Try `name`, then `value` (login id), then `eMail`.
  const filters = [`name eq '${safe}'`, `value eq '${safe}'`, `eMail eq '${safe}'`]

  // Tokens to try in order: caller (user) token first, then service token.
  const tokens: { tk: string; kind: string }[] = []
  if (callerToken) tokens.push({ tk: callerToken, kind: 'user' })
  if (serviceToken && serviceToken !== callerToken) tokens.push({ tk: serviceToken, kind: 'service' })
  if (!tokens.length) {
    log('no-token-available')
    return { adOrgId: 0, mWarehouseId: 0, _trace: trace }
  }

  for (const { tk, kind } of tokens) {
    for (const filter of filters) {
      // No $select — some iDempiere builds reject it (commas aren't always
      // url-encoded properly and / or the option isn't supported). The
      // function still only ever returns the two FK ids from the response,
      // so the wire format being wider doesn't widen what this endpoint
      // exposes to callers.
      const url = `models/ad_user?$filter=${string.urlEncode(filter)}&$top=10`
      const res: any = await fetchHelper(event, url, 'GET', tk, null).catch((e: any) => {
        log('fetch-failed', { kind, filter, status: e?.status, message: e?.message ?? String(e) })
        return null
      })
      const records: any[] | null = Array.isArray(res?.records) ? res.records
                                  : Array.isArray(res)          ? res
                                  : null
      log('try', {
        kind,
        filter,
        count: records?.length ?? 0,
        // Help spot whether iDempiere returned the field shape we expect.
        firstRecordKeys: records?.[0] ? Object.keys(records[0]).slice(0, 12) : null,
      })
      if (!records?.length) continue

      const match = records.find((u: any) => {
        const cId = (u?.AD_Client_ID && typeof u.AD_Client_ID === 'object') ? u.AD_Client_ID.id : u?.AD_Client_ID
        return Number(cId) === cIdNum
      }) ?? records[0]

      const result = {
        adOrgId:      extractFkId(pickField(match, ['AD_Org_ID', 'ad_org_id', 'AD_ORG_ID', 'adOrgId'])),
        mWarehouseId: extractFkId(pickField(match, ['M_Warehouse_ID', 'm_warehouse_id', 'M_WAREHOUSE_ID', 'mWarehouseId'])),
      }
      log('resolved', { kind, ...result, matchedRecordId: match?.id })
      return { ...result, _trace: trace }
    }
  }
  log('no-record-found')
  return { adOrgId: 0, mWarehouseId: 0, _trace: trace }
})
