import { Client, Colors, EmbedBuilder, Guild, TextChannel } from 'discord.js';
import { prisma } from '../database';
import { logger } from '../utils/logger';
import { auditLogService } from './AuditLogService';

const REMINDER_INTERVAL_MS = 60 * 1000;
const ONE_HOUR_MS = 60 * 60 * 1000;
const TEN_MINUTES_MS = 10 * 60 * 1000;

function toDiscordTimestamps(date: Date): { full: string; relative: string } {
  const unix = Math.floor(date.getTime() / 1000);
  return {
    full: `<t:${unix}:F>`,
    relative: `<t:${unix}:R>`,
  };
}

function buildEventEmbed(
  mode: 'announcement' | 'oneHourReminder' | 'tenMinuteReminder' | 'started',
  title: string,
  description: string | null,
  startsAt: Date,
  createdByUserId: string,
) {
  const timestamps = toDiscordTimestamps(startsAt);

  const config = {
    announcement: {
      color: Colors.Blurple,
      titlePrefix: 'Event Scheduled',
      lead: 'A new server event has been scheduled.',
    },
    oneHourReminder: {
      color: Colors.Gold,
      titlePrefix: 'Event Reminder',
      lead: 'This event starts in about 1 hour.',
    },
    tenMinuteReminder: {
      color: Colors.Orange,
      titlePrefix: 'Event Reminder',
      lead: 'This event starts in about 10 minutes.',
    },
    started: {
      color: Colors.Green,
      titlePrefix: 'Event Started',
      lead: 'This event is starting now.',
    },
  }[mode];

  const embed = new EmbedBuilder()
    .setColor(config.color)
    .setTitle(`${config.titlePrefix}: ${title}`)
    .setDescription(description || config.lead)
    .addFields(
      { name: 'Starts At', value: timestamps.full, inline: true },
      { name: 'Relative', value: timestamps.relative, inline: true },
      { name: 'Created By', value: `<@${createdByUserId}>`, inline: true },
    )
    .setTimestamp();

  if (description) {
    embed.addFields({ name: 'Summary', value: config.lead, inline: false });
  }

  return embed;
}

async function resolveAnnouncementChannel(guild: Guild, channelId: string | null): Promise<TextChannel | null> {
  if (!channelId) return null;

  const channel = await guild.channels.fetch(channelId).catch(() => null);
  if (!channel || !channel.isTextBased() || !(channel instanceof TextChannel)) {
    return null;
  }

  return channel;
}

export class EventService {
  private static reminderInterval: NodeJS.Timeout | null = null;

  public static async announceEvent(guild: Guild, eventId: string) {
    const event = await prisma.guildEvent.findFirst({
      where: {
        id: eventId,
        guildId: guild.id,
      },
    });

    if (!event) {
      throw new Error(`GuildEvent ${eventId} not found for guild ${guild.id}`);
    }

    const channel = await resolveAnnouncementChannel(guild, event.announcementChannelId);
    if (!channel) {
      throw new Error(`Announcement channel is not configured or unavailable for event ${eventId}`);
    }

    const embed = buildEventEmbed(
      'announcement',
      event.title,
      event.description,
      event.startsAt,
      event.createdByUserId,
    );

    await channel.send({ embeds: [embed] });

    await prisma.guildEvent.update({
      where: { id: event.id },
      data: { announcedAt: new Date() },
    });
  }

  public static startReminderLoop(client: Client) {
    if (this.reminderInterval) {
      clearInterval(this.reminderInterval);
    }

    this.processDueEvents(client).catch((error) => {
      logger.error('EventService: Initial event processing failed', error);
    });

    this.reminderInterval = setInterval(() => {
      this.processDueEvents(client).catch((error) => {
        logger.error('EventService: Scheduled event processing failed', error);
      });
    }, REMINDER_INTERVAL_MS);

    logger.info('EventService: Reminder loop started (1m interval).');
  }

  public static stopReminderLoop() {
    if (this.reminderInterval) {
      clearInterval(this.reminderInterval);
      this.reminderInterval = null;
      logger.info('EventService: Reminder loop stopped.');
    }
  }

  private static async processDueEvents(client: Client) {
    const now = new Date();
    const upcomingEvents = await prisma.guildEvent.findMany({
      where: {
        status: 'SCHEDULED',
        startsAt: { gt: now },
      },
      orderBy: { startsAt: 'asc' },
    });

    for (const event of upcomingEvents) {
      if (event.reminderOffsets.length === 0) {
        continue;
      }

      const guild = client.guilds.cache.get(event.guildId) ?? await client.guilds.fetch(event.guildId).catch(() => null);
      if (!guild) continue;

      const channel = await resolveAnnouncementChannel(guild, event.announcementChannelId);
      if (!channel) continue;

      const diff = event.startsAt.getTime() - now.getTime();

      const dueOffsets = event.reminderOffsets.filter((offset: number) =>
        offset > 0 &&
        !event.sentReminderOffsets.includes(offset) &&
        diff <= offset * 60 * 1000 &&
        diff > 0
      );

      for (const offset of dueOffsets) {
        await this.sendReminder(guild, channel, event.id, offset);
      }
    }

    const startedEvents = await prisma.guildEvent.findMany({
      where: {
        status: 'SCHEDULED',
        startsAt: { lte: now },
      },
      orderBy: { startsAt: 'asc' },
    });

    for (const event of startedEvents) {
      const guild = client.guilds.cache.get(event.guildId) ?? await client.guilds.fetch(event.guildId).catch(() => null);
      if (!guild) continue;

      if (event.reminderOffsets.includes(0) && !event.sentReminderOffsets.includes(0)) {
        const channel = await resolveAnnouncementChannel(guild, event.announcementChannelId);

        if (channel) {
          try {
            const embed = buildEventEmbed(
              'started',
              event.title,
              event.description,
              event.startsAt,
              event.createdByUserId,
            );
            await channel.send({ embeds: [embed] });
          } catch (error) {
            logger.error(`EventService: Failed to send start announcement for event ${event.id}`, error);
            await auditLogService.log(guild, {
              category: 'SYSTEM',
              severity: 'WARN',
              title: 'Event Start Announcement Failed',
              description: `Failed to send start announcement for event **${event.title}** (\`${event.id}\`).`,
            }).catch(() => {});
          }
        }
      }

      await prisma.guildEvent.update({
        where: { id: event.id },
        data: {
          startedAnnounced: event.reminderOffsets.includes(0) ? true : event.startedAnnounced,
          sentReminderOffsets: event.reminderOffsets.includes(0) && !event.sentReminderOffsets.includes(0)
            ? [...event.sentReminderOffsets, 0]
            : event.sentReminderOffsets,
          status: 'COMPLETED',
        },
      });
    }
  }

  private static async sendReminder(
    guild: Guild,
    channel: TextChannel,
    eventId: string,
    offsetMinutes: number,
  ) {
    const event = await prisma.guildEvent.findUnique({ where: { id: eventId } });
    if (!event) return;

    const mode = offsetMinutes >= 60 ? 'oneHourReminder' : 'tenMinuteReminder';
    const embed = buildEventEmbed(
      mode,
      event.title,
      event.description,
      event.startsAt,
      event.createdByUserId,
    );

    try {
      await channel.send({ embeds: [embed] });

      await prisma.guildEvent.update({
        where: { id: event.id },
        data: {
          sentReminderOffsets: [...event.sentReminderOffsets, offsetMinutes],
          remindedOneHour: mode === 'oneHourReminder' ? true : event.remindedOneHour,
          remindedTenMinutes: mode === 'tenMinuteReminder' ? true : event.remindedTenMinutes,
        },
      });
    } catch (error) {
      logger.error(`EventService: Failed to send ${offsetMinutes}m reminder for event ${event.id}`, error);
      await auditLogService.log(guild, {
        category: 'SYSTEM',
        severity: 'WARN',
        title: 'Event Reminder Failed',
        description: `Failed to send ${offsetMinutes}m reminder for event **${event.title}** (\`${event.id}\`).`,
      }).catch(() => {});
    }
  }
}
