import type {
  SuggestionAction,
  SuggestionConfigDashboard,
  SuggestionConfigDashboardPatch,
  SuggestionDashboard,
  SuggestionListQuery
} from '@nexumi/shared';
import type { Suggestion } from '@prisma/client';
import { prisma } from '../prisma';
import { addJobAndAwait, getSuggestionsQueue, getSuggestionsQueueEvents } from '../queues';

export class SuggestionActionTimeoutError extends Error {
  constructor() {
    super('The bot did not confirm the suggestion update in time. Reload the list shortly to check the result.');
  }
}

async function ensureSuggestionConfig(guildId: string) {
  await prisma.guild.upsert({ where: { id: guildId }, update: {}, create: { id: guildId } });
  return prisma.suggestionConfig.upsert({ where: { guildId }, update: {}, create: { guildId } });
}

function toConfigDashboard(
  config: Awaited<ReturnType<typeof ensureSuggestionConfig>>
): SuggestionConfigDashboard {
  return {
    enabled: config.enabled,
    openChannelId: config.openChannelId ?? '',
    approvedChannelId: config.approvedChannelId ?? '',
    deniedChannelId: config.deniedChannelId ?? '',
    createThread: config.createThread
  };
}

export async function getSuggestionConfigDashboard(guildId: string): Promise<SuggestionConfigDashboard> {
  const config = await ensureSuggestionConfig(guildId);
  return toConfigDashboard(config);
}

export async function updateSuggestionConfigDashboard(
  guildId: string,
  patch: SuggestionConfigDashboardPatch
): Promise<SuggestionConfigDashboard> {
  await ensureSuggestionConfig(guildId);

  const { openChannelId, approvedChannelId, deniedChannelId, ...rest } = patch;
  const updated = await prisma.suggestionConfig.update({
    where: { guildId },
    data: {
      ...rest,
      ...(openChannelId !== undefined ? { openChannelId: openChannelId === '' ? null : openChannelId } : {}),
      ...(approvedChannelId !== undefined
        ? { approvedChannelId: approvedChannelId === '' ? null : approvedChannelId }
        : {}),
      ...(deniedChannelId !== undefined
        ? { deniedChannelId: deniedChannelId === '' ? null : deniedChannelId }
        : {})
    }
  });
  return toConfigDashboard(updated);
}

function toDashboard(suggestion: Suggestion): SuggestionDashboard {
  return {
    id: suggestion.id,
    authorId: suggestion.authorId,
    content: suggestion.content,
    status: suggestion.status as SuggestionDashboard['status'],
    upvotes: suggestion.upvotes,
    downvotes: suggestion.downvotes,
    staffReason: suggestion.staffReason,
    createdAt: suggestion.createdAt.toISOString()
  };
}

export async function listSuggestions(
  guildId: string,
  query: SuggestionListQuery
): Promise<SuggestionDashboard[]> {
  const suggestions = await prisma.suggestion.findMany({
    where: { guildId, ...(query.status ? { status: query.status } : {}) },
    orderBy: { createdAt: 'desc' },
    take: query.limit
  });
  return suggestions.map(toDashboard);
}

/**
 * Staff actions (approve/deny/consider/implement) need to move or edit the
 * suggestion's Discord embed and DM the author, which requires the bot's
 * discord.js Client. The WebUI enqueues a `suggestionStatusUpdate` job on
 * the same `suggestions` BullMQ queue the bot worker consumes (see
 * `apps/bot/src/modules/suggestions/service.ts#runSuggestionStatusUpdateJob`)
 * and waits (bounded) for it to finish.
 */
export async function applySuggestionAction(
  guildId: string,
  suggestionId: string,
  action: SuggestionAction,
  _actorUserId: string
): Promise<SuggestionDashboard> {
  const existing = await prisma.suggestion.findFirst({ where: { id: suggestionId, guildId } });
  if (!existing) {
    throw new SuggestionActionTimeoutError();
  }

  const { confirmed } = await addJobAndAwait<{ id: string }>(
    getSuggestionsQueue(),
    getSuggestionsQueueEvents(),
    'suggestionStatusUpdate',
    {
      suggestionId,
      guildId,
      status: action.status,
      reason: action.staffReason ?? ''
    },
    { removeOnComplete: true, removeOnFail: 25 }
  );

  if (!confirmed) {
    throw new SuggestionActionTimeoutError();
  }

  const updated = await prisma.suggestion.findUnique({ where: { id: suggestionId } });
  if (!updated) {
    throw new SuggestionActionTimeoutError();
  }
  return toDashboard(updated);
}
