import {
  PermissionFlagsBits,
  type ButtonInteraction,
  type GuildMember,
  type Interaction,
  type MessageComponentInteraction,
  type RoleSelectMenuInteraction,
  type StringSelectMenuInteraction
} from 'discord.js';
import {
  decodeComponentCustomId,
  mapComponentsV2TextFields,
  parseMessageComponentsV2,
  t,
  type ComponentAction,
  type ComponentV2Source,
  type Locale,
  type MessageComponentsV2
} from '@nexumi/shared';
import type { BotContext } from '../../types.js';
import { getGuildLocale } from '../../i18n.js';
import { logger } from '../../logger.js';

export function isComponentsV2Interaction(customId: string): boolean {
  return decodeComponentCustomId(customId) !== null;
}

async function loadPayload(
  context: BotContext,
  source: ComponentV2Source,
  ref: string,
  guildId: string
): Promise<MessageComponentsV2 | null> {
  switch (source) {
    case 'w': {
      const config = await context.prisma.welcomeConfig.findUnique({ where: { guildId: ref } });
      if (!config || config.guildId !== guildId) {
        return null;
      }
      return parseMessageComponentsV2(config.welcomeComponents);
    }
    case 'l': {
      const config = await context.prisma.welcomeConfig.findUnique({ where: { guildId: ref } });
      if (!config || config.guildId !== guildId) {
        return null;
      }
      return parseMessageComponentsV2(config.leaveComponents);
    }
    case 't': {
      const tag = await context.prisma.tag.findUnique({ where: { id: ref } });
      if (!tag || tag.guildId !== guildId) {
        return null;
      }
      return parseMessageComponentsV2(tag.components);
    }
    case 's': {
      const schedule = await context.prisma.scheduledMessage.findUnique({ where: { id: ref } });
      if (!schedule || schedule.guildId !== guildId) {
        return null;
      }
      return parseMessageComponentsV2(schedule.components);
    }
    case 'm': {
      const binding = await context.prisma.componentMessageBinding.findUnique({ where: { id: ref } });
      if (!binding || binding.guildId !== guildId) {
        return null;
      }
      return parseMessageComponentsV2(binding.payload);
    }
    case 'a': {
      if (ref !== 'singleton') {
        return null;
      }
      const config = await context.prisma.botAboutConfig.findUnique({ where: { id: 'singleton' } });
      return parseMessageComponentsV2(config?.components);
    }
    default:
      return null;
  }
}

function resolveStringSelectAction(
  payload: MessageComponentsV2,
  actionId: string,
  selectedValues: string[]
): ComponentAction | null {
  const direct = payload.actions[actionId];
  if (direct && selectedValues.length === 0) {
    return direct;
  }

  // Prefer per-option actionId when present.
  const findOptionAction = (nodes: MessageComponentsV2['components']): ComponentAction | null => {
    for (const node of nodes) {
      if (node.type === 'string_select') {
        for (const option of node.options) {
          if (selectedValues.includes(option.value) && option.actionId) {
            return payload.actions[option.actionId] ?? null;
          }
        }
      }
      if (node.type === 'action_row') {
        const nested = findOptionAction(node.components as MessageComponentsV2['components']);
        if (nested) {
          return nested;
        }
      }
      if (node.type === 'container') {
        const nested = findOptionAction(node.components);
        if (nested) {
          return nested;
        }
      }
    }
    return null;
  };

  return findOptionAction(payload.components) ?? payload.actions[actionId] ?? null;
}

async function assertRoleAssignable(member: GuildMember, roleId: string, locale: Locale): Promise<string | null> {
  const me = member.guild.members.me;
  if (!me?.permissions.has(PermissionFlagsBits.ManageRoles)) {
    return t(locale, 'componentsV2.error.botMissingManageRoles');
  }
  const role = member.guild.roles.cache.get(roleId) ?? (await member.guild.roles.fetch(roleId).catch(() => null));
  if (!role) {
    return t(locale, 'componentsV2.error.roleNotFound');
  }
  if (role.managed) {
    return t(locale, 'componentsV2.error.roleManaged');
  }
  if (role.position >= me.roles.highest.position) {
    return t(locale, 'componentsV2.error.roleHierarchy');
  }
  return null;
}

async function executeAction(
  interaction: MessageComponentInteraction,
  action: ComponentAction,
  locale: Locale,
  selectedRoleIds: string[]
): Promise<void> {
  const member = interaction.member;
  if (!member || !interaction.guild || typeof member === 'string') {
    await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true });
    return;
  }

  const guildMember =
    'roles' in member && typeof (member as GuildMember).roles?.add === 'function'
      ? (member as GuildMember)
      : await interaction.guild.members.fetch(interaction.user.id);

  switch (action.type) {
    case 'NONE':
      await interaction.deferUpdate();
      return;
    case 'EPHEMERAL_REPLY':
      await interaction.reply({ content: action.content ?? '—', ephemeral: true });
      return;
    case 'PUBLIC_REPLY':
      await interaction.reply({ content: action.content ?? '—' });
      return;
    case 'ASSIGN_ROLE': {
      const err = await assertRoleAssignable(guildMember, action.roleId!, locale);
      if (err) {
        await interaction.reply({ content: err, ephemeral: true });
        return;
      }
      await guildMember.roles.add(action.roleId!);
      logger.info(
        { guildId: interaction.guildId, userId: interaction.user.id, roleId: action.roleId, action: 'ASSIGN_ROLE' },
        'Components V2 role action'
      );
      await interaction.reply({
        content: t(locale, 'componentsV2.role.assigned'),
        ephemeral: true
      });
      return;
    }
    case 'REMOVE_ROLE': {
      const err = await assertRoleAssignable(guildMember, action.roleId!, locale);
      if (err) {
        await interaction.reply({ content: err, ephemeral: true });
        return;
      }
      await guildMember.roles.remove(action.roleId!);
      logger.info(
        { guildId: interaction.guildId, userId: interaction.user.id, roleId: action.roleId, action: 'REMOVE_ROLE' },
        'Components V2 role action'
      );
      await interaction.reply({
        content: t(locale, 'componentsV2.role.removed'),
        ephemeral: true
      });
      return;
    }
    case 'TOGGLE_ROLE': {
      const err = await assertRoleAssignable(guildMember, action.roleId!, locale);
      if (err) {
        await interaction.reply({ content: err, ephemeral: true });
        return;
      }
      const hasRole = guildMember.roles.cache.has(action.roleId!);
      if (hasRole) {
        await guildMember.roles.remove(action.roleId!);
        await interaction.reply({
          content: t(locale, 'componentsV2.role.removed'),
          ephemeral: true
        });
      } else {
        await guildMember.roles.add(action.roleId!);
        await interaction.reply({
          content: t(locale, 'componentsV2.role.assigned'),
          ephemeral: true
        });
      }
      logger.info(
        {
          guildId: interaction.guildId,
          userId: interaction.user.id,
          roleId: action.roleId,
          action: 'TOGGLE_ROLE',
          removed: hasRole
        },
        'Components V2 role action'
      );
      return;
    }
    case 'ASSIGN_SELECTED_ROLES': {
      if (selectedRoleIds.length === 0) {
        await interaction.reply({
          content: t(locale, 'componentsV2.error.noRolesSelected'),
          ephemeral: true
        });
        return;
      }
      const assigned: string[] = [];
      for (const roleId of selectedRoleIds) {
        const err = await assertRoleAssignable(guildMember, roleId, locale);
        if (err) {
          continue;
        }
        await guildMember.roles.add(roleId);
        assigned.push(roleId);
      }
      logger.info(
        { guildId: interaction.guildId, userId: interaction.user.id, roleIds: assigned, action: 'ASSIGN_SELECTED_ROLES' },
        'Components V2 role action'
      );
      if (assigned.length === 0) {
        await interaction.reply({
          content: t(locale, 'componentsV2.error.roleNotFound'),
          ephemeral: true
        });
        return;
      }
      await interaction.reply({
        content: t(locale, 'componentsV2.role.assignedSelected'),
        ephemeral: true
      });
      return;
    }
    default:
      await interaction.reply({ content: t(locale, 'generic.error'), ephemeral: true });
  }
}

export async function handleComponentsV2Interaction(
  interaction: Interaction,
  context: BotContext
): Promise<void> {
  if (
    !interaction.isButton() &&
    !interaction.isStringSelectMenu() &&
    !interaction.isUserSelectMenu() &&
    !interaction.isRoleSelectMenu() &&
    !interaction.isChannelSelectMenu() &&
    !interaction.isMentionableSelectMenu()
  ) {
    return;
  }

  const locale = await getGuildLocale(context.prisma, interaction.guildId);
  if (!interaction.inGuild() || !interaction.guildId) {
    await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true });
    return;
  }

  const decoded = decodeComponentCustomId(interaction.customId);
  if (!decoded) {
    return;
  }

  const payload = await loadPayload(context, decoded.source, decoded.ref, interaction.guildId);
  if (!payload) {
    await interaction.reply({
      content: t(locale, 'componentsV2.error.notFound'),
      ephemeral: true
    });
    return;
  }

  let action: ComponentAction | null = null;
  let selectedRoleIds: string[] = [];

  if (interaction.isStringSelectMenu()) {
    action = resolveStringSelectAction(payload, decoded.actionId, interaction.values);
  } else if (interaction.isRoleSelectMenu()) {
    action = payload.actions[decoded.actionId] ?? null;
    selectedRoleIds = (interaction as RoleSelectMenuInteraction).values;
  } else if (interaction.isButton()) {
    action = payload.actions[decoded.actionId] ?? null;
  } else {
    action = payload.actions[decoded.actionId] ?? null;
  }

  if (!action) {
    await interaction.reply({
      content: t(locale, 'componentsV2.error.actionMissing'),
      ephemeral: true
    });
    return;
  }

  // Apply placeholders for reply content when possible.
  const renderedActions = mapComponentsV2TextFields(payload, (value) => value).actions;
  const rendered = renderedActions[decoded.actionId] ?? action;

  await executeAction(
    interaction as MessageComponentInteraction,
    interaction.isStringSelectMenu() ? action : rendered,
    locale,
    selectedRoleIds
  );
}

// Re-export for typing convenience in index routing.
export type ComponentsV2ButtonInteraction = ButtonInteraction;
export type ComponentsV2StringSelectInteraction = StringSelectMenuInteraction;
