import fetchHelper from './fetchHelper'
import { cooldownKey, assertNotCoolingDown, recordFailure, recordSuccess } from './authCooldown'

// Treat missing cookies and literal junk values some older flows wrote
// ('undefined'/'null'/'') as absent.
export const hasCtxValue = (value: any): boolean =>
  value !== undefined && value !== null && value !== '' && value !== 'undefined' && value !== 'null'

const decodeCredential = (value: any): string => {
  if (!hasCtxValue(value)) return ''
  try {
    return Buffer.from(String(value), 'base64').toString('utf8')
  } catch {
    return ''
  }
}

/**
 * Two-tier token refresh (auth/refresh → auth/tokens with stored credentials)
 * WITHOUT any cookie/sqlite side effects, so it is safe to call after response
 * headers are already sent (SSE polling).
 *
 * Throws WITHOUT calling iDempiere when the stored cookies cannot possibly
 * authenticate — empty/missing credentials or incomplete client/role/org
 * context used to flood AuthFailure.log with empty-username logins
 * ("Invalid User ID or Password" / "Missing organizationId parameter").
 * Failed credential logins are additionally rate-limited per session
 * (see authCooldown.ts).
 *
 * Returns the raw iDempiere token response ({ token, refresh_token, userId, … }).
 */
export async function refreshTokensRaw(event: any): Promise<any> {
  const token = getCookie(event, 'logship_it')
  const refreshToken = getCookie(event, 'logship_rt')
  const userId = getCookie(event, 'logship_user_id')
  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')
  const userName = decodeCredential(getCookie(event, 'logship_xu'))
  const password = decodeCredential(getCookie(event, 'logship_py'))

  const canRefresh = hasCtxValue(refreshToken) && hasCtxValue(userId) && hasCtxValue(clientId)
  // iDempiere's one-step login rejects clientId+roleId without organizationId
  // ("Missing organizationId parameter"). organizationId '0' (org "*") is valid.
  const canCredentialLogin = userName.trim() !== '' && password !== ''
    && hasCtxValue(clientId) && hasCtxValue(roleId) && hasCtxValue(organizationId)

  let resToken: any = null
  let refreshFailed = !canRefresh

  if (canRefresh) {
    try {
      resToken = await fetchHelper(event, 'auth/refresh', 'POST', token, {
        refresh_token: refreshToken,
        clientId: clientId,
        userId: userId
      })
    } catch {
      resToken = null
      refreshFailed = true
    }
  }

  // Credential fallback only when the refresh tier failed/was impossible —
  // mirrors the original try/catch semantics.
  if (refreshFailed) {
    if (!canCredentialLogin) {
      throw createError({
        statusCode: 401,
        statusMessage: 'Session expired',
        data: {
          status: 401,
          message: 'Token refresh not possible: no valid refresh token and no complete stored credentials/context — re-login required'
        }
      })
    }

    const key = cooldownKey(event)
    assertNotCoolingDown(key)

    const parameters: any = {
      clientId: clientId,
      roleId: roleId,
      organizationId: organizationId,
      language: hasCtxValue(language) ? language : 'en_US'
    }
    // Omit optional keys entirely when unset — never send ''.
    if (hasCtxValue(warehouseId)) parameters.warehouseId = warehouseId

    try {
      resToken = await fetchHelper(event, 'auth/tokens', 'POST', '', {
        userName: userName,
        password: password,
        parameters: parameters
      })
      recordSuccess(key)
    } catch (error: any) {
      recordFailure(key)
      throw error
    }
  }

  return resToken ?? {}
}

export default refreshTokensRaw
