import {
  ActionRowBuilder,
  ButtonBuilder,
  ButtonStyle,
  ChannelType,
  EmbedBuilder,
  OverwriteType,
  PermissionFlagsBits,
  type CategoryChannel,
  type Guild,
  type GuildMember,
  type VoiceChannel
} from 'discord.js';
import { applyTagPlaceholders, t, tf, type Locale } from '@nexumi/shared';
import { ensureGuild } from '../../guild.js';
import { logger } from '../../logger.js';
import type { BotContext } from '../../types.js';

export const TV_BUTTON_PREFIX = 'tv:';
export const TV_MODAL_PREFIX = 'tv:modal:';

export async function getTempVoiceConfig(prisma: BotContext['prisma'], guildId: string) {
  await ensureGuild(prisma, guildId);
  let config = await prisma.tempVoiceConfig.findUnique({ where: { guildId } });
  if (!config) {
    config = await prisma.tempVoiceConfig.create({ data: { guildId } });
  }
  return config;
}

export async function updateTempVoiceConfig(
  prisma: BotContext['prisma'],
  guildId: string,
  data: {
    enabled?: boolean;
    hubChannelId?: string | null;
    categoryId?: string | null;
    defaultName?: string;
    defaultLimit?: number;
  }
) {
  await ensureGuild(prisma, guildId);
  return prisma.tempVoiceConfig.upsert({
    where: { guildId },
    update: data,
    create: { guildId, ...data }
  });
}

export async function getTempVoiceChannel(prisma: BotContext['prisma'], channelId: string) {
  return prisma.tempVoiceChannel.findUnique({ where: { channelId } });
}

export async function getTempVoiceChannelForMember(
  prisma: BotContext['prisma'],
  guildId: string,
  member: GuildMember
): Promise<{ record: NonNullable<Awaited<ReturnType<typeof getTempVoiceChannel>>>; channel: VoiceChannel } | null> {
  const voiceChannelId = member.voice.channelId;
  if (!voiceChannelId) {
    return null;
  }

  const record = await getTempVoiceChannel(prisma, voiceChannelId);
  if (!record || record.guildId !== guildId) {
    return null;
  }

  const channel = member.guild.channels.cache.get(voiceChannelId);
  if (!channel?.isVoiceBased() || channel.isDMBased()) {
    return null;
  }

  return { record, channel: channel as VoiceChannel };
}

export function renderDefaultChannelName(template: string, member: GuildMember): string {
  const name = applyTagPlaceholders(template, {
    user: member.displayName,
    'user.name': member.displayName,
    'user.tag': member.user.username
  });
  return name.slice(0, 100);
}

export function buildControlPanelRows(channelId: string, locale: Locale): ActionRowBuilder<ButtonBuilder>[] {
  const mk = (action: string, labelKey: string, style: ButtonStyle = ButtonStyle.Secondary) =>
    new ButtonBuilder()
      .setCustomId(`${TV_BUTTON_PREFIX}${action}:${channelId}`)
      .setLabel(t(locale, labelKey))
      .setStyle(style);

  return [
    new ActionRowBuilder<ButtonBuilder>().addComponents(
      mk('rename', 'tempvoice.button.rename'),
      mk('limit', 'tempvoice.button.limit'),
      mk('lock', 'tempvoice.button.lock'),
      mk('hide', 'tempvoice.button.hide')
    ),
    new ActionRowBuilder<ButtonBuilder>().addComponents(
      mk('kick', 'tempvoice.button.kick', ButtonStyle.Danger),
      mk('transfer', 'tempvoice.button.transfer'),
      mk('claim', 'tempvoice.button.claim', ButtonStyle.Success)
    )
  ];
}

export function buildControlPanelEmbed(channelName: string, locale: Locale): EmbedBuilder {
  return new EmbedBuilder()
    .setTitle(t(locale, 'tempvoice.panel.title'))
    .setDescription(tf(locale, 'tempvoice.panel.description', { channel: channelName }))
    .setColor(0x6366f1);
}

export async function sendControlPanelToChannel(
  channel: VoiceChannel,
  locale: Locale
): Promise<void> {
  const embed = buildControlPanelEmbed(channel.name, locale);
  const rows = buildControlPanelRows(channel.id, locale);
  await channel.send({ embeds: [embed], components: rows }).catch((error) => {
    logger.debug({ error, channelId: channel.id }, 'Could not send temp voice control panel');
  });
}

export async function createTempVoiceChannel(
  context: BotContext,
  member: GuildMember,
  config: Awaited<ReturnType<typeof getTempVoiceConfig>>
): Promise<VoiceChannel | null> {
  if (!config.categoryId) {
    return null;
  }

  const category = await member.guild.channels.fetch(config.categoryId).catch(() => null);
  if (!category || category.type !== ChannelType.GuildCategory) {
    return null;
  }

  const channelName = renderDefaultChannelName(config.defaultName, member);
  const limit = config.defaultLimit > 0 ? config.defaultLimit : undefined;

  const channel = await member.guild.channels.create({
    name: channelName,
    type: ChannelType.GuildVoice,
    parent: category.id,
    userLimit: limit,
    permissionOverwrites: [
      {
        id: member.guild.roles.everyone.id,
        type: OverwriteType.Role,
        allow: [PermissionFlagsBits.Connect, PermissionFlagsBits.ViewChannel]
      },
      {
        id: member.id,
        type: OverwriteType.Member,
        allow: [
          PermissionFlagsBits.Connect,
          PermissionFlagsBits.ViewChannel,
          PermissionFlagsBits.ManageChannels,
          PermissionFlagsBits.MoveMembers,
          PermissionFlagsBits.SendMessages
        ]
      },
      {
        id: member.guild.members.me!.id,
        type: OverwriteType.Member,
        allow: [
          PermissionFlagsBits.Connect,
          PermissionFlagsBits.ViewChannel,
          PermissionFlagsBits.ManageChannels,
          PermissionFlagsBits.MoveMembers,
          PermissionFlagsBits.SendMessages
        ]
      }
    ],
    reason: 'Temp voice join-to-create'
  });

  await context.prisma.tempVoiceChannel.create({
    data: {
      guildId: member.guild.id,
      channelId: channel.id,
      ownerId: member.id
    }
  });

  await member.voice.setChannel(channel).catch(async (error) => {
    logger.warn({ error, channelId: channel.id }, 'Failed to move member into temp voice channel');
    await channel.delete('Temp voice move failed').catch(() => undefined);
    await context.prisma.tempVoiceChannel.delete({ where: { channelId: channel.id } }).catch(() => undefined);
  });

  if (!member.voice.channelId || member.voice.channelId !== channel.id) {
    return null;
  }

  const { getGuildLocale } = await import('../../i18n.js');
  const locale = await getGuildLocale(context.prisma, member.guild.id);
  await sendControlPanelToChannel(channel, locale);
  return channel;
}

export async function deleteTempVoiceChannel(context: BotContext, channelId: string): Promise<void> {
  await context.prisma.tempVoiceChannel.delete({ where: { channelId } }).catch(() => undefined);
  const channel = await context.client.channels.fetch(channelId).catch(() => null);
  if (channel?.isVoiceBased()) {
    await channel.delete('Temp voice channel empty').catch((error) => {
      logger.warn({ error, channelId }, 'Failed to delete temp voice channel');
    });
  }
}

export async function assertTempVoiceOwner(
  context: BotContext,
  member: GuildMember,
  channelId: string
): Promise<{ record: NonNullable<Awaited<ReturnType<typeof getTempVoiceChannel>>>; channel: VoiceChannel } | null> {
  const record = await getTempVoiceChannel(context.prisma, channelId);
  if (!record || record.guildId !== member.guild.id) {
    return null;
  }

  const channel = member.guild.channels.cache.get(channelId);
  if (!channel?.isVoiceBased() || channel.isDMBased()) {
    return null;
  }

  if (record.ownerId !== member.id) {
    return null;
  }

  return { record, channel: channel as VoiceChannel };
}

export async function renameTempChannel(channel: VoiceChannel, name: string): Promise<void> {
  await channel.setName(name.slice(0, 100));
}

export async function setTempChannelLimit(channel: VoiceChannel, limit: number): Promise<void> {
  await channel.setUserLimit(limit);
}

export async function lockTempChannel(channel: VoiceChannel): Promise<boolean> {
  await channel.permissionOverwrites.edit(channel.guild.roles.everyone, {
    Connect: false
  });
  return true;
}

export async function hideTempChannel(channel: VoiceChannel): Promise<boolean> {
  await channel.permissionOverwrites.edit(channel.guild.roles.everyone, {
    ViewChannel: false
  });
  return true;
}

export async function kickFromTempChannel(channel: VoiceChannel, targetId: string): Promise<boolean> {
  const target = channel.members.get(targetId);
  if (!target) {
    return false;
  }
  await target.voice.disconnect('Temp voice kick');
  return true;
}

export async function transferTempChannelOwnership(
  context: BotContext,
  record: { channelId: string; ownerId: string },
  channel: VoiceChannel,
  newOwner: GuildMember,
  locale: Locale
): Promise<void> {
  await context.prisma.tempVoiceChannel.update({
    where: { channelId: record.channelId },
    data: { ownerId: newOwner.id }
  });

  await channel.permissionOverwrites.edit(record.ownerId, {
    ManageChannels: null,
    MoveMembers: null
  });
  await channel.permissionOverwrites.edit(newOwner.id, {
    Connect: true,
    ViewChannel: true,
    ManageChannels: true,
    MoveMembers: true,
    SendMessages: true
  });

  await sendControlPanelToChannel(channel, locale);
}

export async function claimTempChannel(
  context: BotContext,
  record: { channelId: string; ownerId: string },
  channel: VoiceChannel,
  claimer: GuildMember,
  locale: Locale
): Promise<'owner_present' | 'claimed' | 'not_in_channel'> {
  if (channel.members.has(record.ownerId)) {
    return 'owner_present';
  }
  if (!channel.members.has(claimer.id)) {
    return 'not_in_channel';
  }

  await transferTempChannelOwnership(context, record, channel, claimer, locale);
  return 'claimed';
}

export async function resolveHubJoin(
  context: BotContext,
  member: GuildMember,
  hubChannelId: string
): Promise<void> {
  const config = await getTempVoiceConfig(context.prisma, member.guild.id);
  if (!config.enabled || config.hubChannelId !== hubChannelId || !config.categoryId) {
    return;
  }

  await createTempVoiceChannel(context, member, config);
}

export async function resolveVoiceLeave(
  context: BotContext,
  guild: Guild,
  channelId: string
): Promise<void> {
  const record = await getTempVoiceChannel(context.prisma, channelId);
  if (!record) {
    return;
  }

  const channel = guild.channels.cache.get(channelId);
  if (!channel?.isVoiceBased()) {
    await context.prisma.tempVoiceChannel.delete({ where: { channelId } }).catch(() => undefined);
    return;
  }

  if (channel.members.size === 0) {
    await deleteTempVoiceChannel(context, channelId);
  }
}

export function parseTempVoiceButton(customId: string): { action: string; channelId: string } | null {
  if (!customId.startsWith(TV_BUTTON_PREFIX) || customId.startsWith(TV_MODAL_PREFIX)) {
    return null;
  }
  const body = customId.slice(TV_BUTTON_PREFIX.length);
  const sep = body.indexOf(':');
  if (sep <= 0) {
    return null;
  }
  return {
    action: body.slice(0, sep),
    channelId: body.slice(sep + 1)
  };
}

export function parseTempVoiceModal(customId: string): { action: string; channelId: string } | null {
  if (!customId.startsWith(TV_MODAL_PREFIX)) {
    return null;
  }
  const body = customId.slice(TV_MODAL_PREFIX.length);
  const sep = body.indexOf(':');
  if (sep <= 0) {
    return null;
  }
  return {
    action: body.slice(0, sep),
    channelId: body.slice(sep + 1)
  };
}

export function tempVoiceModalId(action: string, channelId: string): string {
  return `${TV_MODAL_PREFIX}${action}:${channelId}`;
}

export function tempVoiceButtonId(action: string, channelId: string): string {
  return `${TV_BUTTON_PREFIX}${action}:${channelId}`;
}

export async function fetchCategory(guild: Guild, categoryId: string): Promise<CategoryChannel | null> {
  const channel = await guild.channels.fetch(categoryId).catch(() => null);
  if (!channel || channel.type !== ChannelType.GuildCategory) {
    return null;
  }
  return channel;
}
