import { PermissionFlagsBits, type ChatInputCommandInteraction } from 'discord.js';
import type { Prisma } from '@prisma/client';
import { buildCaseMetadata, type CaseAuditSource, t, type Locale } from '@nexumi/shared';
import { safeRemoveBullJob } from '../../lib/safe-remove-job.js';
import { moderationQueue } from '../../queues.js';
import type { BotContext } from '../../types.js';
import { MAX_TIMEOUT_MS } from './duration.js';

type CreateCaseInput = {
  guildId: string;
  targetUserId: string;
  moderatorId: string;
  action: string;
  reason?: string;
  source?: CaseAuditSource;
  channelId?: string;
  metadata?: Record<string, unknown>;
};

export async function createCase(context: BotContext, input: CreateCaseInput) {
  await context.prisma.guild.upsert({
    where: { id: input.guildId },
    update: {},
    create: { id: input.guildId }
  });

  const metadata = buildCaseMetadata({
    source: input.source ?? 'slash_command',
    channelId: input.channelId,
    extras: input.metadata
  });

  let created = null;
  for (let attempt = 0; attempt < 5; attempt++) {
    try {
      created = await context.prisma.$transaction(
        async (tx) => {
          const lastCase = await tx.case.findFirst({
            where: { guildId: input.guildId },
            orderBy: { caseNumber: 'desc' },
            select: { caseNumber: true }
          });
          return tx.case.create({
            data: {
              guildId: input.guildId,
              caseNumber: (lastCase?.caseNumber ?? 0) + 1,
              targetUserId: input.targetUserId,
              moderatorId: input.moderatorId,
              action: input.action,
              reason: input.reason,
              metadata: metadata as Prisma.InputJsonValue
            }
          });
        },
        { isolationLevel: 'Serializable' }
      );
      break;
    } catch (error) {
      const code =
        typeof error === 'object' && error && 'code' in error
          ? String((error as { code: unknown }).code)
          : '';
      if (code !== 'P2002' && attempt === 4) {
        throw error;
      }
      if (attempt === 4) {
        throw error;
      }
    }
  }

  if (!created) {
    throw new Error('Failed to create case');
  }

  const { logModAction } = await import('../logging/events.js');
  await logModAction(context, {
    guildId: input.guildId,
    caseNumber: created.caseNumber,
    action: input.action,
    targetUserId: input.targetUserId,
    moderatorId: input.moderatorId,
    reason: input.reason
  });
  return created;
}

export function tempBanJobId(guildId: string, userId: string): string {
  return `tempBan:${guildId}:${userId}`;
}

export async function scheduleTempBanExpire(
  guildId: string,
  userId: string,
  durationMs: number,
  reason?: string
) {
  const jobId = tempBanJobId(guildId, userId);
  const existing = await moderationQueue.getJob(jobId);
  await safeRemoveBullJob(existing, { label: 'tempBanExpire', jobId });

  await moderationQueue.add(
    'tempBanExpire',
    { guildId, userId, reason: reason ?? null },
    {
      jobId,
      delay: durationMs,
      removeOnComplete: 1000,
      removeOnFail: 500
    }
  );
}

export async function applyWarnEscalation(
  interaction: ChatInputCommandInteraction,
  context: BotContext,
  userId: string,
  moderatorId: string,
  locale: Locale
): Promise<string | null> {
  const guildId = interaction.guildId;
  if (!guildId || !interaction.guild) {
    return null;
  }

  const warningCount = await context.prisma.warning.count({
    where: { guildId, userId }
  });

  const rule = await context.prisma.escalationRule.findUnique({
    where: { guildId_warnCount: { guildId, warnCount: warningCount } }
  });

  if (!rule || !rule.enabled) {
    return null;
  }

  const member = await interaction.guild.members.fetch(userId);
  const reason =
    rule.reason ??
    t(locale, 'moderation.escalation.defaultReason').replace('{warnCount}', String(warningCount));

  if (rule.action === 'TIMEOUT') {
    if (!rule.durationMs) {
      return t(locale, 'moderation.escalation.skippedDuration').replace(
        '{warnCount}',
        String(warningCount)
      );
    }
    if (!interaction.guild.members.me?.permissions.has(PermissionFlagsBits.ModerateMembers)) {
      return t(locale, 'moderation.escalation.skippedPermission').replace(
        '{warnCount}',
        String(warningCount)
      );
    }
    const durationMs = Math.min(rule.durationMs, MAX_TIMEOUT_MS);
    await member.timeout(durationMs, reason);
    await createCase(context, {
      guildId,
      targetUserId: userId,
      moderatorId,
      action: 'TIMEOUT',
      reason,
      channelId: interaction.channelId ?? undefined,
      source: 'escalation',
      metadata: { escalation: true, durationMs, warningCount }
    });
    return t(locale, 'moderation.escalation.appliedTimeout').replace(
      '{durationMs}',
      String(durationMs)
    );
  }

  if (rule.action === 'KICK') {
    if (!interaction.guild.members.me?.permissions.has(PermissionFlagsBits.KickMembers)) {
      return t(locale, 'moderation.escalation.skippedPermission').replace(
        '{warnCount}',
        String(warningCount)
      );
    }
    await member.kick(reason);
    await createCase(context, {
      guildId,
      targetUserId: userId,
      moderatorId,
      action: 'KICK',
      reason,
      channelId: interaction.channelId ?? undefined,
      source: 'escalation',
      metadata: { escalation: true, warningCount }
    });
    return t(locale, 'moderation.escalation.appliedKick');
  }

  if (!interaction.guild.members.me?.permissions.has(PermissionFlagsBits.BanMembers)) {
    return t(locale, 'moderation.escalation.skippedPermission').replace(
      '{warnCount}',
      String(warningCount)
    );
  }
  await interaction.guild.members.ban(userId, { reason });
  await createCase(context, {
    guildId,
    targetUserId: userId,
    moderatorId,
    action: 'BAN',
    reason,
    channelId: interaction.channelId ?? undefined,
    source: 'escalation',
    metadata: { escalation: true, warningCount }
  });
  return t(locale, 'moderation.escalation.appliedBan');
}
