/**
 * 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) plus the ad_org list (for the
 * owning organization's companyname prefix), 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[]; orgPrefix: string }

/** Length of the owning-org tag shown in front of a matched caller name ("LogY: Max Mustermann"). */
export const ORG_PREFIX_LEN = 4

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 opts = { headers: { Authorization: 'Bearer ' + token, Accept: 'application/json' }, retry: 0, timeout: FETCH_TIMEOUT_MS } as const
  // ad_org is a separate tiny list (17 rows on prod) instead of a per-row $expand.
  const [res, orgRes]: any[] = await Promise.all([
    $fetch(`${config.api.url}/models/ad_user?$select=Name,Phone,Phone2,AD_Org_ID&$filter=${filter}&$top=5000`, opts),
    $fetch(`${config.api.url}/models/ad_org?$select=companyname&$top=1000`, opts)
  ])

  // First ORG_PREFIX_LEN characters of ad_org.companyname; orgs without one (e.g. "*") get no prefix.
  const orgPrefixes = new Map<number, string>()
  for (const o of (orgRes?.records || [])) {
    const prefix = String(o?.companyname ?? '').trim().slice(0, ORG_PREFIX_LEN).trim()
    if (prefix) orgPrefixes.set(Number(o?.id), prefix)
  }

  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, orgPrefix: orgPrefixes.get(Number(r?.AD_Org_ID?.id)) || '' })
  }
  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
}
