import {
  ChannelType,
  PermissionFlagsBits,
  TextChannel,
  type ChatInputCommandInteraction
} from 'discord.js';
import { t, tf } from '@nexumi/shared';
import type { SlashCommand } from '../../types.js';
import { requirePermission } from '../../permissions.js';
import { assertModerableMember } from '../../hierarchy.js';
import { applyWarnEscalation, createCase } from './service.js';
import { parseDuration, parseTimeoutDuration, tryParseDuration } from './duration.js';
import { getGuildLocale } from '../../i18n.js';
import { promptDestructiveConfirmation } from './confirmations.js';
import {
  banCommandData,
  caseCommandData,
  kickCommandData,
  lockCommandData,
  modNoteCommandData,
  nickCommandData,
  purgeCommandData,
  slowmodeCommandData,
  timeoutCommandData,
  unbanCommandData,
  unlockCommandData,
  untimeoutCommandData,
  warnCommandData
} from './command-definitions.js';

function reasonFrom(i: ChatInputCommandInteraction, locale: 'de' | 'en'): string {
  return i.options.getString('reason') ?? t(locale, 'moderation.reason.none');
}

function auditFrom(interaction: ChatInputCommandInteraction) {
  return {
    channelId: interaction.channelId ?? undefined,
    source: 'slash_command' as const
  };
}

async function ensureMod(
  interaction: ChatInputCommandInteraction,
  permission: bigint,
  locale: 'de' | 'en'
): Promise<boolean> {
  if (!interaction.inGuild()) {
    await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true });
    return false;
  }
  return requirePermission(interaction, permission, locale, { botPermissions: permission });
}

async function replySuccessCase(
  interaction: ChatInputCommandInteraction,
  locale: 'de' | 'en',
  caseNumber: number,
  reason?: string
): Promise<void> {
  const content = reason
    ? tf(locale, 'moderation.success.caseWithReason', { caseNumber, reason })
    : tf(locale, 'moderation.success.case', { caseNumber });
  if (interaction.deferred || interaction.replied) {
    await interaction.editReply({ content });
    return;
  }
  await interaction.reply({
    content,
    ephemeral: true
  });
}

const banCommand: SlashCommand = {
  data: banCommandData,
  async execute(interaction, context) {
    const locale = await getGuildLocale(context.prisma, interaction.guildId);
    if (!(await ensureMod(interaction, PermissionFlagsBits.BanMembers, locale))) return;
    const user = interaction.options.getUser('user', true);
    const reason = reasonFrom(interaction, locale);
    const duration = interaction.options.getString('duration');

    if (duration) {
      const durationMs = tryParseDuration(duration);
      if (durationMs === null || durationMs <= 0) {
        await interaction.reply({
          content: t(locale, 'moderation.duration.invalid'),
          ephemeral: true
        });
        return;
      }
    }

    const hierarchy = await assertModerableMember(interaction, user.id, locale, 'ban');
    if (!hierarchy.ok) return;

    await promptDestructiveConfirmation(interaction, context, locale, {
      action: 'ban',
      payload: {
        userId: user.id,
        reason,
        duration
      }
    });
  }
};

const unbanCommand: SlashCommand = {
  data: unbanCommandData,
  async execute(interaction, context) {
    const locale = await getGuildLocale(context.prisma, interaction.guildId);
    if (!(await ensureMod(interaction, PermissionFlagsBits.BanMembers, locale))) return;
    const userId = interaction.options.getString('user_id', true);
    const reason = reasonFrom(interaction, locale);
    await interaction.guild!.members.unban(userId, reason);
    const modCase = await createCase(context, {
      guildId: interaction.guildId!,
      targetUserId: userId,
      moderatorId: interaction.user.id,
      ...auditFrom(interaction),
      action: 'UNBAN',
      reason
    });
    await replySuccessCase(interaction, locale, modCase.caseNumber, reason);
  }
};

const kickCommand: SlashCommand = {
  data: kickCommandData,
  async execute(interaction, context) {
    const locale = await getGuildLocale(context.prisma, interaction.guildId);
    if (!(await ensureMod(interaction, PermissionFlagsBits.KickMembers, locale))) return;
    const user = interaction.options.getUser('user', true);
    const reason = reasonFrom(interaction, locale);
    const hierarchy = await assertModerableMember(interaction, user.id, locale, 'kick');
    if (!hierarchy.ok || !hierarchy.member) return;
    await hierarchy.member.kick(reason);
    const modCase = await createCase(context, {
      guildId: interaction.guildId!,
      targetUserId: user.id,
      moderatorId: interaction.user.id,
      ...auditFrom(interaction),
      action: 'KICK',
      reason
    });
    await replySuccessCase(interaction, locale, modCase.caseNumber, reason);
  }
};

const timeoutCommand: SlashCommand = {
  data: timeoutCommandData,
  async execute(interaction, context) {
    const locale = await getGuildLocale(context.prisma, interaction.guildId);
    if (!(await ensureMod(interaction, PermissionFlagsBits.ModerateMembers, locale))) return;
    const user = interaction.options.getUser('user', true);
    let duration: number;
    try {
      duration = parseTimeoutDuration(interaction.options.getString('duration', true));
    } catch {
      await interaction.reply({
        content: t(locale, 'moderation.duration.invalidTimeout'),
        ephemeral: true
      });
      return;
    }
    const reason = reasonFrom(interaction, locale);
    const hierarchy = await assertModerableMember(interaction, user.id, locale, 'timeout');
    if (!hierarchy.ok || !hierarchy.member) return;
    await hierarchy.member.timeout(duration, reason);
    const modCase = await createCase(context, {
      guildId: interaction.guildId!,
      targetUserId: user.id,
      moderatorId: interaction.user.id,
      ...auditFrom(interaction),
      action: 'TIMEOUT',
      reason,
      metadata: { durationMs: duration }
    });
    await replySuccessCase(interaction, locale, modCase.caseNumber, reason);
  }
};

const untimeoutCommand: SlashCommand = {
  data: untimeoutCommandData,
  async execute(interaction, context) {
    const locale = await getGuildLocale(context.prisma, interaction.guildId);
    if (!(await ensureMod(interaction, PermissionFlagsBits.ModerateMembers, locale))) return;
    const user = interaction.options.getUser('user', true);
    const reason = reasonFrom(interaction, locale);
    const hierarchy = await assertModerableMember(interaction, user.id, locale, 'timeout');
    if (!hierarchy.ok || !hierarchy.member) return;
    await hierarchy.member.timeout(null, reason);
    const modCase = await createCase(context, {
      guildId: interaction.guildId!,
      targetUserId: user.id,
      moderatorId: interaction.user.id,
      ...auditFrom(interaction),
      action: 'UN_TIMEOUT',
      reason
    });
    await replySuccessCase(interaction, locale, modCase.caseNumber, reason);
  }
};

const warnCommand: SlashCommand = {
  data: warnCommandData,
  async execute(interaction, context) {
    const locale = await getGuildLocale(context.prisma, interaction.guildId);
    if (!(await ensureMod(interaction, PermissionFlagsBits.ModerateMembers, locale))) return;
    const sub = interaction.options.getSubcommand();
    if (sub === 'escalation-set') {
      const warnCount = interaction.options.getInteger('count', true);
      const action = interaction.options.getString('action', true);
      const durationInput = interaction.options.getString('duration');
      const reason = interaction.options.getString('reason');
      let durationMs: number | null = null;
      if (durationInput) {
        try {
          durationMs =
            action === 'TIMEOUT' ? parseTimeoutDuration(durationInput) : parseDuration(durationInput);
        } catch {
          await interaction.reply({
            content: t(locale, 'moderation.duration.invalid'),
            ephemeral: true
          });
          return;
        }
      }
      if (action === 'TIMEOUT' && !durationMs) {
        await interaction.reply({
          content: t(locale, 'moderation.escalation.durationRequired'),
          ephemeral: true
        });
        return;
      }
      await context.prisma.escalationRule.upsert({
        where: {
          guildId_warnCount: {
            guildId: interaction.guildId!,
            warnCount
          }
        },
        update: {
          action,
          durationMs,
          reason,
          enabled: true
        },
        create: {
          guildId: interaction.guildId!,
          warnCount,
          action,
          durationMs,
          reason,
          enabled: true
        }
      });
      const durationText = durationMs ? ` (${durationMs} ms)` : '';
      await interaction.reply({
        content: tf(locale, 'moderation.escalation.saved', {
          warnCount,
          action,
          duration: durationText
        }),
        ephemeral: true
      });
      return;
    }

    if (sub === 'escalation-list') {
      const rules = await context.prisma.escalationRule.findMany({
        where: { guildId: interaction.guildId!, enabled: true },
        orderBy: { warnCount: 'asc' }
      });
      const content =
        rules.length === 0
          ? t(locale, 'moderation.escalation.noneConfigured')
          : rules
              .map(
                (rule) =>
                  `- ${rule.warnCount} warnings => ${rule.action}${rule.durationMs ? ` (${rule.durationMs} ms)` : ''}${rule.reason ? ` | ${rule.reason}` : ''}`
              )
              .join('\n');
      await interaction.reply({ content, ephemeral: true });
      return;
    }

    if (sub === 'escalation-remove') {
      const warnCount = interaction.options.getInteger('count', true);
      await context.prisma.escalationRule.delete({
        where: { guildId_warnCount: { guildId: interaction.guildId!, warnCount } }
      });
      await interaction.reply({
        content: tf(locale, 'moderation.escalation.removed', { warnCount }),
        ephemeral: true
      });
      return;
    }

    if (sub === 'add') {
      const user = interaction.options.getUser('user', true);
      const reason = interaction.options.getString('reason', true);
      const modCase = await createCase(context, {
        guildId: interaction.guildId!,
        targetUserId: user.id,
        moderatorId: interaction.user.id,
        ...auditFrom(interaction),
        action: 'WARN_ADD',
        reason
      });
      await context.prisma.user.upsert({
        where: { id: user.id },
        update: {},
        create: { id: user.id }
      });
      const warning = await context.prisma.warning.create({
        data: {
          guildId: interaction.guildId!,
          userId: user.id,
          moderatorId: interaction.user.id,
          reason,
          caseId: modCase.id
        }
      });
      const escalationResult = await applyWarnEscalation(
        interaction,
        context,
        user.id,
        interaction.user.id,
        locale
      );
      await interaction.reply({
        content: escalationResult
          ? `${tf(locale, 'moderation.warning.created', { warningId: warning.id })}\n${escalationResult}`
          : tf(locale, 'moderation.warning.created', { warningId: warning.id }),
        ephemeral: true
      });
      return;
    }

    if (sub === 'list') {
      const user = interaction.options.getUser('user', true);
      const warnings = await context.prisma.warning.findMany({
        where: { guildId: interaction.guildId!, userId: user.id },
        orderBy: { createdAt: 'desc' },
        take: 20
      });
      const content =
        warnings.length === 0
          ? t(locale, 'moderation.warning.none')
          : warnings
              .map(
                (w: { id: string; reason: string | null }) =>
                  `- ${w.id}: ${w.reason ?? t(locale, 'moderation.warning.defaultReason')}`
              )
              .join('\n');
      await interaction.reply({ content, ephemeral: true });
      return;
    }

    if (sub === 'remove') {
      const warningId = interaction.options.getString('warning_id', true);
      const warning = await context.prisma.warning.findFirst({
        where: { id: warningId, guildId: interaction.guildId! }
      });
      if (!warning) {
        await interaction.reply({
          content: t(locale, 'moderation.warning.notFound'),
          ephemeral: true
        });
        return;
      }
      await context.prisma.warning.delete({ where: { id: warning.id } });
      const modCase = await createCase(context, {
        guildId: interaction.guildId!,
        targetUserId: warning.userId,
        moderatorId: interaction.user.id,
        ...auditFrom(interaction),
        action: 'WARN_REMOVE',
        reason: `Warning ${warningId} removed`
      });
      await replySuccessCase(interaction, locale, modCase.caseNumber);
      return;
    }

    const user = interaction.options.getUser('user', true);
    await context.prisma.warning.deleteMany({
      where: { guildId: interaction.guildId!, userId: user.id }
    });
    const modCase = await createCase(context, {
      guildId: interaction.guildId!,
      targetUserId: user.id,
      moderatorId: interaction.user.id,
      ...auditFrom(interaction),
      action: 'WARN_CLEAR',
      reason: 'Warnings cleared'
    });
    await replySuccessCase(interaction, locale, modCase.caseNumber);
  }
};

const purgeCommand: SlashCommand = {
  data: purgeCommandData,
  async execute(interaction, context) {
    const locale = await getGuildLocale(context.prisma, interaction.guildId);
    if (!(await ensureMod(interaction, PermissionFlagsBits.ManageMessages, locale))) return;
    const amount = interaction.options.getInteger('amount', true);
    const targetUser = interaction.options.getUser('user');
    const botsOnly = interaction.options.getBoolean('bots') ?? false;
    const linksOnly = interaction.options.getBoolean('links') ?? false;
    const attachmentsOnly = interaction.options.getBoolean('attachments') ?? false;
    const regexInput = interaction.options.getString('regex');
    if (regexInput) {
      try {
        new RegExp(regexInput, 'i');
      } catch {
        await interaction.reply({ content: t(locale, 'generic.invalidRegex'), ephemeral: true });
        return;
      }
    }

    if (!(interaction.channel instanceof TextChannel)) {
      await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true });
      return;
    }

    await promptDestructiveConfirmation(interaction, context, locale, {
      action: 'purge',
      payload: {
        channelId: interaction.channel.id,
        amount,
        targetUserId: targetUser?.id ?? null,
        botsOnly,
        linksOnly,
        attachmentsOnly,
        regex: regexInput
      }
    });
  }
};

const slowmodeCommand: SlashCommand = {
  data: slowmodeCommandData,
  async execute(interaction, context) {
    const locale = await getGuildLocale(context.prisma, interaction.guildId);
    if (!(await ensureMod(interaction, PermissionFlagsBits.ManageChannels, locale))) return;
    const seconds = interaction.options.getInteger('seconds', true);
    if (interaction.channel instanceof TextChannel) {
      await interaction.channel.setRateLimitPerUser(seconds);
    }
    const modCase = await createCase(context, {
      guildId: interaction.guildId!,
      targetUserId: interaction.user.id,
      moderatorId: interaction.user.id,
      ...auditFrom(interaction),
      action: 'SLOWMODE',
      reason: `Set slowmode to ${seconds}s`
    });
    await replySuccessCase(interaction, locale, modCase.caseNumber);
  }
};

const lockCommand: SlashCommand = {
  data: lockCommandData,
  async execute(interaction, context) {
    const locale = await getGuildLocale(context.prisma, interaction.guildId);
    if (!(await ensureMod(interaction, PermissionFlagsBits.ManageChannels, locale))) return;
    const serverWide = interaction.options.getBoolean('server') ?? false;
    const guild = interaction.guild!;

    if (serverWide) {
      await interaction.deferReply({ ephemeral: true });
      for (const channel of guild.channels.cache.values()) {
        if (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement) {
          continue;
        }
        await channel.permissionOverwrites.edit(guild.roles.everyone, {
          SendMessages: false
        });
      }
    } else if (interaction.channel?.type === ChannelType.GuildText) {
      await interaction.channel.permissionOverwrites.edit(guild.roles.everyone, {
        SendMessages: false
      });
    }

    const modCase = await createCase(context, {
      guildId: interaction.guildId!,
      targetUserId: interaction.user.id,
      moderatorId: interaction.user.id,
      ...auditFrom(interaction),
      action: 'LOCK',
      reason: serverWide ? 'Server locked' : 'Channel locked'
    });
    await replySuccessCase(interaction, locale, modCase.caseNumber);
  }
};

const unlockCommand: SlashCommand = {
  data: unlockCommandData,
  async execute(interaction, context) {
    const locale = await getGuildLocale(context.prisma, interaction.guildId);
    if (!(await ensureMod(interaction, PermissionFlagsBits.ManageChannels, locale))) return;
    const serverWide = interaction.options.getBoolean('server') ?? false;
    const guild = interaction.guild!;

    if (serverWide) {
      await interaction.deferReply({ ephemeral: true });
      for (const channel of guild.channels.cache.values()) {
        if (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement) {
          continue;
        }
        await channel.permissionOverwrites.edit(guild.roles.everyone, {
          SendMessages: null
        });
      }
    } else if (interaction.channel?.type === ChannelType.GuildText) {
      await interaction.channel.permissionOverwrites.edit(guild.roles.everyone, {
        SendMessages: null
      });
    }

    const modCase = await createCase(context, {
      guildId: interaction.guildId!,
      targetUserId: interaction.user.id,
      moderatorId: interaction.user.id,
      ...auditFrom(interaction),
      action: 'UNLOCK',
      reason: serverWide ? 'Server unlocked' : 'Channel unlocked'
    });
    await replySuccessCase(interaction, locale, modCase.caseNumber);
  }
};

const nickCommand: SlashCommand = {
  data: nickCommandData,
  async execute(interaction, context) {
    const locale = await getGuildLocale(context.prisma, interaction.guildId);
    if (!(await ensureMod(interaction, PermissionFlagsBits.ManageNicknames, locale))) return;
    const sub = interaction.options.getSubcommand();
    const user = interaction.options.getUser('user', true);
    const hierarchy = await assertModerableMember(interaction, user.id, locale, 'nick');
    if (!hierarchy.ok || !hierarchy.member) return;
    const member = hierarchy.member;
    if (sub === 'set') {
      const nickname = interaction.options.getString('nickname', true);
      await member.setNickname(nickname);
      const modCase = await createCase(context, {
        guildId: interaction.guildId!,
        targetUserId: user.id,
        moderatorId: interaction.user.id,
        ...auditFrom(interaction),
        action: 'NICK_SET',
        reason: nickname
      });
      await replySuccessCase(interaction, locale, modCase.caseNumber);
    } else {
      await member.setNickname(null);
      const modCase = await createCase(context, {
        guildId: interaction.guildId!,
        targetUserId: user.id,
        moderatorId: interaction.user.id,
        ...auditFrom(interaction),
        action: 'NICK_RESET',
        reason: 'Nickname reset'
      });
      await replySuccessCase(interaction, locale, modCase.caseNumber);
    }
  }
};

const caseCommand: SlashCommand = {
  data: caseCommandData,
  async execute(interaction, context) {
    const locale = await getGuildLocale(context.prisma, interaction.guildId);
    if (!(await ensureMod(interaction, PermissionFlagsBits.ModerateMembers, locale))) return;
    const sub = interaction.options.getSubcommand();
    const caseNumber = interaction.options.getInteger('id', true);
    if (sub === 'view') {
      const found = await context.prisma.case.findFirst({
        where: {
          guildId: interaction.guildId!,
          caseNumber,
          deletedAt: null
        }
      });
      await interaction.reply({
        content: found ? JSON.stringify(found, null, 2) : t(locale, 'moderation.case.notFound'),
        ephemeral: true
      });
      return;
    }
    if (sub === 'edit') {
      const reason = interaction.options.getString('reason', true);
      const existing = await context.prisma.case.findFirst({
        where: {
          guildId: interaction.guildId!,
          caseNumber,
          deletedAt: null
        }
      });
      if (!existing) {
        await interaction.reply({
          content: t(locale, 'moderation.case.notFound'),
          ephemeral: true
        });
        return;
      }
      await context.prisma.case.update({
        where: { id: existing.id },
        data: { reason }
      });
      await interaction.reply({
        content: tf(locale, 'moderation.success.case', { caseNumber }),
        ephemeral: true
      });
      return;
    }
    await promptDestructiveConfirmation(interaction, context, locale, {
      action: 'case_delete',
      payload: { caseNumber }
    });
  }
};

const modNoteCommand: SlashCommand = {
  data: modNoteCommandData,
  async execute(interaction, context) {
    const locale = await getGuildLocale(context.prisma, interaction.guildId);
    if (!(await ensureMod(interaction, PermissionFlagsBits.ModerateMembers, locale))) return;
    const sub = interaction.options.getSubcommand();
    const user = interaction.options.getUser('user', true);
    await context.prisma.user.upsert({ where: { id: user.id }, update: {}, create: { id: user.id } });
    if (sub === 'add') {
      const note = interaction.options.getString('note', true);
      await context.prisma.modNote.create({
        data: {
          guildId: interaction.guildId!,
          userId: user.id,
          moderatorId: interaction.user.id,
          note
        }
      });
      const modCase = await createCase(context, {
        guildId: interaction.guildId!,
        targetUserId: user.id,
        moderatorId: interaction.user.id,
        ...auditFrom(interaction),
        action: 'MODNOTE_ADD',
        reason: note
      });
      await replySuccessCase(interaction, locale, modCase.caseNumber);
      return;
    }
    const notes = await context.prisma.modNote.findMany({
      where: { guildId: interaction.guildId!, userId: user.id },
      orderBy: { createdAt: 'desc' },
      take: 20
    });
    const content =
      notes.length === 0
        ? t(locale, 'moderation.note.none')
        : notes.map((n: { note: string }) => `- ${n.note}`).join('\n');
    await interaction.reply({ content, ephemeral: true });
  }
};

export const moderationCommands: SlashCommand[] = [
  banCommand,
  unbanCommand,
  kickCommand,
  timeoutCommand,
  untimeoutCommand,
  warnCommand,
  purgeCommand,
  slowmodeCommand,
  lockCommand,
  unlockCommand,
  nickCommand,
  caseCommand,
  modNoteCommand
];
