import {
  PermissionFlagsBits,
  type GuildTextBasedChannel,
  type Message,
  type TextChannel
} from 'discord.js';
import {
  DashboardMessageSendSchema,
  MessageComponentsV2Schema,
  WelcomeEmbedSchema,
  type MessageComponentsV2
} from '@nexumi/shared';
import { ensureGuild } from '../../guild.js';
import { buildEmbedFromPayload } from '../../lib/embed-payload.js';
import { buildComponentsV2Payload } from '../../lib/components-v2-payload.js';
import { logger } from '../../logger.js';
import type { BotContext } from '../../types.js';

export class MessageSendError extends Error {
  constructor(public readonly code: string) {
    super(code);
  }
}

async function assertSendableChannel(
  context: BotContext,
  channelId: string
): Promise<GuildTextBasedChannel> {
  const channel = await context.client.channels.fetch(channelId);
  if (!channel?.isTextBased() || channel.isDMBased()) {
    throw new MessageSendError('invalid_channel');
  }

  const me = channel.guild.members.me;
  if (
    !me?.permissionsIn(channel).has(PermissionFlagsBits.SendMessages) ||
    !me.permissionsIn(channel).has(PermissionFlagsBits.ViewChannel)
  ) {
    throw new MessageSendError('channel_unavailable');
  }

  return channel;
}

export type DashboardMessageSendJob = {
  guildId: string;
  channelId: string;
  createdById: string;
  mode: 'EMBED' | 'COMPONENTS_V2';
  embed?: unknown;
  components?: unknown;
  bindingId?: string | null;
};

export type DashboardMessageSendResult = {
  bindingId: string | null;
  messageId: string | null;
  channelId: string;
};

export async function sendDashboardMessage(
  context: BotContext,
  job: DashboardMessageSendJob
): Promise<DashboardMessageSendResult> {
  await ensureGuild(context.prisma, job.guildId);
  const input = DashboardMessageSendSchema.parse({
    channelId: job.channelId,
    mode: job.mode,
    embed: job.embed ?? null,
    components: job.components ?? null
  });

  const channel = await assertSendableChannel(context, input.channelId);

  if (input.mode === 'EMBED') {
    const embedData = WelcomeEmbedSchema.parse(input.embed);
    const embed = buildEmbedFromPayload(embedData, { renderText: (value) => value });
    if (!embed) {
      throw new MessageSendError('content_required');
    }
    const message = await (channel as TextChannel).send({ embeds: [embed] });
    return { bindingId: null, messageId: message.id, channelId: channel.id };
  }

  const components = MessageComponentsV2Schema.parse(input.components) as MessageComponentsV2;
  let bindingId = job.bindingId ?? null;

  if (!bindingId) {
    const binding = await context.prisma.componentMessageBinding.create({
      data: {
        guildId: job.guildId,
        channelId: input.channelId,
        payload: components,
        createdById: job.createdById
      }
    });
    bindingId = binding.id;
  }

  const payload = buildComponentsV2Payload(components, {
    source: 'm',
    ref: bindingId
  });
  if (!payload) {
    throw new MessageSendError('content_required');
  }

  const message: Message = await (channel as TextChannel).send(payload);
  await context.prisma.componentMessageBinding.update({
    where: { id: bindingId },
    data: { messageId: message.id }
  });

  logger.info(
    { guildId: job.guildId, channelId: channel.id, messageId: message.id, bindingId },
    'Dashboard Components V2 message sent'
  );

  return { bindingId, messageId: message.id, channelId: channel.id };
}
