import { Queue, QueueEvents } from 'bullmq';
import { redis } from './redis';

/**
 * BullMQ queue/job names mirrored 1:1 from `apps/bot/src/queues.ts` and
 * `apps/bot/src/jobs.ts`. The WebUI has no discord.js Client, so any
 * dashboard action that needs to touch Discord (posting a giveaway message,
 * drawing winners, restoring a guild backup) is enqueued here and picked up
 * by the bot's existing workers - the WebUI never talks to Discord directly
 * for these actions.
 *
 * Queues and QueueEvents are created lazily on first use. Eager construction
 * at import time would open Redis connections during `next build` (where
 * Redis is intentionally unavailable), flooding the build log with
 * ECONNREFUSED errors.
 */
export const giveawayQueueName = 'giveaways';
export const scheduleQueueName = 'schedules';
export const guildBackupQueueName = 'guild-backups';
export const suggestionsQueueName = 'suggestions';

const globalForQueues = globalThis as unknown as {
  __nexumiGiveawayQueue?: Queue;
  __nexumiScheduleQueue?: Queue;
  __nexumiGuildBackupQueue?: Queue;
  __nexumiSuggestionsQueue?: Queue;
  __nexumiGiveawayQueueEvents?: QueueEvents;
  __nexumiGuildBackupQueueEvents?: QueueEvents;
  __nexumiSuggestionsQueueEvents?: QueueEvents;
};

function queueConnection() {
  return redis;
}

/** QueueEvents needs its own connection; duplicate inherits lazyConnect. */
function queueEventsConnection() {
  return redis.duplicate();
}

export function getGiveawayQueue(): Queue {
  globalForQueues.__nexumiGiveawayQueue ??= new Queue(giveawayQueueName, {
    connection: queueConnection()
  });
  return globalForQueues.__nexumiGiveawayQueue;
}

export function getScheduleQueue(): Queue {
  globalForQueues.__nexumiScheduleQueue ??= new Queue(scheduleQueueName, {
    connection: queueConnection()
  });
  return globalForQueues.__nexumiScheduleQueue;
}

export function getGuildBackupQueue(): Queue {
  globalForQueues.__nexumiGuildBackupQueue ??= new Queue(guildBackupQueueName, {
    connection: queueConnection()
  });
  return globalForQueues.__nexumiGuildBackupQueue;
}

export function getSuggestionsQueue(): Queue {
  globalForQueues.__nexumiSuggestionsQueue ??= new Queue(suggestionsQueueName, {
    connection: queueConnection()
  });
  return globalForQueues.__nexumiSuggestionsQueue;
}

/**
 * QueueEvents instances are only needed for jobs where the dashboard needs
 * to wait for the bot to finish (e.g. ending a giveaway so the winners list
 * is immediately visible).
 */
export function getGiveawayQueueEvents(): QueueEvents {
  globalForQueues.__nexumiGiveawayQueueEvents ??= new QueueEvents(giveawayQueueName, {
    connection: queueEventsConnection()
  });
  return globalForQueues.__nexumiGiveawayQueueEvents;
}

export function getGuildBackupQueueEvents(): QueueEvents {
  globalForQueues.__nexumiGuildBackupQueueEvents ??= new QueueEvents(guildBackupQueueName, {
    connection: queueEventsConnection()
  });
  return globalForQueues.__nexumiGuildBackupQueueEvents;
}

export function getSuggestionsQueueEvents(): QueueEvents {
  globalForQueues.__nexumiSuggestionsQueueEvents ??= new QueueEvents(suggestionsQueueName, {
    connection: queueEventsConnection()
  });
  return globalForQueues.__nexumiSuggestionsQueueEvents;
}

const DEFAULT_WAIT_TIMEOUT_MS = 15_000;

/**
 * Adds a job and waits (bounded) for the bot worker to finish it, so the
 * dashboard can immediately reflect the result instead of polling. Falls
 * back to "not confirmed" (does not throw) on timeout - the DB row created
 * by the worker is still the source of truth and will show up on next load.
 */
export async function addJobAndAwait<T = unknown>(
  queue: Queue,
  queueEvents: QueueEvents,
  jobName: string,
  data: unknown,
  options: Parameters<Queue['add']>[2] = {},
  timeoutMs = DEFAULT_WAIT_TIMEOUT_MS
): Promise<{ confirmed: boolean; result: T | null }> {
  const job = await queue.add(jobName, data, options);
  try {
    const result = (await job.waitUntilFinished(queueEvents, timeoutMs)) as T;
    return { confirmed: true, result };
  } catch {
    return { confirmed: false, result: null };
  }
}
