import type { GiveawayCreateDashboard, GiveawayDashboard } from '@nexumi/shared';
import type { Giveaway } from '@prisma/client';
import { prisma } from '../prisma';
import { addJobAndAwait, giveawayQueue, giveawayQueueEvents } from '../queues';

export class GiveawayCreateTimeoutError extends Error {
  constructor() {
    super('The bot did not confirm the giveaway in time. It may still be starting - check the list shortly.');
  }
}

function toDashboard(giveaway: Giveaway): GiveawayDashboard {
  return {
    id: giveaway.id,
    guildId: giveaway.guildId,
    channelId: giveaway.channelId,
    messageId: giveaway.messageId,
    prize: giveaway.prize,
    winnerCount: giveaway.winnerCount,
    endsAt: giveaway.endsAt.toISOString(),
    requiredRoleId: giveaway.requiredRoleId,
    requiredLevel: giveaway.requiredLevel,
    requiredMemberDays: giveaway.requiredMemberDays,
    paused: giveaway.paused,
    endedAt: giveaway.endedAt?.toISOString() ?? null,
    winners: giveaway.winners,
    entrants: giveaway.entrants,
    hostId: giveaway.hostId,
    createdAt: giveaway.createdAt.toISOString()
  };
}

export async function listGiveaways(guildId: string): Promise<GiveawayDashboard[]> {
  const giveaways = await prisma.giveaway.findMany({
    where: { guildId },
    orderBy: [{ endedAt: 'asc' }, { endsAt: 'asc' }],
    take: 50
  });
  return giveaways.map(toDashboard);
}

/**
 * The WebUI has no discord.js Client to post the giveaway announcement
 * message, so creation is delegated to the bot via the same `giveaways`
 * BullMQ queue used by `/giveaway start` (see
 * `apps/bot/src/modules/giveaways/service.ts#runGiveawayCreateJob`). We wait
 * (bounded) for the job to finish so the dashboard can show the created
 * giveaway immediately.
 */
export async function createGiveawayDashboard(
  guildId: string,
  hostId: string,
  input: GiveawayCreateDashboard
): Promise<GiveawayDashboard> {
  await prisma.guild.upsert({ where: { id: guildId }, update: {}, create: { id: guildId } });

  const durationMs = new Date(input.endsAt).getTime() - Date.now();
  if (durationMs <= 0) {
    throw new GiveawayCreateTimeoutError();
  }

  const { confirmed, result } = await addJobAndAwait<{ id: string }>(
    giveawayQueue,
    giveawayQueueEvents,
    'giveawayCreate',
    {
      guildId,
      channelId: input.channelId,
      hostId,
      prize: input.prize,
      winnerCount: input.winnerCount,
      durationMs,
      requiredRoleId: input.requiredRoleId || null,
      requiredLevel: input.requiredLevel ?? null,
      requiredMemberDays: input.requiredMemberDays ?? null
    },
    { removeOnComplete: true, removeOnFail: 25 }
  );

  if (!confirmed || !result) {
    throw new GiveawayCreateTimeoutError();
  }

  const giveaway = await prisma.giveaway.findUnique({ where: { id: result.id } });
  if (!giveaway) {
    throw new GiveawayCreateTimeoutError();
  }
  return toDashboard(giveaway);
}

export async function setGiveawayPaused(
  guildId: string,
  giveawayId: string,
  paused: boolean
): Promise<GiveawayDashboard | null> {
  const existing = await prisma.giveaway.findFirst({ where: { id: giveawayId, guildId } });
  if (!existing || existing.endedAt) {
    return null;
  }
  const updated = await prisma.giveaway.update({ where: { id: giveawayId }, data: { paused } });
  return toDashboard(updated);
}

/**
 * Ends a giveaway immediately by re-scheduling its `giveawayEnd` job with no
 * delay and waiting for the bot to draw winners, update the Discord message
 * and DM them - see `apps/bot/src/modules/giveaways/service.ts#endGiveaway`.
 */
export async function endGiveawayDashboard(guildId: string, giveawayId: string): Promise<GiveawayDashboard | null> {
  const existing = await prisma.giveaway.findFirst({ where: { id: giveawayId, guildId } });
  if (!existing || existing.endedAt) {
    return null;
  }

  const existingJob = await giveawayQueue.getJob(`giveaway-end-${giveawayId}`);
  await existingJob?.remove();

  await addJobAndAwait(
    giveawayQueue,
    giveawayQueueEvents,
    'giveawayEnd',
    { giveawayId },
    { jobId: `giveaway-end-${giveawayId}`, removeOnComplete: true, removeOnFail: 50, delay: 0 }
  );

  const updated = await prisma.giveaway.findUnique({ where: { id: giveawayId } });
  return updated ? toDashboard(updated) : null;
}

export async function deleteGiveawayDashboard(guildId: string, giveawayId: string): Promise<boolean> {
  const existing = await prisma.giveaway.findFirst({ where: { id: giveawayId, guildId } });
  if (!existing) {
    return false;
  }

  const job = await giveawayQueue.getJob(`giveaway-end-${giveawayId}`);
  await job?.remove();

  await prisma.giveaway.delete({ where: { id: giveawayId } });
  return true;
}
