import type {
  SelfRoleEntry,
  SelfRolePanelDashboard,
  SelfRolePanelDashboardCreate,
  SelfRolePanelDashboardUpdate
} from '@nexumi/shared';
import type { SelfRolePanel } from '@prisma/client';
import { prisma } from '../prisma';
import { addJobAndAwait, getSelfrolesQueue, getSelfrolesQueueEvents } from '../queues';

export class SelfRolePanelSyncError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'SelfRolePanelSyncError';
  }
}

function toRolesArray(raw: unknown): SelfRoleEntry[] {
  if (Array.isArray(raw)) {
    return raw.filter(
      (entry): entry is SelfRoleEntry =>
        Boolean(entry) && typeof entry === 'object' && typeof (entry as SelfRoleEntry).roleId === 'string'
    );
  }

  if (raw && typeof raw === 'object' && Array.isArray((raw as { entries?: unknown }).entries)) {
    return toRolesArray((raw as { entries: unknown }).entries);
  }

  return [];
}

function toDashboard(panel: SelfRolePanel): SelfRolePanelDashboard {
  return {
    id: panel.id,
    channelId: panel.channelId,
    title: panel.title,
    description: panel.description,
    mode: panel.mode as SelfRolePanelDashboard['mode'],
    behavior: panel.behavior as SelfRolePanelDashboard['behavior'],
    roles: toRolesArray(panel.roles),
    expiresAt: panel.expiresAt?.toISOString() ?? null
  };
}

function temporaryDurationFromExpiresAt(expiresAt: string | null | undefined): number | undefined {
  if (!expiresAt) {
    return undefined;
  }
  const ms = new Date(expiresAt).getTime() - Date.now();
  return ms > 0 ? ms : undefined;
}

export async function listSelfRolePanels(guildId: string): Promise<SelfRolePanelDashboard[]> {
  const panels = await prisma.selfRolePanel.findMany({
    where: { guildId },
    orderBy: { createdAt: 'desc' }
  });
  return panels.map(toDashboard);
}

/**
 * Dashboard create has no discord.js Client, so panel posting is delegated to
 * the bot via the `selfroles` BullMQ queue (`selfRolePanelCreate`).
 */
export async function createSelfRolePanel(
  guildId: string,
  input: SelfRolePanelDashboardCreate
): Promise<SelfRolePanelDashboard> {
  await prisma.guild.upsert({ where: { id: guildId }, update: {}, create: { id: guildId } });

  const temporaryDurationMs =
    input.behavior === 'TEMPORARY' ? temporaryDurationFromExpiresAt(input.expiresAt) : undefined;
  if (input.behavior === 'TEMPORARY' && (!temporaryDurationMs || temporaryDurationMs < 60_000)) {
    throw new SelfRolePanelSyncError(
      'Temporary panels require expiresAt at least 1 minute in the future (role duration).'
    );
  }

  const { confirmed, result } = await addJobAndAwait<{ id: string }>(
    getSelfrolesQueue(),
    getSelfrolesQueueEvents(),
    'selfRolePanelCreate',
    {
      guildId,
      channelId: input.channelId,
      title: input.title,
      description: input.description ?? null,
      mode: input.mode,
      behavior: input.behavior,
      roles: input.roles,
      temporaryDurationMs,
      expiresAt: input.expiresAt ?? null
    },
    { removeOnComplete: true, removeOnFail: 25 },
    30_000
  );

  if (!confirmed || !result) {
    throw new SelfRolePanelSyncError(
      'The bot did not confirm the self-role panel in time. It may still be posting — check the channel shortly.'
    );
  }

  const panel = await prisma.selfRolePanel.findUnique({ where: { id: result.id } });
  if (!panel?.messageId) {
    throw new SelfRolePanelSyncError(
      confirmed
        ? 'Self-role panel job finished without posting a Discord message'
        : 'Self-role panel create timed out – the bot may still be processing; refresh and retry'
    );
  }
  return toDashboard(panel);
}

export async function updateSelfRolePanel(
  guildId: string,
  panelId: string,
  patch: SelfRolePanelDashboardUpdate
): Promise<SelfRolePanelDashboard | null> {
  const existing = await prisma.selfRolePanel.findFirst({ where: { id: panelId, guildId } });
  if (!existing) {
    return null;
  }

  const nextBehavior = patch.behavior ?? (existing.behavior as SelfRolePanelDashboard['behavior']);
  const temporaryDurationMs =
    nextBehavior === 'TEMPORARY'
      ? temporaryDurationFromExpiresAt(
          patch.expiresAt !== undefined ? patch.expiresAt : existing.expiresAt?.toISOString()
        )
      : undefined;

  if (nextBehavior === 'TEMPORARY' && patch.behavior === 'TEMPORARY') {
    const storedDuration =
      existing.roles &&
      typeof existing.roles === 'object' &&
      !Array.isArray(existing.roles) &&
      typeof (existing.roles as { temporaryDurationMs?: unknown }).temporaryDurationMs === 'number'
        ? (existing.roles as { temporaryDurationMs: number }).temporaryDurationMs
        : undefined;
    if (!temporaryDurationMs && !storedDuration) {
      throw new SelfRolePanelSyncError(
        'Temporary panels require expiresAt at least 1 minute in the future (role duration).'
      );
    }
  }

  const { confirmed } = await addJobAndAwait(
    getSelfrolesQueue(),
    getSelfrolesQueueEvents(),
    'selfRolePanelUpdate',
    {
      panelId,
      guildId,
      channelId: patch.channelId,
      title: patch.title,
      description: patch.description,
      mode: patch.mode,
      behavior: patch.behavior,
      roles: patch.roles,
      temporaryDurationMs,
      expiresAt: patch.expiresAt
    },
    {
      jobId: `selfrole-update-${panelId}-${Date.now()}`,
      removeOnComplete: true,
      removeOnFail: 25
    },
    30_000
  );

  const updated = await prisma.selfRolePanel.findUnique({ where: { id: panelId } });
  if (!updated?.messageId) {
    throw new SelfRolePanelSyncError(
      confirmed
        ? 'Self-role panel update finished without a Discord message'
        : 'Self-role panel update timed out – the bot may still be processing; refresh and retry'
    );
  }
  return toDashboard(updated);
}

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

  const { confirmed } = await addJobAndAwait(
    getSelfrolesQueue(),
    getSelfrolesQueueEvents(),
    'selfRolePanelDelete',
    { panelId, guildId },
    {
      jobId: `selfrole-delete-${panelId}-${Date.now()}`,
      removeOnComplete: true,
      removeOnFail: 25
    },
    30_000
  );

  const remaining = await prisma.selfRolePanel.findUnique({ where: { id: panelId } });
  if (remaining) {
    throw new SelfRolePanelSyncError(
      confirmed
        ? 'Self-role panel delete finished without deleting the panel'
        : 'Self-role panel delete timed out – the bot may still be processing; refresh and retry'
    );
  }
  return true;
}
