import { Client, EmbedBuilder, TextChannel } from 'discord.js';
import { prisma } from '../database';
import { settingsStore } from '../config/state';
import { logger } from '../utils/logger';

export function normalizeBirthdayInput(raw: string) {
  const input = (raw || '').trim();
  if (!input) return null;

  const iso = input.match(/^(\d{4})-(\d{2})-(\d{2})$/);
  if (iso) {
    const year = Number(iso[1]);
    const month = Number(iso[2]);
    const day = Number(iso[3]);
    if (isValidDate(day, month, year)) return `${year.toString().padStart(4, '0')}-${pad(month)}-${pad(day)}`;
    return null;
  }

  const dmy = input.match(/^(\d{2})\.(\d{2})\.(\d{4})$/);
  if (dmy) {
    const day = Number(dmy[1]);
    const month = Number(dmy[2]);
    const year = Number(dmy[3]);
    if (isValidDate(day, month, year)) return `${year.toString().padStart(4, '0')}-${pad(month)}-${pad(day)}`;
    return null;
  }

  const dm = input.match(/^(\d{2})\.(\d{2})\.?$/);
  if (dm) {
    const day = Number(dm[1]);
    const month = Number(dm[2]);
    if (isValidDate(day, month, 2000)) return `--${pad(month)}-${pad(day)}`;
    return null;
  }

  return null;
}

function isValidDate(day: number, month: number, year?: number) {
  if (month < 1 || month > 12 || day < 1 || day > 31) return false;
  const checkYear = year ?? 2000;
  const d = new Date(checkYear, month - 1, day);
  return d.getFullYear() === checkYear && d.getMonth() + 1 === month && d.getDate() === day;
}

function pad(num: number) {
  return String(num).padStart(2, '0');
}

export interface BirthdayRecord {
  id: string;
  userId: string;
  guildId: string;
  birthDate: string;
  createdAt: Date;
  updatedAt: Date;
}

export class BirthdayService {
  private client: Client | null = null;
  private timer: NodeJS.Timeout | null = null;
  private lastSent = new Map<string, string>();

  public setClient(client: Client) {
    this.client = client;
  }

  public startScheduler(intervalMs = 60 * 60 * 1000) {
    if (this.timer) clearInterval(this.timer);
    const interval = Math.max(10 * 60 * 1000, intervalMs);
    this.timer = setInterval(() => this.checkAndSend().catch((err) => logger.warn(`birthday check failed: ${err}`)), interval);
  }

  public stopScheduler() {
    if (this.timer) clearInterval(this.timer);
    this.timer = null;
  }

  public async setBirthday(guildId: string, userId: string, birthDate: string) {
    await prisma.birthday.upsert({
      where: { birthday_user_guild: { guildId, userId } },
      update: { birthDate },
      create: { guildId, userId, birthDate }
    });
  }

  public async getBirthday(guildId: string, userId: string) {
    return prisma.birthday.findUnique({ where: { birthday_user_guild: { guildId, userId } } });
  }

  public async listBirthdays(guildId: string) {
    return prisma.birthday.findMany({ where: { guildId }, orderBy: { birthDate: 'asc' } });
  }

  public async checkAndSend(force = false) {
    if (!this.client) return;
    const now = new Date();
    const month = now.getMonth() + 1;
    const day = now.getDate();
    const todayKey = `${now.getFullYear()}-${pad(month)}-${pad(day)}`;

    for (const [guildId, cfgRaw] of settingsStore.all()) {
      const cfg = cfgRaw ?? {};
      const enabled = cfg.birthdayEnabled ?? cfg.birthdayConfig?.enabled ?? true;
      if (!enabled) continue;
      const channelId = cfg.birthdayConfig?.channelId;
      if (!channelId) continue;
      const sendHour = cfg.birthdayConfig?.sendHour ?? 9;
      if (!force) {
        if (this.lastSent.get(guildId) === todayKey) continue;
        if (now.getHours() < sendHour) continue;
      }

      const matches = await prisma.birthday.findMany({
        where: { guildId, birthDate: { endsWith: `-${pad(month)}-${pad(day)}` } }
      });
      if (!matches.length) continue;
      await this.publishBirthdays(guildId, channelId, matches, cfg.birthdayConfig?.messageTemplate);
      this.lastSent.set(guildId, todayKey);
    }
  }

  public invalidate(guildId: string) {
    this.lastSent.delete(guildId);
  }

  private async publishBirthdays(guildId: string, channelId: string, birthdays: BirthdayRecord[], template?: string) {
    if (!this.client) return;
    const channel = await this.client.channels.fetch(channelId).catch(() => null);
    if (!channel || !channel.isTextBased()) return;
    const guild = this.client.guilds.cache.get(guildId) ?? (await this.client.guilds.fetch(guildId).catch(() => null));
    const members = new Map<string, any>();
    if (guild) {
      await Promise.all(
        birthdays.map(async (entry) => {
          const mem = await guild.members.fetch(entry.userId).catch(() => null);
          if (mem) members.set(entry.userId, mem);
        })
      );
    }
    const mentionList = birthdays.map((b) => `<@${b.userId}>`).join(' ');
    const embed = new EmbedBuilder()
      .setTitle('Happy Birthday!')
      .setColor(0xf97316)
      .setTimestamp(new Date());

    const lines: string[] = [];
    birthdays.forEach((entry, idx) => {
      const member = members?.get(entry.userId);
      if (idx === 0 && member) {
        embed.setThumbnail(member.user.displayAvatarURL() || null);
        embed.setAuthor({ name: member.displayName || member.user.username, iconURL: member.user.displayAvatarURL() || undefined });
      }
      const line = (template || 'Alles Gute zum Geburtstag, {user}!').replace(/\{user\}/g, `<@${entry.userId}>`);
      lines.push(line);
    });
    embed.setDescription(lines.join('\n'));
    await (channel as TextChannel).send({ content: mentionList || undefined, embeds: [embed] });
  }
}
