/**
 * In-memory reverse phone-number directory backing
 * server/api/telephony/cid-lookup/[number].get.ts. Split out from the route
 * so server/plugins/warm-cid-lookup.ts can pre-fill it at server boot
 * without importing a dynamic route file.
 *
 * Source is iDempiere ad_user (phone/phone2), fetched with the SERVICE token
 * (config.api.idempieretoken — same pattern as phoneActivity.ts/extensions.ts;
 * there is no user session here). The full "active users with a phone number"
 * set is small (~470 of ~62k ad_user rows on prod) and cheap (~150 ms), so it
 * is kept whole in memory with a short TTL rather than queried per lookup —
 * see the file header of cid-lookup/[number].get.ts for why a live per-call
 * iDempiere dependency is the wrong shape for a synchronous Asterisk CURL().
 */
import { string } from 'alga-js'

export interface DirectoryEntry { name: string; digits: string[] }

export const digitsOf = (n: any) => String(n ?? '').replace(/\D+/g, '')
/** Last N significant digits, used to compare numbers regardless of country/trunk prefix. */
export const tailOf = (digits: string, len = 8) => digits.slice(-len)
/** True if one digit string ends with the other (handles a shorter stored/caller number). */
export const tailsMatch = (a: string, b: string, minLen = 6): boolean => {
  if (a.length < minLen || b.length < minLen) return false
  return a.length >= b.length ? a.endsWith(b) : b.endsWith(a)
}

let directoryCache: DirectoryEntry[] = []
let cacheExpiresAt = 0
let refreshInFlight: Promise<void> | null = null

const CACHE_TTL_MS = 60_000 // how stale a name can be after an iDempiere edit
const BACKOFF_MS = 15_000   // don't hammer iDempiere every call while it's down
const FETCH_TIMEOUT_MS = 3_000 // cold-start budget; well inside Asterisk's CURL wait

async function fetchDirectory(): Promise<DirectoryEntry[]> {
  const config = useRuntimeConfig()
  const token = (config.api as any)?.idempieretoken
  if (!token) return []

  const filter = string.urlEncode('IsActive eq true and (phone neq null or phone2 neq null)')
  const res: any = await $fetch(
    `${config.api.url}/models/ad_user?$select=Name,Phone,Phone2&$filter=${filter}&$top=5000`,
    { headers: { Authorization: 'Bearer ' + token, Accept: 'application/json' }, retry: 0, timeout: FETCH_TIMEOUT_MS }
  )

  const out: DirectoryEntry[] = []
  for (const r of (res?.records || [])) {
    const name = String(r?.Name ?? r?.name ?? '').trim()
    if (!name) continue
    const digits = [digitsOf(r?.Phone ?? r?.phone), digitsOf(r?.Phone2 ?? r?.phone2)].filter((d) => d.length >= 6)
    if (digits.length) out.push({ name, digits })
  }
  return out
}

/** Warm cache: instant. Expired cache: served as-is, refreshed in the background. Cold: blocks once. */
export async function getCidDirectory(): Promise<DirectoryEntry[]> {
  if (Date.now() < cacheExpiresAt) return directoryCache

  if (directoryCache.length === 0) {
    try {
      directoryCache = await fetchDirectory()
      cacheExpiresAt = Date.now() + CACHE_TTL_MS
    } catch (err: any) {
      console.warn('[cid-lookup] iDempiere directory fetch failed:', err?.message || err)
      cacheExpiresAt = Date.now() + BACKOFF_MS
    }
    return directoryCache
  }

  if (!refreshInFlight) {
    refreshInFlight = fetchDirectory()
      .then((fresh) => { directoryCache = fresh; cacheExpiresAt = Date.now() + CACHE_TTL_MS })
      .catch((err: any) => {
        console.warn('[cid-lookup] iDempiere directory refresh failed:', err?.message || err)
        cacheExpiresAt = Date.now() + BACKOFF_MS
      })
      .finally(() => { refreshInFlight = null })
  }
  return directoryCache
}
