import { string } from 'alga-js'
import getDhlMainCredential, { invalidateDhlMainCredential, type DhlMainCredential } from './dhlMainCredential'

// DHL ROPC tokens are app-level (not per-user), so we share one cached token across
// all operators hitting the same Node process. TTL of 50 minutes leaves a safety
// margin under DHL's typical 1-hour token lifetime. The per-user cookie is kept
// as a fallback so a fresh process can pick up a still-valid token without re-auth.
const TOKEN_TTL_MS = 50 * 60 * 1000
const COOKIE_MAX_AGE_S = 50 * 60
let cachedToken: string | null = null
let cachedTokenExpiry = 0

// Wraps a failed DHL OAuth (ROPC) token request into a plain, human-readable
// Error with no `.data` payload of its own. That's deliberate: every catch
// block downstream (commission routes' itemErrors/status.detail extraction,
// errorHandlingHelper's generic fallback) prioritizes fields off `err.data`
// (the shipment-creation error shape) before falling back to `err.message` —
// stripping `.data` here means those all naturally surface THIS message
// instead of a raw/cryptic OAuth error body or "DHL API Error".
//
// Three cases, told apart by whether DHL answered at all:
//   no HTTP status  → network/DNS/timeout: the credentials were never checked
//                     (2026-10-21: an Unbound SERVFAIL on api-eu.dhl.com was
//                     chased as a wrong password for an hour because of the
//                     old one-size-fits-all message)
//   5xx             → DHL-side outage, credentials not the problem either
//   anything else   → DHL rejected the login (invalid_grant, 401, 400 …)
const httpStatusOf = (err: any): number =>
  Number(err?.statusCode ?? err?.status ?? err?.response?.status ?? 0) || 0

// ofetch nests the socket error: FetchError.cause = TypeError('fetch failed'),
// whose .cause carries the Node code (ENOTFOUND, ECONNREFUSED, ETIMEDOUT, …).
// A refused connection arrives as an AggregateError (one entry per address
// tried), so the code sits on `.errors[0]` instead of the error itself.
const networkCodeOf = (err: any): string => {
  let cur = err
  for (let i = 0; i < 5 && cur; i++) {
    if (typeof cur?.code === 'string' && cur.code !== '') return cur.code
    const first = Array.isArray(cur?.errors) ? cur.errors[0] : undefined
    if (typeof first?.code === 'string' && first.code !== '') return first.code
    cur = cur.cause
  }
  return ''
}

export function buildDhlAuthError(err: any): Error {
  const detail = err?.data?.error_description || err?.data?.error || err?.data?.detail
    || err?.data?.title || err?.statusMessage || err?.message || 'Unbekannter Fehler'
  const status = httpStatusOf(err)

  if (status === 0) {
    const code = networkCodeOf(err)
    const hint = code === 'ENOTFOUND' || code === 'EAI_AGAIN'
      ? 'DNS-Auflösung von api-eu.dhl.com fehlgeschlagen (Resolver/Firewall prüfen)'
      : code === 'ETIMEDOUT' || code === 'UND_ERR_CONNECT_TIMEOUT' || code === 'UND_ERR_HEADERS_TIMEOUT'
        ? 'Zeitüberschreitung beim Verbindungsaufbau (Internet/Firewall prüfen)'
        : code === 'ECONNREFUSED' || code === 'ECONNRESET'
          ? 'Verbindung abgelehnt/abgebrochen (Firewall/Proxy prüfen)'
          : 'keine Antwort von DHL (Netzwerk/DNS prüfen)'
    return new Error(`DHL-API nicht erreichbar — ${hint}. Die Zugangsdaten wurden dabei NICHT geprüft.${code ? ' Fehlercode: ' + code + '.' : ''} Details: ${detail}`)
  }

  if (status >= 500) {
    return new Error(`DHL-API gestört (HTTP ${status}) — DHL hat den Anmelde-Request nicht verarbeitet, die Zugangsdaten sind nicht die Ursache. Bitte später erneut versuchen. Details: ${detail}`)
  }

  return new Error(`DHL-Anmeldung fehlgeschlagen (HTTP ${status}) — bitte die DHL-Zugangsdaten (Benutzername/Passwort/API-Key) auf dem DHL-Shipper bzw. der Order Source prüfen. Details: ${detail}`)
}

const fetchTokenWith = async (cred: DhlMainCredential) => {
  try {
    return await $fetch(cred.dhlurl+'/account/auth/ropc/v1/token', {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: `grant_type=password&username=${string.urlEncode(cred.dhluser)}&password=${string.urlEncode(cred.dhlpass)}&client_id=${string.urlEncode(cred.dhlkey)}&client_secret=${string.urlEncode(cred.dhlsecret)}`
    })
  } catch(err: any) {
    throw buildDhlAuthError(err)
  }
}

const getToken = async (event: any) => {
  const cred = await getDhlMainCredential(event)
  try {
    return await fetchTokenWith(cred)
  } catch(err: any) {
    // The credentials may have just been rotated on the DHL shipper record —
    // bypass the 10-min credential cache once and retry only if they changed.
    invalidateDhlMainCredential()
    const fresh = await getDhlMainCredential(event)
    if(fresh.dhlpass !== cred.dhlpass || fresh.dhluser !== cred.dhluser || fresh.dhlkey !== cred.dhlkey || fresh.dhlsecret !== cred.dhlsecret || fresh.dhlurl !== cred.dhlurl) {
      return await fetchTokenWith(fresh)
    }
    throw err
  }
}

const persistToken = (event: any, token: string) => {
  cachedToken = token
  cachedTokenExpiry = Date.now() + TOKEN_TTL_MS
  setCookie(event, 'logship_bdhtl', token, { maxAge: COOKIE_MAX_AGE_S })
}

const resolveToken = async (event: any): Promise<string> => {
  if (cachedToken && cachedTokenExpiry > Date.now()) {
    return cachedToken
  }
  const cookieToken = getCookie(event, 'logship_bdhtl') ?? ''
  if (cookieToken !== '') {
    return cookieToken
  }
  const res = await getToken(event)
  if (res?.access_token) {
    persistToken(event, res.access_token)
    return res.access_token
  }
  return ''
}

export default async function dhlHelper(event: any, url: string, method: string = 'GET', body: any) {
  const cred = await getDhlMainCredential(event)
  let token = await resolveToken(event)

  let options: any = {}
  if(body) {
    options = {
      ...options,
      body: body
    }
  }

  try {
    return await $fetch(cred.dhlurl+'/'+url, {
      method: method,
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
        Authorization: 'Bearer ' + token
      },
      ...options
    })
  } catch(err: any) {
    // If it's a validation error (400), don't retry with new token - it won't help
    if(err?.statusCode === 400 || err?.status === 400) {
      console.error('[DHL API Error] Validation error (400):', JSON.stringify(err?.data, null, 2))
      throw err
    }

    // Invalidate cache: token might be revoked even though our TTL says it's good.
    cachedToken = null
    cachedTokenExpiry = 0

    const res2 = await getToken(event)
    if(res2?.access_token) {
      token = res2.access_token
      persistToken(event, token)
    }

    return await $fetch(cred.dhlurl+'/'+url, {
      method: method,
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
        Authorization: 'Bearer ' + token
      },
      ...options
    })
  }

}