import { Events } from 'discord.js';
import { logger } from '../../logger.js';
import type { BotContext } from '../../types.js';
import { addVoiceMinutes, incrementMessageActivity, voiceSessionKey } from './activity.js';
import {
  isFakeJoin,
  recordMemberJoin,
  recordMemberLeave,
  removeInviteFromCache,
  resolveUsedInvite,
  syncAllGuildInviteCaches,
  syncGuildInviteCache,
  upsertInviteInCache
} from './invites.js';

export function registerStatsEvents(context: BotContext): void {
  context.client.once(Events.ClientReady, () => {
    void syncAllGuildInviteCaches(context.client, context.redis).catch((error) => {
      logger.error({ error }, 'Initial invite cache sync failed');
    });
  });

  context.client.on(Events.GuildCreate, (guild) => {
    void syncGuildInviteCache(guild, context.redis).catch((error) => {
      logger.error({ error, guildId: guild.id }, 'Invite cache sync failed for new guild');
    });
  });

  context.client.on(Events.InviteCreate, (invite) => {
    if (!invite.guild) {
      return;
    }
    void upsertInviteInCache(context.redis, invite.guild.id, invite).catch((error) => {
      logger.error({ error, inviteCode: invite.code }, 'Failed to update invite cache on create');
    });
  });

  context.client.on(Events.InviteDelete, (invite) => {
    if (!invite.guild) {
      return;
    }
    void removeInviteFromCache(context.redis, invite.guild.id, invite.code).catch((error) => {
      logger.error({ error, inviteCode: invite.code }, 'Failed to update invite cache on delete');
    });
  });

  context.client.on(Events.GuildMemberAdd, async (member) => {
    try {
      const usedInvite = await resolveUsedInvite(context.redis, member.guild);
      const isFake = isFakeJoin(member);

      await recordMemberJoin(
        context.prisma,
        member.guild.id,
        member.id,
        usedInvite?.inviterId ?? null,
        usedInvite?.code ?? null,
        isFake
      );
    } catch (error) {
      logger.error({ error, userId: member.id, guildId: member.guild.id }, 'Invite join tracking failed');
    }
  });

  context.client.on(Events.GuildMemberRemove, async (member) => {
    try {
      await recordMemberLeave(context.prisma, member.guild.id, member.id);
    } catch (error) {
      logger.error({ error, userId: member.id, guildId: member.guild.id }, 'Invite leave tracking failed');
    }
  });

  context.client.on(Events.MessageCreate, async (message) => {
    try {
      if (!message.guild || message.author.bot) {
        return;
      }

      await incrementMessageActivity(
        context.prisma,
        message.guild.id,
        message.author.id,
        message.channel.id
      );
    } catch (error) {
      logger.error({ error, messageId: message.id }, 'Activity message tracking failed');
    }
  });

  context.client.on(Events.VoiceStateUpdate, async (oldState, newState) => {
    try {
      const guildId = newState.guild.id;
      const userId = newState.id;
      const oldChannelId = oldState.channelId;
      const newChannelId = newState.channelId;

      if (oldChannelId && oldChannelId !== newChannelId) {
        const key = voiceSessionKey(guildId, userId);
        const joinedAt = await context.redis.get(key);
        await context.redis.del(key);

        if (joinedAt) {
          await addVoiceMinutes(
            context.prisma,
            guildId,
            userId,
            oldChannelId,
            Number(joinedAt)
          );
        }
      }

      if (newChannelId && newChannelId !== oldChannelId) {
        await context.redis.set(voiceSessionKey(guildId, userId), String(Date.now()));
      }
    } catch (error) {
      logger.error({ error, userId: newState.id }, 'Activity voice tracking failed');
    }
  });
}
