import { DateTime } from 'luxon'
import Organization from '#models/organization'

/**
 * Clock — abstraction time-sensitive de l'app.
 *
 * En prod (`org.demoMode = false`, ou pas d'orgId fourni), retourne
 * `DateTime.utc()`. C'est l'API à utiliser **par défaut** dans tous les
 * services qui font des comparaisons de dates (relance scheduler,
 * checkin scheduler, dashboard KPIs, jobs BullMQ).
 *
 * En mode démo (`org.demoMode = true` ET `org.virtualNow != null`),
 * retourne le `virtualNow` stocké sur l'org. C'est ce qui permet
 * "d'avancer dans le temps" pendant une démo sans toucher au système.
 *
 * **Garde-fou prod** : si `orgId` est absent, on retourne toujours
 * l'horloge système — on ne fait JAMAIS un side-effect demoMode si
 * on n'a pas explicitement le contexte d'une org.
 *
 * Cette abstraction sert aussi aux tests (on pourra mock plus tard
 * via un singleton injectable, V2). En V1, on lit la DB à chaque
 * appel : c'est fonctionnel, et le cache n'a pas de sens vu que la
 * valeur peut bouger pendant une démo.
 *
 * Cache : on prend un cache mémoire très court (250ms) pour ne pas
 * spammer la DB quand un même handler appelle clock.now() plusieurs
 * fois. Invalidé à chaque écriture (cf. setVirtualNow).
 */

type CachedClock = {
  demoMode: boolean
  virtualNow: DateTime | null
  fetchedAt: number
}

const CACHE_TTL_MS = 250
const cache = new Map<string, CachedClock>()

async function loadOrg(orgId: string): Promise<CachedClock> {
  const cached = cache.get(orgId)
  if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) return cached

  const org = await Organization.find(orgId)
  const entry: CachedClock = {
    demoMode: org?.demoMode ?? false,
    virtualNow: org?.virtualNow ?? null,
    fetchedAt: Date.now(),
  }
  cache.set(orgId, entry)
  return entry
}

/**
 * Heure courante. Async pour permettre la lecture DB en mode démo.
 *
 *   const now = await clock.now(invoice.organizationId)
 *
 * Si `orgId` est `null` / `undefined`, retourne `DateTime.utc()`
 * synchronement-compatible (pas de side-effect démo).
 */
export async function now(orgId?: string | null): Promise<DateTime> {
  if (!orgId) return DateTime.utc()
  const c = await loadOrg(orgId)
  if (c.demoMode && c.virtualNow) return c.virtualNow
  return DateTime.utc()
}

/**
 * Variante sync — utile dans des chemins où on n'a pas d'orgId
 * (création de tokens auth par exemple). Comportement strictement
 * identique à `DateTime.utc()`. Existe pour faciliter une migration
 * incrémentale : remplacer `DateTime.utc()` ou `DateTime.now()` par
 * `clockSync()` rend explicite que c'est une horloge système.
 */
export function nowSync(): DateTime {
  return DateTime.utc()
}

/**
 * Met à jour `virtual_now` sur une org (utilisé par /demo/tick).
 * Invalide le cache pour que le prochain `now(orgId)` renvoie la
 * nouvelle valeur sans attendre le TTL.
 */
export async function setVirtualNow(orgId: string, virtualNow: DateTime): Promise<void> {
  const org = await Organization.findOrFail(orgId)
  org.virtualNow = virtualNow
  await org.save()
  cache.delete(orgId)
}

/**
 * Active/désactive le mode démo. Côté `start`, on initialise
 * `virtualNow` à maintenant (UTC) si pas déjà set.
 */
export async function setDemoMode(orgId: string, enabled: boolean): Promise<void> {
  const org = await Organization.findOrFail(orgId)
  org.demoMode = enabled
  if (enabled && !org.virtualNow) {
    org.virtualNow = DateTime.utc()
  }
  if (!enabled) {
    org.virtualNow = null
  }
  await org.save()
  cache.delete(orgId)
}

/** Invalidation manuelle (tests). */
export function clearClockCache(): void {
  cache.clear()
}
