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';
export const messagesQueueName = 'messages';
export const verificationQueueName = 'verification';
export const automodQueueName = 'automod';
export const presenceQueueName = 'presence';
export const selfrolesQueueName = 'selfroles';
export const ticketsQueueName = 'tickets';
export const statsQueueName = 'stats';

const globalForQueues = globalThis as unknown as {
  __nexumiGiveawayQueue?: Queue;
  __nexumiScheduleQueue?: Queue;
  __nexumiGuildBackupQueue?: Queue;
  __nexumiSuggestionsQueue?: Queue;
  __nexumiMessagesQueue?: Queue;
  __nexumiVerificationQueue?: Queue;
  __nexumiAutomodQueue?: Queue;
  __nexumiPresenceQueue?: Queue;
  __nexumiSelfrolesQueue?: Queue;
  __nexumiTicketsQueue?: Queue;
  __nexumiStatsQueue?: Queue;
  __nexumiGiveawayQueueEvents?: QueueEvents;
  __nexumiGuildBackupQueueEvents?: QueueEvents;
  __nexumiSuggestionsQueueEvents?: QueueEvents;
  __nexumiMessagesQueueEvents?: QueueEvents;
  __nexumiSelfrolesQueueEvents?: QueueEvents;
  __nexumiVerificationQueueEvents?: QueueEvents;
  __nexumiTicketsQueueEvents?: QueueEvents;
  __nexumiStatsQueueEvents?: 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;
}

export function getMessagesQueue(): Queue {
  globalForQueues.__nexumiMessagesQueue ??= new Queue(messagesQueueName, {
    connection: queueConnection()
  });
  return globalForQueues.__nexumiMessagesQueue;
}

export function getVerificationQueue(): Queue {
  globalForQueues.__nexumiVerificationQueue ??= new Queue(verificationQueueName, {
    connection: queueConnection()
  });
  return globalForQueues.__nexumiVerificationQueue;
}

export function getAutomodQueue(): Queue {
  globalForQueues.__nexumiAutomodQueue ??= new Queue(automodQueueName, {
    connection: queueConnection()
  });
  return globalForQueues.__nexumiAutomodQueue;
}

export function getPresenceQueue(): Queue {
  globalForQueues.__nexumiPresenceQueue ??= new Queue(presenceQueueName, {
    connection: queueConnection()
  });
  return globalForQueues.__nexumiPresenceQueue;
}

export function getSelfrolesQueue(): Queue {
  globalForQueues.__nexumiSelfrolesQueue ??= new Queue(selfrolesQueueName, {
    connection: queueConnection()
  });
  return globalForQueues.__nexumiSelfrolesQueue;
}

export function getTicketsQueue(): Queue {
  globalForQueues.__nexumiTicketsQueue ??= new Queue(ticketsQueueName, {
    connection: queueConnection()
  });
  return globalForQueues.__nexumiTicketsQueue;
}

export function getStatsQueue(): Queue {
  globalForQueues.__nexumiStatsQueue ??= new Queue(statsQueueName, {
    connection: queueConnection()
  });
  return globalForQueues.__nexumiStatsQueue;
}

/**
 * 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;
}

export function getMessagesQueueEvents(): QueueEvents {
  globalForQueues.__nexumiMessagesQueueEvents ??= new QueueEvents(messagesQueueName, {
    connection: queueEventsConnection()
  });
  return globalForQueues.__nexumiMessagesQueueEvents;
}

export function getSelfrolesQueueEvents(): QueueEvents {
  globalForQueues.__nexumiSelfrolesQueueEvents ??= new QueueEvents(selfrolesQueueName, {
    connection: queueEventsConnection()
  });
  return globalForQueues.__nexumiSelfrolesQueueEvents;
}

export function getVerificationQueueEvents(): QueueEvents {
  globalForQueues.__nexumiVerificationQueueEvents ??= new QueueEvents(verificationQueueName, {
    connection: queueEventsConnection()
  });
  return globalForQueues.__nexumiVerificationQueueEvents;
}

export function getTicketsQueueEvents(): QueueEvents {
  globalForQueues.__nexumiTicketsQueueEvents ??= new QueueEvents(ticketsQueueName, {
    connection: queueEventsConnection()
  });
  return globalForQueues.__nexumiTicketsQueueEvents;
}

export function getStatsQueueEvents(): QueueEvents {
  globalForQueues.__nexumiStatsQueueEvents ??= new QueueEvents(statsQueueName, {
    connection: queueEventsConnection()
  });
  return globalForQueues.__nexumiStatsQueueEvents;
}

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.
 * Enqueue / Redis failures are rethrown. Only waitUntilFinished timeouts
 * (and failed jobs) return `{ confirmed: false }`.
 */
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 (error) {
    const message = error instanceof Error ? error.message : String(error);
    // BullMQ timeout message typically includes "timed out"
    if (/timed out|timeout/i.test(message)) {
      return { confirmed: false, result: null };
    }
    // Job failed in the worker — treat as unconfirmed rather than crashing
    // the API with a raw stack when the dashboard can refresh from DB.
    if (/failed with reason|job.*failed/i.test(message)) {
      return { confirmed: false, result: null };
    }
    throw error;
  }
}
