/**
 * Smart queue.
 *
 * Postgres is the source of truth; Redis holds a sorted-set mirror so "what's
 * next" is an O(log n) read shared across every server instance. The score
 * encodes both dimensions of priority:
 *
 *     score = priority * 1e12 - queuedAtMillis
 *
 * Higher score = served first, so urgent beats normal, and within one priority
 * the longest-waiting chat wins.
 */
import { redis, KEYS } from '../../redis/client.js';
import { listQueued } from './service.js';
import { logger } from '../../utils/logger.js';
import type { ConversationDTO } from '../types.js';

const PRIORITY_WEIGHT = 1e12;

export function queueScore(priority: number, queuedAt: Date | string): number {
  const ms = new Date(queuedAt).getTime();
  return priority * PRIORITY_WEIGHT - ms;
}

export async function enqueue(conv: ConversationDTO): Promise<void> {
  await redis.zadd(KEYS.queue, queueScore(conv.priority, conv.queuedAt), conv.id);
}

export async function dequeue(conversationId: string): Promise<void> {
  await redis.zrem(KEYS.queue, conversationId);
}

export async function queueSize(): Promise<number> {
  return redis.zcard(KEYS.queue);
}

/** 1-based position, or null when the chat is no longer queued. */
export async function queuePosition(conversationId: string): Promise<number | null> {
  const rank = await redis.zrevrank(KEYS.queue, conversationId);
  return rank === null ? null : rank + 1;
}

/** Queued conversation ids, best-first. */
export async function queueIds(limit = 100): Promise<string[]> {
  return redis.zrevrange(KEYS.queue, 0, limit - 1);
}

/**
 * Rebuild the Redis mirror from Postgres.
 * Called at boot so a Redis flush (or a cold start) is self-healing.
 */
export async function rebuildQueue(): Promise<number> {
  const queued = await listQueued();
  const pipeline = redis.pipeline();
  pipeline.del(KEYS.queue);
  for (const conv of queued) {
    pipeline.zadd(KEYS.queue, queueScore(conv.priority, conv.queuedAt), conv.id);
  }
  await pipeline.exec();
  logger.info('queue rebuilt from database', { size: queued.length });
  return queued.length;
}

/**
 * Suggest the best agent for a chat: online, under capacity, least loaded.
 * Used for the auto-assign path; manual accept from the queue bypasses it.
 */
export async function suggestAgent(
  candidates: { id: string; presence: string; activeChats?: number; maxConcurrentChats: number }[],
): Promise<string | null> {
  const eligible = candidates
    .filter((a) => a.presence === 'online' && (a.activeChats ?? 0) < a.maxConcurrentChats)
    .sort((a, b) => (a.activeChats ?? 0) - (b.activeChats ?? 0));
  return eligible[0]?.id ?? null;
}
