import { ChannelType, PermissionFlagsBits, type Guild, type GuildMember, type Message, type TextChannel } from 'discord.js';
import type { BotContext } from '../../types.js';
import { createCase } from '../moderation/service.js';
import {
  getAutoModConfig,
  getEnabledAutoModRules,
  isExcepted,
  parseExceptions,
  parseThreshold,
  parseWordList
} from './service.js';
import { evaluateRule, loadPhishingDomains, type FilterResult } from './filters.js';
import type { AutoModRuleType } from '@nexumi/shared';
import { logger } from '../../logger.js';

async function applyAutoModAction(
  context: BotContext,
  message: Message,
  ruleType: AutoModRuleType,
  action: string,
  reason: string,
  durationMs?: number | null
): Promise<void> {
  const guild = message.guild;
  if (!guild || !message.member) {
    return;
  }

  const me = guild.members.me;
  if (!me) {
    return;
  }

  if (me.permissions.has(PermissionFlagsBits.ManageMessages) && message.deletable) {
    await message.delete().catch((error) => {
      logger.warn({ error, messageId: message.id }, 'AutoMod failed to delete message');
    });
  }

  const moderatorId = context.client.user?.id ?? guild.client.user?.id ?? '0';
  const baseReason = `AutoMod (${ruleType}): ${reason}`;

  if (action === 'WARN') {
    await context.prisma.warning.create({
      data: {
        guildId: guild.id,
        userId: message.author.id,
        moderatorId,
        reason: baseReason
      }
    });
    await createCase(context, {
      guildId: guild.id,
      targetUserId: message.author.id,
      moderatorId,
      action: 'WARN_ADD',
      reason: baseReason,
      channelId: message.channel.id,
      source: 'automod',
      metadata: { ruleType, automod: true }
    });
    return;
  }

  if (action === 'TIMEOUT' && durationMs && durationMs > 0) {
    if (me.permissions.has(PermissionFlagsBits.ModerateMembers)) {
      await message.member.timeout(durationMs, baseReason);
      await createCase(context, {
        guildId: guild.id,
        targetUserId: message.author.id,
        moderatorId,
        action: 'TIMEOUT',
        reason: baseReason,
        channelId: message.channel.id,
        source: 'automod',
        metadata: { ruleType, automod: true, durationMs }
      });
    }
    return;
  }

  if (action === 'KICK') {
    if (me.permissions.has(PermissionFlagsBits.KickMembers)) {
      await message.member.kick(baseReason);
      await createCase(context, {
        guildId: guild.id,
        targetUserId: message.author.id,
        moderatorId,
        action: 'KICK',
        reason: baseReason,
        channelId: message.channel.id,
        source: 'automod',
        metadata: { ruleType, automod: true }
      });
    }
    return;
  }

  if (action === 'BAN') {
    if (me.permissions.has(PermissionFlagsBits.BanMembers)) {
      await guild.members.ban(message.author.id, { reason: baseReason });
      await createCase(context, {
        guildId: guild.id,
        targetUserId: message.author.id,
        moderatorId,
        action: 'BAN',
        reason: baseReason,
        channelId: message.channel.id,
        source: 'automod',
        metadata: { ruleType, automod: true }
      });
    }
  }
}

export async function handleAutoModMessage(
  context: BotContext,
  message: Message
): Promise<FilterResult | null> {
  if (!message.guild || message.author.bot || !message.content) {
    return null;
  }

  const config = await getAutoModConfig(context.prisma, message.guild.id);
  if (!config.enabled) {
    return null;
  }

  const member = message.member ?? (await message.guild.members.fetch(message.author.id));
  const roleIds = member.roles.cache.map((role) => role.id);
  const rules = await getEnabledAutoModRules(context.prisma, message.guild.id);
  const phishingDomains = await loadPhishingDomains(context.redis);

  const filterCtx = {
    content: message.content,
    authorId: message.author.id,
    channelId: message.channel.id,
    roleIds,
    redis: context.redis,
    phishingDomains
  };

  for (const rule of rules) {
    const exceptions = parseExceptions(rule.exceptions);
    if (isExcepted(exceptions, message.channel.id, roleIds)) {
      continue;
    }

    const threshold = parseThreshold(rule.threshold);
    const wordList = parseWordList(rule.wordList);
    const result = await evaluateRule(
      rule.ruleType as AutoModRuleType,
      filterCtx,
      threshold,
      wordList
    );

    if (result) {
      await applyAutoModAction(
        context,
        message,
        result.ruleType,
        rule.action,
        result.reason,
        rule.durationMs
      );
      return result;
    }
  }

  return null;
}

export async function activateLockdown(guild: Guild): Promise<void> {
  for (const channel of guild.channels.cache.values()) {
    if (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement) {
      continue;
    }
    const everyone = guild.roles.everyone;
    await channel.permissionOverwrites.edit(everyone, {
      SendMessages: false
    });
  }
}

export async function deactivateLockdown(guild: Guild): Promise<void> {
  for (const channel of guild.channels.cache.values()) {
    if (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement) {
      continue;
    }
    const everyone = guild.roles.everyone;
    await channel.permissionOverwrites.edit(everyone, {
      SendMessages: null
    });
  }
}

export async function handleAntiRaidJoin(
  context: BotContext,
  member: GuildMember
): Promise<void> {
  const config = await getAutoModConfig(context.prisma, member.guild.id);
  if (!config.antiRaidEnabled) {
    return;
  }

  // While VERIFY raid-response is active, keep gating new joins with the unverified role.
  if (config.lockdownActive && config.antiRaidAction === 'VERIFY') {
    await applyRaidVerifyRole(context, member);
    return;
  }

  if (config.lockdownActive) {
    return;
  }

  const key = `automod:raid:${member.guild.id}`;
  const count = await context.redis.incr(key);
  if (count === 1) {
    await context.redis.expire(key, config.antiRaidWindowSeconds);
  }

  if (count < config.antiRaidJoinThreshold) {
    return;
  }

  await context.prisma.autoModConfig.update({
    where: { guildId: member.guild.id },
    data: { lockdownActive: true }
  });

  if (config.antiRaidAction === 'LOCKDOWN') {
    await activateLockdown(member.guild);
  } else if (config.antiRaidAction === 'VERIFY') {
    await applyRaidVerifyRole(context, member);
  }

  const alertChannel = member.guild.systemChannel ?? member.guild.publicUpdatesChannel;
  if (alertChannel && alertChannel.isTextBased()) {
    await (alertChannel as TextChannel).send(
      `Anti-raid triggered: ${count} joins in ${config.antiRaidWindowSeconds}s (${config.antiRaidAction}).`
    );
  }
}

async function applyRaidVerifyRole(context: BotContext, member: GuildMember): Promise<void> {
  const verification = await context.prisma.verificationConfig.findUnique({
    where: { guildId: member.guild.id }
  });
  const roleId = verification?.unverifiedRoleId;
  if (!roleId) {
    logger.warn(
      { guildId: member.guild.id },
      'Anti-raid VERIFY triggered but no unverified role is configured'
    );
    return;
  }
  const me = member.guild.members.me;
  if (!me?.permissions.has(PermissionFlagsBits.ManageRoles)) {
    return;
  }
  const role = member.guild.roles.cache.get(roleId);
  if (!role || role.position >= me.roles.highest.position) {
    return;
  }
  if (!member.roles.cache.has(roleId)) {
    await member.roles.add(roleId).catch((error) => {
      logger.warn({ error, memberId: member.id }, 'Failed to apply unverified role during raid verify');
    });
  }
}

export async function handleAntiNukeAction(
  context: BotContext,
  guildId: string,
  executorId: string,
  actionType: 'ban' | 'channel_delete'
): Promise<void> {
  const config = await getAutoModConfig(context.prisma, guildId);
  if (!config.antiNukeEnabled) {
    return;
  }

  const botId = context.client.user?.id;
  if (!botId || executorId === botId) {
    return;
  }

  const key = `automod:nuke:${guildId}:${executorId}:${actionType}`;
  const count = await context.redis.incr(key);
  if (count === 1) {
    await context.redis.expire(key, config.antiNukeWindowSeconds);
  }

  const threshold =
    actionType === 'ban' ? config.antiNukeBanThreshold : config.antiNukeChannelThreshold;
  if (count < threshold) {
    return;
  }

  const guild = await context.client.guilds.fetch(guildId);
  const executor = await guild.members.fetch(executorId).catch(() => null);
  if (!executor) {
    return;
  }

  const me = guild.members.me;
  if (!me?.permissions.has(PermissionFlagsBits.Administrator)) {
    return;
  }

  await executor.roles.set([], 'Anti-nuke: mass destructive action detected');
  await createCase(context, {
    guildId,
    targetUserId: executorId,
    moderatorId: botId,
    action: 'ANTI_NUKE',
    reason: `Anti-nuke triggered (${actionType}: ${count} in ${config.antiNukeWindowSeconds}s)`,
    source: 'automod',
    metadata: { actionType, count, antiNuke: true }
  });
}
