import { PermissionFlagsBits, type GuildMember, type Role } from 'discord.js';
import type { BotContext } from '../../types.js';
import { getWelcomeConfig, resolveWelcomePayload, resolveLeavePayload, renderWelcomeText } from './service.js';
import { sendLeaveMessage, sendWelcomeMessage } from './renderer.js';
import { logger } from '../../logger.js';

async function resolveRole(member: GuildMember, roleId: string): Promise<Role | null> {
  const cached = member.guild.roles.cache.get(roleId);
  if (cached) {
    return cached;
  }
  return member.guild.roles.fetch(roleId).catch(() => null);
}

async function applyAutoroles(member: GuildMember, roleIds: string[]): Promise<void> {
  const uniqueIds = [...new Set(roleIds.filter(Boolean))];
  if (uniqueIds.length === 0) {
    return;
  }

  const me =
    member.guild.members.me ?? (await member.guild.members.fetchMe().catch(() => null));
  if (!me) {
    logger.warn({ guildId: member.guild.id }, 'Autoroles skipped: bot member unavailable');
    return;
  }
  if (!me.permissions.has(PermissionFlagsBits.ManageRoles)) {
    logger.warn({ guildId: member.guild.id }, 'Autoroles skipped: missing ManageRoles permission');
    return;
  }

  const assignable: string[] = [];
  for (const roleId of uniqueIds) {
    if (roleId === member.guild.id) {
      continue;
    }
    if (member.roles.cache.has(roleId)) {
      continue;
    }

    const role = await resolveRole(member, roleId);
    if (!role) {
      logger.warn({ guildId: member.guild.id, roleId }, 'Autorole skipped: role not found');
      continue;
    }
    if (role.managed) {
      logger.warn({ guildId: member.guild.id, roleId }, 'Autorole skipped: managed role');
      continue;
    }
    if (!role.editable) {
      logger.warn(
        {
          guildId: member.guild.id,
          roleId,
          rolePosition: role.position,
          botHighest: me.roles.highest.position
        },
        'Autorole skipped: role above bot or not editable'
      );
      continue;
    }
    assignable.push(roleId);
  }

  if (assignable.length === 0) {
    return;
  }

  await member.roles.add(assignable, 'Nexumi welcome autorole').catch((error) => {
    logger.warn({ error, memberId: member.id, assignable }, 'Failed to assign autoroles');
  });
}

export async function handleMemberWelcome(context: BotContext, member: GuildMember): Promise<void> {
  const config = await getWelcomeConfig(context.prisma, member.guild.id);

  const roleIds = member.user.bot ? config.botAutoroleIds : config.userAutoroleIds;
  await applyAutoroles(member, roleIds);

  if (config.welcomeEnabled && config.welcomeChannelId) {
    const channel = await member.guild.channels.fetch(config.welcomeChannelId).catch(() => null);
    if (channel?.isTextBased() && !channel.isDMBased()) {
      await sendWelcomeMessage(
        channel,
        resolveWelcomePayload(config),
        member,
        member.guild
      );
    }
  }

  if (config.welcomeDmEnabled && config.welcomeDmContent && !member.user.bot) {
    await member
      .send({ content: renderWelcomeText(config.welcomeDmContent, member, member.guild) })
      .catch(() => undefined);
  }
}

export async function handleMemberLeave(context: BotContext, member: GuildMember): Promise<void> {
  const config = await getWelcomeConfig(context.prisma, member.guild.id);
  if (!config.leaveEnabled || !config.leaveChannelId) {
    return;
  }
  const channel = await member.guild.channels.fetch(config.leaveChannelId).catch(() => null);
  if (!channel?.isTextBased() || channel.isDMBased()) {
    return;
  }
  await sendLeaveMessage(channel, resolveLeavePayload(config), member, member.guild);
}

export function registerWelcomeEvents(context: BotContext): void {
  context.client.on('guildMemberAdd', async (member) => {
    await handleMemberWelcome(context, member);
    const { handleAntiRaidJoin } = await import('../automod/actions.js');
    await handleAntiRaidJoin(context, member);
  });

  context.client.on('guildMemberRemove', async (member) => {
    if (!member.guild) {
      return;
    }
    await handleMemberLeave(context, member as GuildMember);
  });
}
