import {
  Client,
  Events,
  GatewayIntentBits,
  Partials,
  ShardingManager,
  type Interaction
} from 'discord.js';
import { env } from './env.js';
import { logger } from './logger.js';
import { prisma } from './db.js';
import { redis } from './redis.js';
import { registerCommands, routeCommand, routeContextMenu } from './commands.js';
import { ensureRecurringJobs, startWorkers } from './jobs.js';
import type { BotContext } from './types.js';
import { startHealthServer } from './health.js';
import { getGuildLocale } from './i18n.js';
import { ephemeral } from './interaction-reply.js';
import { t } from '@nexumi/shared';
import { handleAutoModMessage } from './modules/automod/actions.js';
import { registerLoggingEvents } from './modules/logging/events.js';
import { registerWelcomeEvents } from './modules/welcome/events.js';
import { registerVerificationEvents } from './modules/verification/events.js';
import { registerLevelingEvents } from './modules/leveling/events.js';
import { registerUtilityEvents } from './modules/utility/index.js';
import { registerTicketEvents } from './modules/tickets/index.js';
import { registerSelfRoleEvents } from './modules/selfroles/index.js';
import { registerTagEvents } from './modules/tags/index.js';
import { registerStarboardEvents } from './modules/starboard/index.js';
import { registerTempVoiceEvents } from './modules/tempvoice/index.js';
import { registerStatsEvents } from './modules/stats/index.js';
import { routeComponentInteraction } from './interaction-registry.js';
import { isPrimaryShard, runOncePerCluster } from './shard-lifecycle.js';
import { isGuildBlacklisted, isUserBlacklisted } from './module-gates.js';
import { publishShardMetrics, recordEventMetric } from './metrics.js';
import { captureException, initSentry } from './sentry.js';

const isShard = process.argv.includes('--shard');

if (!isShard) {
  initSentry('bot-manager');
  startHealthServer();
  const manager = new ShardingManager(new URL('./index.js', import.meta.url).pathname, {
    token: env.BOT_TOKEN,
    totalShards: 'auto',
    shardArgs: ['--shard']
  });
  manager.on('shardCreate', (shard) => logger.info({ shardId: shard.id }, 'Shard spawned'));

  // Register slash commands once from the manager (not once per shard).
  try {
    await runOncePerCluster(
      'nexumi:lock:register-commands',
      300,
      async () => {
        await registerCommands();
      },
      'Command registration'
    );
  } catch (error) {
    logger.error({ error }, 'Failed to register application commands from manager');
    captureException(error, { phase: 'registerCommands' });
  }

  await manager.spawn();
} else {
  initSentry('bot-shard');
  const client = new Client({
    intents: [
      GatewayIntentBits.Guilds,
      GatewayIntentBits.GuildMembers,
      GatewayIntentBits.GuildMessages,
      GatewayIntentBits.GuildMessageReactions,
      GatewayIntentBits.GuildModeration,
      GatewayIntentBits.GuildVoiceStates,
      GatewayIntentBits.GuildEmojisAndStickers,
      GatewayIntentBits.GuildInvites,
      GatewayIntentBits.MessageContent
    ],
    partials: [
      Partials.Channel,
      Partials.Message,
      Partials.GuildMember,
      Partials.Reaction
    ]
  });

  const context: BotContext = { client, prisma, redis };
  startWorkers(context);

  registerLoggingEvents(context);
  registerWelcomeEvents(context);
  registerVerificationEvents(context);
  registerLevelingEvents(context);
  registerUtilityEvents(context);
  registerTicketEvents(context);
  registerSelfRoleEvents(client, context);
  registerTagEvents(client, context);
  registerStarboardEvents(context);
  registerTempVoiceEvents(context);
  registerStatsEvents(context);

  client.on(Events.ClientReady, async () => {
    logger.info({ tag: client.user?.tag, shards: client.shard?.ids }, 'Bot ready');

    if (isPrimaryShard(client)) {
      try {
        await runOncePerCluster(
          'nexumi:lock:ensure-recurring-jobs',
          120,
          async () => {
            await ensureRecurringJobs();
          },
          'Recurring job registration'
        );
      } catch (error) {
        logger.error({ error }, 'Failed to ensure recurring jobs');
        captureException(error, { phase: 'ensureRecurringJobs' });
      }

      try {
        await runOncePerCluster(
          'nexumi:lock:recover-overdue-giveaways',
          60,
          async () => {
            const { recoverOverdueGiveaways } = await import('./modules/giveaways/index.js');
            await recoverOverdueGiveaways(context);
          },
          'Overdue giveaway recovery'
        );
      } catch (error) {
        logger.error({ error }, 'Failed to recover overdue giveaways');
        captureException(error, { phase: 'recoverOverdueGiveaways' });
      }
    }

    try {
      const { applyBotPresence, publishBotStatus } = await import('./presence.js');
      await applyBotPresence(context);
      await publishBotStatus(context);
    } catch (error) {
      logger.warn({ error }, 'Failed to apply initial bot presence/status');
    }

    try {
      await publishShardMetrics(context.redis, client);
    } catch (error) {
      logger.warn({ error }, 'Failed to publish initial shard metrics');
    }
  });

  client.on(Events.GuildCreate, async (guild) => {
    try {
      if (await isGuildBlacklisted(context.prisma, guild.id)) {
        logger.info({ guildId: guild.id }, 'Leaving blacklisted guild');
        await guild.leave();
      }
    } catch (error) {
      logger.error({ error, guildId: guild.id }, 'GuildCreate blacklist check failed');
      captureException(error, { guildId: guild.id });
    }
  });

  setInterval(() => {
    publishShardMetrics(context.redis, client).catch(() => undefined);
  }, 30_000);

  client.on(Events.MessageCreate, async (message) => {
    try {
      await recordEventMetric(context.redis);
      if (message.author.bot) return;
      if (await isUserBlacklisted(context.prisma, message.author.id)) return;
      if (message.guildId && (await isGuildBlacklisted(context.prisma, message.guildId))) return;
      await handleAutoModMessage(context, message);
    } catch (error) {
      logger.error({ error, messageId: message.id }, 'AutoMod message handler failed');
      captureException(error, { messageId: message.id });
    }
  });

  client.on(Events.InteractionCreate, async (interaction: Interaction) => {
    try {
      if (
        interaction.user &&
        (await isUserBlacklisted(context.prisma, interaction.user.id)) &&
        !interaction.isChatInputCommand() &&
        !interaction.isMessageContextMenuCommand()
      ) {
        if (interaction.isRepliable() && !interaction.replied && !interaction.deferred) {
          const locale = await getGuildLocale(context.prisma, interaction.guildId);
          await interaction.reply({
            content: t(locale, 'generic.userBlacklisted'),
            ephemeral: true
          });
        }
        return;
      }

      if (interaction.isChatInputCommand()) {
        await routeCommand(interaction, context);
        return;
      }

      if (interaction.isMessageContextMenuCommand()) {
        await routeContextMenu(interaction, context);
        return;
      }

      if (
        interaction.isButton() ||
        interaction.isStringSelectMenu() ||
        interaction.isUserSelectMenu() ||
        interaction.isRoleSelectMenu() ||
        interaction.isChannelSelectMenu() ||
        interaction.isMentionableSelectMenu() ||
        interaction.isModalSubmit()
      ) {
        await routeComponentInteraction(interaction, context);
      }
    } catch (error) {
      logger.error({ error }, 'Interaction handling failed');
      captureException(error, {
        interactionType: interaction.type,
        guildId: interaction.guildId
      });
      try {
        const locale = await getGuildLocale(context.prisma, interaction.guildId);
        const message = t(locale, 'generic.error');
        if (interaction.isRepliable()) {
          if (interaction.replied || interaction.deferred) {
            await interaction.followUp({ content: message, ...ephemeral() });
          } else {
            await interaction.reply({ content: message, ...ephemeral() });
          }
        }
      } catch (replyError) {
        logger.warn({ replyError }, 'Failed to send interaction error reply');
      }
    }
  });

  await client.login(env.BOT_TOKEN);
}
