import { string, date } from 'alga-js'
import fetchHelper from '../../utils/fetchHelper'
import sqliteHelper from '../../utils/sqliteHelper'
import setAuthCookie from '../../utils/setAuthCookie'
import { trimUser, trimRole, trimOrganization, trimClient, trimWarehouse } from '../../utils/trimCookieData'
import { enforceMobileWorkerGate } from '../../utils/mobileWorkerHelper'
import { AUTO_FINALIZE_SECRET } from '../../utils/autoFinalizeSecret'

const MOBILE_MAX_AGE = 30 * 24 * 60 * 60 // 30 days

/**
 * Extracts a foreign-key id from an ad_user (or similar) record. OData returns
 * FKs as `{ id, identifier, "model-name": "..." }`; other paths may return a
 * raw number. Tries multiple casing variants because iDempiere's response
 * casing varies by endpoint.
 */
function extractUserFkId(user: any, variants: string[]): number {
  if (!user || typeof user !== 'object') return 0
  let raw: any = null
  for (const k of variants) {
    if (user[k] != null) { raw = user[k]; break }
  }
  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 extractUserOrgId = (user: any) =>
  extractUserFkId(user, ['AD_Org_ID', 'ad_org_id', 'AD_ORG_ID', 'adOrgId'])

const extractUserWarehouseId = (user: any) =>
  extractUserFkId(user, ['M_Warehouse_ID', 'm_warehouse_id', 'M_WAREHOUSE_ID', 'mWarehouseId'])

const AUTO_DEBUG = process.env.IDEMPIERE_DEBUG === '1' ||
                   process.env.IDEMPIERE_DEBUG === 'true' ||
                   process.env.AUTO_FINALIZE_DEBUG === '1'
const dbg = (msg: string, obj?: any) => {
  if (AUTO_DEBUG) console.log('[autoFinalize]', msg, obj === undefined ? '' : JSON.stringify(obj))
}

/**
 * Look up the ad_user record by login using the CALLER's token. Used early
 * in the flow when we have a usable session token but not yet a userId.
 * Tries a couple of filter shapes because different iDempiere versions are
 * picky about combined predicates.
 */
async function findUserByLogin(event: any, token: string, clientId: any, login: string): Promise<any | null> {
  if (!token || !login) return null
  const safe = String(login).replace(/'/g, "''")
  const cIdNum = Number(clientId)

  // iDempiere REST filter fields are lowercase-first; PascalCase causes a
  // 400. Try `name` (the field iDempiere actually expects), then `value`
  // (the login id), then `eMail`.
  const filters = [
    `name eq '${safe}'`,
    `value eq '${safe}'`,
    `eMail eq '${safe}'`,
  ]

  for (const filter of filters) {
    const url = `models/ad_user?$filter=${string.urlEncode(filter)}&$top=10`
    const res: any = await fetchHelper(event, url, 'GET', token, null).catch((e: any) => {
      dbg('findUserByLogin fetch error', { filter, status: e?.status, message: e?.message })
      return null
    })
    const records: any[] | null = Array.isArray(res?.records) ? res.records
                                : Array.isArray(res)          ? res
                                : null
    dbg('findUserByLogin try', { filter, count: records?.length ?? 0 })
    if (records?.length) {
      const match = records.find(u => {
        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]
      return match
    }
  }
  return null
}

/**
 * Internal call to the privileged /auto-defaults endpoint. The endpoint is
 * gated by AUTO_FINALIZE_SECRET (in-memory, per-process) so this is the
 * only place that can reach it. It returns ONLY { adOrgId, mWarehouseId }.
 *
 * Passes the just-issued user token (callerToken) so the endpoint can try
 * the user's in-tenant token before the privileged service token.
 */
async function fetchAutoDefaults(
  event: any,
  clientId: any,
  login: string,
  callerToken?: string | null
): Promise<{ adOrgId: number; mWarehouseId: number; _trace?: any[] } | null> {
  if (!login) return null
  try {
    const res: any = await $fetch('/api/idempiere-auth/auto-defaults', {
      method: 'POST',
      headers: { 'x-auto-finalize-secret': AUTO_FINALIZE_SECRET },
      body: { userName: login, clientId, callerToken: callerToken ?? '' },
    })
    return {
      adOrgId:      Number(res?.adOrgId)      || 0,
      mWarehouseId: Number(res?.mWarehouseId) || 0,
      _trace:       Array.isArray(res?._trace) ? res._trace : [],
    }
  } catch (e: any) {
    console.log('[auto-defaults] fetch error from login.post.ts:', { status: e?.status, message: e?.message })
    return { adOrgId: 0, mWarehouseId: 0, _trace: [{ step: 'fetch-error', status: e?.status, message: e?.message ?? String(e) }] }
  }
}

/**
 * If the user has exactly 1 client and at least 1 role, finalize the session
 * server-side so the client lands on the dashboard without going through
 * /select-role. Primary path: pick the singleton org/warehouse when the role
 * gives access to exactly one — that case already works well. Fallback for
 * the multi-record case: read the user's default AD_Org_ID / M_Warehouse_ID
 * from ad_user. Returns null if neither path produces a value — caller falls
 * through to the clients-list response and the user picks manually.
 */
async function tryAutoFinalize(event: any, resToken: any, body: any, trace: any[] = []): Promise<any | null> {
  const tlog = (step: string, info?: any) => {
    const entry = info === undefined ? { step } : { step, ...info }
    trace.push(entry)
    console.log('[autoFinalize]', JSON.stringify(entry))
  }

  if (body.selectRole) { tlog('skip-selectRole'); return null }
  if (!resToken?.token) { tlog('bail-no-token'); return null }
  if (!Array.isArray(resToken?.clients)) { tlog('bail-no-clients-array'); return null }
  if (resToken.clients.length !== 1) { tlog('bail-multi-tenant', { clientsCount: resToken.clients.length }); return null }

  const c = resToken.clients[0]
  tlog('client', { id: c?.id, name: c?.name })
  const extract = (res: any, key: string): any[] | null => {
    if (!res) return null
    if (Array.isArray(res)) return res
    if (Array.isArray(res[key])) return res[key]
    return null
  }
  // iDempiere returns a wildcard "*" entry (id=0) for roles with "All" access.
  // It's not a real org/warehouse, so drop it before counting.
  const realOnly = (xs: any[]) => xs.filter(x => Number(x?.id) > 0)

  // 1. Roles available for that client
  let rolesRes: any = null
  try { rolesRes = await fetchHelper(event, `auth/roles?client=${c.id}`, 'GET', resToken.token, null) } catch (e: any) { tlog('bail-roles-fetch-failed', { status: e?.status, message: e?.message }); return null }
  const roles = extract(rolesRes, 'roles')
  if (!roles?.length) { tlog('bail-no-roles'); return null }
  const r = roles[0] // pick first role when multiple exist
  tlog('role', { id: r?.id, name: r?.name, total: roles.length })

  // 2. Orgs available for the role. Drop the "*" wildcard up front so the
  //    "is there really a singleton?" check below sees the real shape.
  let orgsRes: any = null
  try { orgsRes = await fetchHelper(event, `auth/organizations?client=${c.id}&role=${r.id}`, 'GET', resToken.token, null) } catch (e: any) { tlog('bail-orgs-fetch-failed', { status: e?.status, message: e?.message }); return null }
  const orgsAll = extract(orgsRes, 'organizations')
  if (!orgsAll?.length) { tlog('bail-no-orgs'); return null }
  const orgs = realOnly(orgsAll)
  if (!orgs.length) { tlog('bail-no-real-orgs', { allCount: orgsAll.length }); return null }
  tlog('orgs', { realCount: orgs.length, totalCount: orgsAll.length, sample: orgs.slice(0, 3).map((o: any) => ({ id: o.id, name: o.name })) })

  // 3. Resolve the user's AD_Org_ID / M_Warehouse_ID defaults so we can
  //    disambiguate multi-org / multi-warehouse cases. Singleton org doesn't
  //    need this, so skip the work there. We try several layers because
  //    iDempiere is inconsistent about giving us a query-capable token early:
  //      a) Did the initial POST already include userId? (rare, but free.)
  //      b) Filter ad_user with the unscoped token.
  //      c) Scout PUT to scope the token, then try userId / filter again.
  //      d) Privileged service-token lookup that returns ONLY the two FK
  //         defaults — see getUserAuthDefaults().
  let userRec: any = null
  let svcDefaults: { adOrgId: number; mWarehouseId: number } | null = null
  let scoutToken: string | null = null
  let scoutOrgId = 0
  let scoutWhId = 0

  dbg('orgs (real)', orgs.map((o: any) => ({ id: o.id, name: o.name ?? o.Name })))

  if (orgs.length > 1) {
    // (a) Some iDempiere builds include userId in the initial POST response.
    const directUserId = resToken.userId ?? resToken.user_id ?? resToken.user?.id
    if (directUserId) {
      dbg('resToken has userId', { directUserId })
      userRec = await fetchHelper(event, 'models/ad_user/' + directUserId, 'GET', resToken.token, null).catch((e: any) => {
        dbg('fetch by directUserId failed', { status: e?.status, message: e?.message })
        return null
      })
    }

    // (b) Filter lookup with the unscoped token.
    if (!userRec) userRec = await findUserByLogin(event, resToken.token, c.id, body.userName)

    // (c) Scout PUT to get a scoped token + (usually) a userId.
    if (!userRec) {
      const scoutOrg = orgs[0]
      let scoutWhRes: any = null
      try { scoutWhRes = await fetchHelper(event, `auth/warehouses?client=${c.id}&role=${r.id}&organization=${scoutOrg.id}`, 'GET', resToken.token, null) } catch (e: any) { dbg('scout warehouses fetch failed', { status: e?.status }) }
      const scoutWhsAll = extract(scoutWhRes, 'warehouses') || []
      const scoutWhsReal = realOnly(scoutWhsAll)
      // Prefer a real warehouse, but accept the wildcard "*" if that's all
      // the role exposes for this org — we just need something to make the
      // PUT valid; the final PUT below uses the user's true defaults.
      const scoutWh = scoutWhsReal[0] ?? scoutWhsAll[0]
      dbg('scout candidate', { orgId: scoutOrg.id, whId: scoutWh?.id, whCount: scoutWhsAll.length })

      if (scoutWh) {
        let scout: any = null
        try {
          scout = await fetchHelper(event, 'auth/tokens', 'PUT', resToken.token, {
            clientId:       Number(c.id),
            roleId:         Number(r.id),
            organizationId: Number(scoutOrg.id),
            warehouseId:    Number(scoutWh.id),
            language:       'en_US'
          })
        } catch (e: any) {
          dbg('scout PUT failed', { status: e?.status, message: e?.message })
        }
        dbg('scout PUT response shape', scout ? { hasToken: !!scout.token, userId: scout.userId, keys: Object.keys(scout).slice(0, 10) } : null)
        if (scout?.token) {
          scoutToken = scout.token
          scoutOrgId = Number(scoutOrg.id)
          scoutWhId  = Number(scoutWh.id)
          const scoutUserId = scout.userId ?? scout.user_id ?? scout.user?.id
          if (scoutUserId) {
            userRec = await fetchHelper(event, 'models/ad_user/' + scoutUserId, 'GET', scout.token, null).catch((e: any) => {
              dbg('fetch by scout.userId failed', { status: e?.status })
              return null
            })
          }
          // Filter lookup with the scoped token.
          if (!userRec) userRec = await findUserByLogin(event, scout.token, c.id, body.userName)
        }
      }
    }

    // (d) Privileged dedicated endpoint — only returns the two FK ids we
    //     actually need, gated by an in-memory per-process secret.
    if (!userRec) {
      const sd: any = await fetchAutoDefaults(event, c.id, body.userName, scoutToken ?? resToken.token)
      svcDefaults = sd ? { adOrgId: sd.adOrgId, mWarehouseId: sd.mWarehouseId } : null
      tlog('svcDefaults', { svcDefaults, innerTrace: sd?._trace })
    }

    tlog('userRec', userRec ? {
      id: userRec.id,
      ad_org_id: extractUserOrgId(userRec),
      m_warehouse_id: extractUserWarehouseId(userRec),
    } : null)
  } else {
    tlog('singleton-org-skip-userRec-lookup')
  }

  // Centralize the "what's the user's preferred org/warehouse?" answer so
  // the picks below don't care which lookup layer produced it.
  const userPrefOrgId = extractUserOrgId(userRec)   || (svcDefaults?.adOrgId      ?? 0)
  const userPrefWhId  = extractUserWarehouseId(userRec) || (svcDefaults?.mWarehouseId ?? 0)
  tlog('userPrefs', { userPrefOrgId, userPrefWhId })

  // 4. Choose the org. Singleton → that one. Multi → user's AD_Org_ID match.
  let o: any = null
  if (orgs.length === 1) {
    o = orgs[0]
  } else if (userPrefOrgId > 0) {
    o = orgs.find(org => Number(org.id) === userPrefOrgId) || null
  }
  if (!o) { tlog('bail-no-org-match', { orgsCount: orgs.length, userPrefOrgId }); return null }
  tlog('chosen-org', { id: o.id, name: o.name })

  // 5. Warehouses for the chosen org (skip the fetch if it's the scout's org —
  //    we already saw its warehouse list above, but the cheapest correct path
  //    is to just re-fetch with a definitely-scoped token).
  const queryToken = scoutToken ?? resToken.token
  let whRes: any = null
  try { whRes = await fetchHelper(event, `auth/warehouses?client=${c.id}&role=${r.id}&organization=${o.id}`, 'GET', queryToken, null) } catch { return null }
  const whs = realOnly(extract(whRes, 'warehouses') || [])
  if (!whs.length) { tlog('bail-no-warehouses'); return null }
  tlog('warehouses', { realCount: whs.length, sample: whs.slice(0, 3).map((w: any) => ({ id: w.id, name: w.name })) })

  let w: any = null
  if (whs.length === 1) {
    w = whs[0]
  } else if (userPrefWhId > 0) {
    w = whs.find(wh => Number(wh.id) === userPrefWhId) || null
  }
  if (!w) { tlog('bail-no-warehouse-match', { whsCount: whs.length, userPrefWhId }); return null }
  tlog('chosen-wh', { id: w.id, name: w.name })

  // 6. Final PUT — but if the scout already landed on the correct org+wh,
  //    skip it and reuse the scout's token.
  let resToken2: any
  if (scoutToken && Number(o.id) === scoutOrgId && Number(w.id) === scoutWhId) {
    resToken2 = { token: scoutToken }
    tlog('reuse-scout-token')
  } else {
    try {
      resToken2 = await fetchHelper(event, 'auth/tokens', 'PUT', queryToken, {
        clientId:       Number(c.id),
        roleId:         Number(r.id),
        organizationId: Number(o.id),
        warehouseId:    Number(w.id),
        language:       'en_US'
      })
      tlog('final-put-ok', { hasToken: !!resToken2?.token, hasUserId: !!resToken2?.userId })
    } catch (e: any) {
      tlog('bail-final-put-failed', { status: e?.status, message: e?.message })
      return null
    }
  }

  if (!resToken2?.token) { tlog('bail-no-final-token'); return null }
  tlog('success', { client: c.id, role: r.id, org: o.id, wh: w.id })

  setAuthCookie(event, 'logship_it', resToken2.token)
  if (resToken2.refresh_token) setAuthCookie(event, 'logship_rt', resToken2.refresh_token)
  setAuthCookie(event, 'logship_client_id',       String(c.id))
  setAuthCookie(event, 'logship_role_id',         String(r.id))
  setAuthCookie(event, 'logship_organization_id', String(o.id))
  setAuthCookie(event, 'logship_warehouse_id',    String(w.id))

  const out: any = { token: resToken2.token, toDashboard: true }

  if (resToken2.userId) {
    setAuthCookie(event, 'logship_user_id', resToken2.userId)
    out.userId = resToken2.userId
  }
  if (resToken2.language) {
    setAuthCookie(event, 'logship_language', resToken2.language)
    out.language = resToken2.language
  }

  const fetchOrNull = (url: string) =>
    fetchHelper(event, url, 'GET', resToken2.token, null).catch(() => null)

  // User record was already fetched during the intermediate PUT phase; reuse
  // it to save a round-trip.
  const [resClient, resRole, resOrg, resWarehouse] = await Promise.all([
    fetchOrNull('models/ad_client/' + c.id),
    fetchOrNull('models/ad_role/' + r.id),
    fetchOrNull('models/ad_org/' + o.id),
    fetchOrNull('models/m_warehouse/' + w.id)
  ])
  const resUser = userRec || (resToken2.userId ? await fetchOrNull('models/ad_user/' + resToken2.userId) : null)

  if (resUser)      { setAuthCookie(event, 'logship_user',         trimUser(resUser));             out.user = resUser }
  if (resClient)    { setAuthCookie(event, 'logship_client',       trimClient(resClient));         out.client = resClient }
  if (resRole)      { setAuthCookie(event, 'logship_role',         trimRole(resRole));             out.role = resRole }
  if (resOrg)       { setAuthCookie(event, 'logship_organization', trimOrganization(resOrg));      out.organization = resOrg }
  if (resWarehouse) { setAuthCookie(event, 'logship_warehouse',    trimWarehouse(resWarehouse));   out.warehouse = resWarehouse }

  enforceMobileWorkerGate(event, !!body?.mobileWorker, resUser, out)

  if (resToken2.refresh_token) {
    sqliteHelper(event, (db: any) => {
      const stmt = db.prepare('INSERT INTO refresh_tokens (refresh_token, token, expiration) VALUES (?, ?, ?)')
      stmt.run(resToken2.refresh_token, resToken2.token, date.add(date.now(''), 1))
      stmt.finalize()
    })
  }

  return out
}

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig()
  const body = await readBody(event)
  let data: any = {}
  const clientId = getCookie(event, 'logship_client_id')
  const roleId = getCookie(event, 'logship_role_id')
  const organizationId = getCookie(event, 'logship_organization_id')
  const warehouseId = getCookie(event, 'logship_warehouse_id')
  const language = getCookie(event, 'logship_language')

  // Set mobile worker flag FIRST so setAuthCookie can detect it
  if (body.mobileWorker) {
    setCookie(event, 'logship_mw', '1', { maxAge: MOBILE_MAX_AGE })
  }

  // Set PWA standalone flag so cookies get persistent maxAge
  // This prevents Android from clearing session cookies when the PWA is backgrounded
  if (body.pwa) {
    setCookie(event, 'logship_pwa', '1', { maxAge: MOBILE_MAX_AGE })
  }

  try {
    let newParameters: any = {}

    // One-step login only with COMPLETE context: iDempiere rejects
    // clientId+roleId without organizationId ("Missing organizationId
    // parameter"). With incomplete cookies we fall back to the two-step flow —
    // tryAutoFinalize resolves client/role/org/warehouse itself.
    const hasCtx = (v: any) => !!v && v !== 'undefined' && v !== 'null'

    if(!body.selectRole && hasCtx(clientId) && hasCtx(roleId) && hasCtx(organizationId)) {
      newParameters = {
        parameters: {
          clientId,
          roleId,
          organizationId
        }
      }

      if(hasCtx(warehouseId)) {
        //@ts-ignore
        newParameters['parameters']['warehouseId'] = warehouseId
      }
      if(hasCtx(language)) {
        //@ts-ignore
        newParameters['parameters']['language'] = language
      }
    }

    const resToken: any = await fetchHelper(event, 'auth/tokens', 'POST', '', {
      userName: body.userName,
      password: body.password,
      ...newParameters
    })
    if(resToken.token) {
      const logshipSession = string.uniqid()
      
      data['token'] = resToken.token
      data['session'] = logshipSession

      setAuthCookie(event, 'logship_session', logshipSession)
      setAuthCookie(event, 'logship_xu', Buffer.from(body.userName, 'binary').toString('base64'))
      setAuthCookie(event, 'logship_py', Buffer.from(body.password, 'binary').toString('base64'))
      setAuthCookie(event, 'logship_it', resToken.token)
      
      //await useStorage().setItem('logship_username_'+logshipSession, body.userName)
      //await useStorage().setItem('logship_password_'+logshipSession, body.password)
      //await useStorage().setItem('logship_token_'+logshipSession, resToken.token)
    }
    if(resToken?.clients) {
      setAuthCookie(event, 'logship_clients', resToken.clients)
      data['clients'] = resToken.clients

      const autoTrace: any[] = []
      const auto = await tryAutoFinalize(event, resToken, body, autoTrace)
      data['_autoTrace'] = autoTrace
      if (auto) {
        Object.assign(data, auto)
        delete data.clients
      } else {
        // tryAutoFinalize bailed — either by design (user ticked "Show
        // selection", so body.selectRole is true) or because it couldn't
        // pick the right org/warehouse. Either way, resolve the user's
        // ad_user defaults so /select-role can prefill the dropdowns.
        const defaults: any = await fetchAutoDefaults(event, resToken.clients[0]?.id, body.userName, resToken.token)
        console.log('[login] post-bail userDefaults:', defaults)
        data['_postBailDefaults'] = defaults
        if (defaults && (defaults.adOrgId > 0 || defaults.mWarehouseId > 0)) {
          setAuthCookie(event, 'logship_user_defaults', { adOrgId: defaults.adOrgId, mWarehouseId: defaults.mWarehouseId })
          data['userDefaults'] = { adOrgId: defaults.adOrgId, mWarehouseId: defaults.mWarehouseId }
        }
      }
      /*if(resToken?.token && (resToken.clients?.length ?? 0) === 1) {
        const clientId = resToken.clients[0].id
        const resClient: any = await fetchHelper(event, 'models/ad_client/'+clientId, 'GET', resToken.token, null)
        if(resClient) {
          setCookie(event, 'logship_client', resClient)
          data['client'] = resClient
        }
        const resRoles: any = await fetchHelper(event, 'models/ad_role', 'GET', resToken.token, null)
        if((resRoles?.length ?? 0) === 1) {
          const resRole: any = await fetchHelper(event, 'models/ad_role/'+resRoles[0].id, 'GET', resToken.token, null)
          if(resRole) {
            // Save complete role data in cookie
            setCookie(event, 'logship_role', resRole)
            data['role'] = resRole
          }
        }
        const resOrgs: any = await fetchHelper(event, 'models/ad_org', 'GET', resToken.token, null)
        if((resOrgs?.length ?? 0) === 1) {
          const resOrg: any = await fetchHelper(event, 'models/ad_org/'+resOrgs[0].id, 'GET', resToken.token, null)
          if(resOrg) {
            setCookie(event, 'logship_organization', resOrg)
            data['organization'] = resOrg
          }
        }
        const resWarehouses: any = await fetchHelper(event, 'models/m_warehouse', 'GET', resToken.token, null)
        if(resWarehouses) {
          const resWarehouse: any = await fetchHelper(event, 'models/m_warehouse/'+resWarehouses[0].id, 'GET', resToken.token, null)
          if(resWarehouse) {
            setCookie(event, 'logship_warehouse', resWarehouse)
            data['warehouse'] = resWarehouse
          }
        }
        setCookie(event, 'logship_language', 'en_US')
        data['language'] = 'en_US'
        const resToken2: any = await fetchHelper(event, 'auth/tokens', 'PUT', resToken.token, {
          clientId: data['client'],
          roleId: data['role'],
          organizationId: data['organization'],
          warehouseId: data['warehouse'],
          language: data['language']
        })
        if(resToken2.token) {
          setCookie(event, 'logship_it', resToken2.token) //await useStorage().setItem('logship_token_'+logshipSession, resToken.token)
          setCookie(event, 'logship_rt', resToken2.refresh_token)
          data['token'] = resToken2.token
          data['refresh_token'] = resToken2.refresh_token
        }
      }*/
    } else {
      if(resToken.userId) {
        setAuthCookie(event, 'logship_user_id', resToken.userId)
        data['userId'] = resToken.userId

        const fetchOrNull = (url: string) =>
          fetchHelper(event, url, 'GET', resToken.token, null).catch((e: any) => {
            if (e?.status === 401 || e?.status === 403) throw e
            return null
          })

        const [resUser, resClient, resRole, resOrg, resWarehouse] = await Promise.all([
          fetchOrNull('models/ad_user/' + resToken.userId),
          clientId       ? fetchOrNull('models/ad_client/' + clientId)       : Promise.resolve(null),
          roleId         ? fetchOrNull('models/ad_role/' + roleId)           : Promise.resolve(null),
          organizationId ? fetchOrNull('models/ad_org/' + organizationId)    : Promise.resolve(null),
          warehouseId    ? fetchOrNull('models/m_warehouse/' + warehouseId)  : Promise.resolve(null)
        ])

        if(resUser) {
          setAuthCookie(event, 'logship_user', trimUser(resUser))
          data['user'] = resUser
        }
        if(resClient) {
          setAuthCookie(event, 'logship_client', trimClient(resClient))
          data['client'] = resClient
        }
        if(resRole) {
          setAuthCookie(event, 'logship_role', trimRole(resRole))
          data['role'] = resRole
        }
        if(resOrg) {
          setAuthCookie(event, 'logship_organization', trimOrganization(resOrg))
          data['organization'] = resOrg
        }
        if(resWarehouse) {
          setAuthCookie(event, 'logship_warehouse', trimWarehouse(resWarehouse))
          data['warehouse'] = resWarehouse
        }
        data['toDashboard'] = true
        enforceMobileWorkerGate(event, !!body?.mobileWorker, resUser, data)
      }
      if(resToken.language) {
        setAuthCookie(event, 'logship_language', resToken.language)
        data['language'] = resToken.language
      }
    }
    if(resToken.refresh_token) {
      //@ts-ignore
      setAuthCookie(event, 'logship_rt', resToken.refresh_token)

      sqliteHelper(event, (db: any) => {
        const stmt = db.prepare("INSERT INTO refresh_tokens (refresh_token, token, expiration) VALUES (?, ?, ?)")
        stmt.run(resToken.refresh_token, resToken.token, date.add(date.now(''), 1))
        stmt.finalize()
      })
    }
  } catch(error: any) {
    // Handle authentication errors with user-friendly messages
    if (error.status === 401) {
      data = {
        body: {
          userName: body.userName,
          password: body.password
        }, 
        status: error.status,
        message: 'Wrong username or password entered. Please check your credentials and try again.'
      }
    } else if (error.status === 403) {
      data = {
        body: {
          userName: body.userName,
          password: body.password
        }, 
        status: error.status,
        message: 'Access denied. Please contact your administrator.'
      }
    } else if (error.status >= 500) {
      data = {
        body: {
          userName: body.userName,
          password: body.password
        }, 
        status: error.status,
        message: 'Server error. Please try again later or contact support.'
      }
    } else {
      // For other errors, provide a generic friendly message
      data = {
        body: {
          userName: body.userName,
          password: body.password
        }, 
        status: error.status,
        message: error.message || 'Login failed. Please try again.'
      }
    }
  }

  return data
})