import {
  ActionRowBuilder,
  ButtonBuilder,
  ButtonStyle,
  EmbedBuilder,
  PermissionFlagsBits,
  type ButtonInteraction,
  type GuildBasedChannel,
  type GuildMember,
  type GuildTextBasedChannel
} from 'discord.js';
import { CaptchaProviderSchema, t, tf, type CaptchaProvider } from '@nexumi/shared';
import type { BotContext } from '../../types.js';
import { getGuildLocale } from '../../i18n.js';
import {
  accountAgeDays,
  createCaptchaChallenge,
  getVerificationConfig,
  parseVerificationButton,
  verificationButtonId,
  verificationCaptchaButtonId
} from './service.js';
import { detectAltAccount } from './alt-detection.js';
import { env } from '../../env.js';
import { logger } from '../../logger.js';
import { ephemeral } from '../../interaction-reply.js';

export class VerificationPanelError extends Error {
  constructor(public readonly code: 'not_configured' | 'missing_permission') {
    super(code);
    this.name = 'VerificationPanelError';
  }
}

function botCanPostPanel(
  channel: GuildBasedChannel,
  meId: string
): channel is GuildTextBasedChannel {
  if (!channel.isTextBased() || channel.isDMBased()) {
    return false;
  }
  const permissions = channel.permissionsFor(meId);
  if (!permissions) {
    return false;
  }
  return (
    permissions.has(PermissionFlagsBits.ViewChannel) &&
    permissions.has(PermissionFlagsBits.SendMessages) &&
    permissions.has(PermissionFlagsBits.EmbedLinks)
  );
}

export type CompleteVerificationOptions = {
  ipHash?: string | null;
  userAgentHash?: string | null;
  captchaProvider?: string | null;
};

async function applyFailAction(
  member: GuildMember,
  failAction: string,
  reason: string
): Promise<void> {
  const me = member.guild.members.me;
  if (failAction === 'KICK' && me?.permissions.has(PermissionFlagsBits.KickMembers)) {
    await member.kick(reason);
    return;
  }
  if (failAction === 'BAN' && me?.permissions.has(PermissionFlagsBits.BanMembers)) {
    await member.guild.members.ban(member.id, { reason });
  }
}

async function logVerificationFailure(
  context: BotContext,
  member: GuildMember,
  input: {
    reason: string;
    detail?: string;
    failAction?: string;
    matchedUserId?: string | null;
  }
): Promise<void> {
  const { logVerificationFail } = await import('../logging/events.js');
  await logVerificationFail(context, {
    guildId: member.guild.id,
    userId: member.id,
    userTag: member.user.tag,
    reason: input.reason,
    detail: input.detail,
    failAction: input.failAction,
    matchedUserId: input.matchedUserId
  });
}

export async function completeVerification(
  context: BotContext,
  guildId: string,
  userId: string,
  options: CompleteVerificationOptions = {}
): Promise<{ ok: boolean; reason?: string }> {
  const config = await getVerificationConfig(context.prisma, guildId);
  if (!config.enabled || !config.verifiedRoleId) {
    return { ok: false, reason: 'not_configured' };
  }

  const guild = await context.client.guilds.fetch(guildId);
  const member = await guild.members.fetch(userId).catch(() => null);
  if (!member) {
    return { ok: false, reason: 'member_not_found' };
  }

  const ageDays = accountAgeDays(member.user.createdAt);
  if (config.minAccountAgeDays > 0 && ageDays < config.minAccountAgeDays) {
    const failReason = `Verification failed: account too young (${ageDays}d)`;
    await logVerificationFailure(context, member, {
      reason: 'account_too_young',
      detail: `Account age ${ageDays}d (required ${config.minAccountAgeDays}d)`,
      failAction: config.failAction
    });
    await applyFailAction(member, config.failAction, failReason);
    return { ok: false, reason: 'account_too_young' };
  }

  const inviteJoin = await context.prisma.inviteJoin.findUnique({
    where: { guildId_userId: { guildId, userId } }
  });

  let flaggedAsAlt = false;
  let matchedUserId: string | null = null;

  if (config.altDetectionEnabled) {
    const lookbackMs = Math.max(
      config.altIpWindowDays * 24 * 60 * 60 * 1000,
      config.altInviteWindowHours * 60 * 60 * 1000
    );
    const since = new Date(Date.now() - lookbackMs);
    const priorRecords = await context.prisma.verificationRecord.findMany({
      where: {
        guildId,
        userId: { not: userId },
        createdAt: { gte: since }
      },
      select: {
        userId: true,
        inviteCode: true,
        ipHash: true,
        createdAt: true,
        flaggedAsAlt: true
      }
    });

    const match = detectAltAccount({
      userId,
      accountAgeDays: ageDays,
      inviteCode: inviteJoin?.code ?? null,
      ipHash: options.ipHash ?? null,
      priorRecords,
      altIpWindowDays: config.altIpWindowDays,
      altInviteWindowHours: config.altInviteWindowHours,
      altMaxAccountsPerInvite: config.altMaxAccountsPerInvite
    });

    if (match) {
      flaggedAsAlt = true;
      matchedUserId = match.matchedUserId;
      logger.info(
        { guildId, userId, reason: match.reason, matchedUserId },
        'Verification alt-account signal'
      );

      await context.prisma.verificationRecord.upsert({
        where: { guildId_userId: { guildId, userId } },
        create: {
          guildId,
          userId,
          inviteCode: inviteJoin?.code ?? null,
          inviterId: inviteJoin?.inviterId ?? null,
          ipHash: options.ipHash ?? null,
          userAgentHash: options.userAgentHash ?? null,
          captchaProvider: options.captchaProvider ?? null,
          flaggedAsAlt: true,
          matchedUserId
        },
        update: {
          inviteCode: inviteJoin?.code ?? null,
          inviterId: inviteJoin?.inviterId ?? null,
          ipHash: options.ipHash ?? null,
          userAgentHash: options.userAgentHash ?? null,
          captchaProvider: options.captchaProvider ?? null,
          flaggedAsAlt: true,
          matchedUserId
        }
      });

      if (config.altBlockOnMatch) {
        const failReason = `Verification failed: possible alt account (${match.reason})`;
        await logVerificationFailure(context, member, {
          reason: 'alt_detected',
          detail: `Signal: ${match.reason}`,
          failAction: config.failAction,
          matchedUserId
        });
        await applyFailAction(member, config.failAction, failReason);
        return { ok: false, reason: 'alt_detected' };
      }

      await logVerificationFailure(context, member, {
        reason: 'alt_detected_flagged',
        detail: `Signal: ${match.reason} (flagged only, not blocked)`,
        failAction: 'NONE',
        matchedUserId
      });
    }
  }

  const me = guild.members.me;
  if (!me?.permissions.has(PermissionFlagsBits.ManageRoles)) {
    return { ok: false, reason: 'missing_permissions' };
  }

  const verifiedRole = guild.roles.cache.get(config.verifiedRoleId);
  if (!verifiedRole || verifiedRole.position >= me.roles.highest.position) {
    return { ok: false, reason: 'invalid_role' };
  }

  if (config.unverifiedRoleId && member.roles.cache.has(config.unverifiedRoleId)) {
    await member.roles.remove(config.unverifiedRoleId).catch((error) => {
      logger.warn({ error }, 'Failed to remove unverified role');
    });
  }

  if (!member.roles.cache.has(config.verifiedRoleId)) {
    await member.roles.add(config.verifiedRoleId);
  }

  await context.prisma.verificationRecord.upsert({
    where: { guildId_userId: { guildId, userId } },
    create: {
      guildId,
      userId,
      inviteCode: inviteJoin?.code ?? null,
      inviterId: inviteJoin?.inviterId ?? null,
      ipHash: options.ipHash ?? null,
      userAgentHash: options.userAgentHash ?? null,
      captchaProvider: options.captchaProvider ?? null,
      flaggedAsAlt,
      matchedUserId
    },
    update: {
      inviteCode: inviteJoin?.code ?? null,
      inviterId: inviteJoin?.inviterId ?? null,
      ipHash: options.ipHash ?? null,
      userAgentHash: options.userAgentHash ?? null,
      captchaProvider: options.captchaProvider ?? null,
      flaggedAsAlt,
      matchedUserId
    }
  });

  return { ok: true };
}

export function buildVerificationPanel(
  config: Awaited<ReturnType<typeof getVerificationConfig>>,
  locale: 'de' | 'en'
) {
  const embed = new EmbedBuilder()
    .setTitle(t(locale, 'verification.panel.title'))
    .setDescription(t(locale, 'verification.panel.description'))
    .setColor(0x6366f1);

  const customId =
    config.mode === 'CAPTCHA'
      ? verificationCaptchaButtonId(config.guildId)
      : verificationButtonId(config.guildId);

  const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
    new ButtonBuilder()
      .setCustomId(customId)
      .setLabel(
        config.mode === 'CAPTCHA'
          ? t(locale, 'verification.panel.buttonCaptcha')
          : t(locale, 'verification.panel.button')
      )
      .setStyle(ButtonStyle.Success)
  );

  return { embeds: [embed], components: [row] };
}

/**
 * Posts or updates the verification panel in the configured channel.
 * Used by `/verify panel` and by the dashboard via BullMQ (`verificationPanelSync`).
 */
export async function syncVerificationPanel(
  context: BotContext,
  guildId: string,
  preferredChannelId?: string | null
): Promise<{ panelChannelId: string; panelMessageId: string }> {
  const locale = await getGuildLocale(context.prisma, guildId);
  const config = await getVerificationConfig(context.prisma, guildId);

  if (!config.enabled || !config.verifiedRoleId) {
    throw new VerificationPanelError('not_configured');
  }

  const targetChannelId = preferredChannelId || config.channelId;
  if (!targetChannelId) {
    throw new VerificationPanelError('not_configured');
  }

  const guild = await context.client.guilds.fetch(guildId);
  const me = guild.members.me;
  if (!me) {
    throw new VerificationPanelError('missing_permission');
  }

  const channel = await guild.channels.fetch(targetChannelId).catch(() => null);
  if (!channel || !botCanPostPanel(channel, me.id)) {
    throw new VerificationPanelError('missing_permission');
  }

  const panel = buildVerificationPanel(config, locale);
  const sameChannel =
    Boolean(config.panelMessageId) && config.panelChannelId === channel.id;

  if (sameChannel && config.panelMessageId) {
    try {
      const existing = await channel.messages.fetch(config.panelMessageId);
      await existing.edit(panel);
      return { panelChannelId: channel.id, panelMessageId: existing.id };
    } catch (error) {
      logger.warn(
        { error, guildId, messageId: config.panelMessageId },
        'Failed to edit verification panel; recreating'
      );
    }
  }

  if (config.panelMessageId && config.panelChannelId && config.panelChannelId !== channel.id) {
    try {
      const oldChannel = await guild.channels.fetch(config.panelChannelId);
      if (oldChannel?.isTextBased() && !oldChannel.isDMBased()) {
        const oldMessage = await oldChannel.messages.fetch(config.panelMessageId);
        await oldMessage.delete();
      }
    } catch {
      // Previous panel may already be gone
    }
  }

  const message = await channel.send(panel);
  await context.prisma.verificationConfig.update({
    where: { guildId },
    data: {
      panelChannelId: channel.id,
      panelMessageId: message.id
    }
  });

  return { panelChannelId: channel.id, panelMessageId: message.id };
}

export async function runVerificationPanelSyncJob(
  context: BotContext,
  data: { guildId: string }
): Promise<{ panelChannelId: string; panelMessageId: string }> {
  return syncVerificationPanel(context, data.guildId);
}

export async function handleVerificationButton(
  interaction: ButtonInteraction,
  context: BotContext
): Promise<void> {
  const parsed = parseVerificationButton(interaction.customId);
  if (!parsed || !interaction.guild) {
    return;
  }

  const locale = await getGuildLocale(context.prisma, interaction.guild.id);
  const config = await getVerificationConfig(context.prisma, parsed.guildId);

  if (!config.enabled) {
    await interaction.reply({ content: t(locale, 'verification.disabled'), ...ephemeral() });
    return;
  }

  if (parsed.mode === 'captcha') {
    const providerParse = CaptchaProviderSchema.safeParse(config.captchaProvider);
    const provider: CaptchaProvider = providerParse.success ? providerParse.data : 'MATH';
    const challenge = await createCaptchaChallenge(
      context.redis,
      parsed.guildId,
      interaction.user.id,
      provider
    );
    const base = (env.WEBUI_URL ?? env.PUBLIC_BASE_URL).replace(/\/$/, '');
    const url = `${base}/verify/captcha?token=${challenge.token}`;
    await interaction.reply({
      content: tf(locale, 'verification.captchaLink', { url }),
      ...ephemeral()
    });
    return;
  }

  await interaction.deferReply(ephemeral());
  const result = await completeVerification(context, parsed.guildId, interaction.user.id);
  if (result.ok) {
    await interaction.editReply({ content: t(locale, 'verification.success') });
    return;
  }

  const key = `verification.error.${result.reason ?? 'generic'}`;
  await interaction.editReply({ content: t(locale, key) });
}
