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

export interface ReactionRoleEntry {
  emoji: string;
  roleId: string;
  label?: string;
  description?: string;
}

export interface ReactionRoleSetInput {
  id?: string;
  guildId: string;
  channelId: string;
  messageId?: string | null;
  title?: string;
  description?: string;
  entries?: ReactionRoleEntry[];
}

export class ReactionRoleService {
  private client: Client | null = null;
  private cache = new Map<string, ReactionRoleEntry[]>();

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

  public async loadCache() {
    this.cache.clear();
    const sets = await prisma.reactionRoleSet.findMany({ where: { messageId: { not: null } } });
    sets.forEach((set) => {
      if (set.messageId) this.cache.set(set.messageId, set.entries as ReactionRoleEntry[]);
    });
  }

  public async listSets(guildId: string) {
    return prisma.reactionRoleSet.findMany({ where: { guildId }, orderBy: { createdAt: 'desc' } });
  }

  public async saveSet(input: ReactionRoleSetInput) {
    const entries = this.sanitizeEntries(input.entries);
    const base = {
      guildId: input.guildId,
      channelId: input.channelId,
      messageId: input.messageId || null,
      title: input.title,
      description: input.description,
      entries
    };
    let record: any;
    if (input.id) {
      const existing = await prisma.reactionRoleSet.findFirst({ where: { id: input.id, guildId: input.guildId } });
      if (!existing) throw new Error('set not found');
      record = await prisma.reactionRoleSet.update({ where: { id: input.id }, data: base });
    } else {
      record = await prisma.reactionRoleSet.create({ data: base });
    }
    const messageId = await this.syncMessage(record.guildId, record.channelId, record.messageId, record.title, record.description, entries);
    if (messageId && messageId !== record.messageId) {
      record = await prisma.reactionRoleSet.update({ where: { id: record.id }, data: { messageId } });
    }
    if (record.messageId) this.cache.set(record.messageId, entries);
    return record;
  }

  public async deleteSet(guildId: string, id: string) {
    const record = await prisma.reactionRoleSet.findFirst({ where: { id, guildId } });
    if (!record) return;
    if (record.messageId) this.cache.delete(record.messageId);
    await prisma.reactionRoleSet.delete({ where: { id } });
  }

  public async handleReaction(reaction: MessageReaction, user: User, add: boolean) {
    if (!reaction.message.guildId || user.bot) return;
    const guildId = reaction.message.guildId;
    const cfg = settingsStore.get(guildId) || {};
    const enabled = cfg.reactionRolesEnabled ?? cfg.reactionRolesConfig?.enabled ?? true;
    if (!enabled) return;
    if (!reaction.message.partial && reaction.message.author?.id === user.id && add) return;
    const entries = await this.getEntriesForMessage(reaction.message.id);
    if (!entries?.length) return;
    const entry = entries.find((e) => this.matchesEmoji(e.emoji, reaction));
    if (!entry) return;
    const guild = this.client?.guilds.cache.get(guildId) ?? (await this.client?.guilds.fetch(guildId).catch(() => null));
    if (!guild) return;
    const member = await guild.members.fetch(user.id).catch(() => null);
    if (!member) return;
    const role = guild.roles.cache.get(entry.roleId);
    if (!role) return;
    const me = guild.members.me;
    if (!me?.permissions.has(PermissionsBitField.Flags.ManageRoles) || me.roles.highest.comparePositionTo(role) <= 0) return;
    if (add) {
      await member.roles.add(role, 'Reaction role');
    } else {
      await member.roles.remove(role, 'Reaction role removed');
    }
  }

  public async resyncGuild(guildId: string) {
    const cfg = settingsStore.get(guildId) || {};
    const enabled = cfg.reactionRolesEnabled ?? cfg.reactionRolesConfig?.enabled ?? true;
    if (!enabled) return;
    const sets = await prisma.reactionRoleSet.findMany({ where: { guildId } });
    for (const set of sets) {
      const messageId = await this.syncMessage(set.guildId, set.channelId, set.messageId, set.title, set.description, set.entries as ReactionRoleEntry[]);
      if (messageId && messageId !== set.messageId) {
        await prisma.reactionRoleSet.update({ where: { id: set.id }, data: { messageId } });
      }
      if (messageId) this.cache.set(messageId, set.entries as ReactionRoleEntry[]);
    }
  }

  public async ensureMessage(guildId: string, id: string) {
    const set = await prisma.reactionRoleSet.findFirst({ where: { id, guildId } });
    if (!set) return null;
    const messageId = await this.syncMessage(set.guildId, set.channelId, set.messageId, set.title, set.description, set.entries as ReactionRoleEntry[]);
    if (messageId && messageId !== set.messageId) {
      await prisma.reactionRoleSet.update({ where: { id: set.id }, data: { messageId } });
    }
    if (messageId) this.cache.set(messageId, set.entries as ReactionRoleEntry[]);
    return messageId;
  }

  private async getEntriesForMessage(messageId: string) {
    if (this.cache.has(messageId)) return this.cache.get(messageId);
    const set = await prisma.reactionRoleSet.findFirst({ where: { messageId } });
    if (set) {
      const entries = (set.entries as ReactionRoleEntry[]) || [];
      this.cache.set(messageId, entries);
      return entries;
    }
    return null;
  }

  private sanitizeEntries(entries?: ReactionRoleEntry[]) {
    if (!Array.isArray(entries)) return [];
    return entries
      .map((e) => ({
        emoji: (e.emoji || '').trim(),
        roleId: (e.roleId || '').trim(),
        label: e.label?.trim(),
        description: e.description?.trim()
      }))
      .filter((e) => e.emoji && e.roleId);
  }

  private matchesEmoji(target: string, reaction: MessageReaction) {
    const normalized = (target || '').replace(/[<>]/g, '').replace(/^:/, '').trim();
    const id = reaction.emoji.id;
    const name = reaction.emoji.name || '';
    if (!normalized) return false;
    if (id && (normalized === id || normalized.endsWith(id) || normalized === `${name}:${id}`)) return true;
    if (name && (normalized === name || normalized === reaction.emoji.toString())) return true;
    if (reaction.emoji.identifier && normalized === reaction.emoji.identifier) return true;
    return false;
  }

  private async syncMessage(
    guildId: string,
    channelId: string,
    messageId: string | null | undefined,
    title: string | null | undefined,
    description: string | null | undefined,
    entries: ReactionRoleEntry[]
  ) {
    if (!this.client) return null;
    const channel = await this.client.channels.fetch(channelId).catch(() => null);
    if (!channel || !channel.isTextBased()) return null;
    const perms = channel.permissionsFor(this.client.user?.id ?? '');
    const canReact = perms?.has(PermissionsBitField.Flags.AddReactions);
    if (!perms?.has(PermissionsBitField.Flags.SendMessages)) return null;
    const embed = new EmbedBuilder()
      .setTitle(title || 'Reaction Roles')
      .setColor(0xf97316)
      .setDescription(description || 'Reagiere, um Rollen zu erhalten.');
    const lines = entries.map((e) => `${e.emoji} <@&${e.roleId}>${e.label ? ' — ' + e.label : ''}${e.description ? ' · ' + e.description : ''}`);
    if (lines.length) embed.addFields({ name: 'Rollen', value: lines.join('\n') });
    let message = null as any;
    if (messageId) {
      message = await (channel as TextChannel).messages.fetch(messageId).catch(() => null);
      if (message) {
        await message.edit({ embeds: [embed] });
      }
    }
    if (!message) {
      message = await (channel as TextChannel).send({ embeds: [embed] });
    }
    if (canReact) {
      for (const entry of entries) {
        try {
          await message.react(entry.emoji);
        } catch (err) {
          logger.warn(`Failed to react with ${entry.emoji}: ${err}`);
        }
      }
    }
    return message.id as string;
  }
}
