import {
  ActionRowBuilder,
  ButtonBuilder,
  ButtonStyle,
  EmbedBuilder,
  PermissionFlagsBits,
  type ButtonInteraction,
  type GuildMember
} 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 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 });
  }
}

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) {
    await applyFailAction(
      member,
      config.failAction,
      `Verification failed: account too young (${ageDays}d)`
    );
    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) {
        await applyFailAction(
          member,
          config.failAction,
          `Verification failed: possible alt account (${match.reason})`
        );
        return { ok: false, reason: 'alt_detected' };
      }
    }
  }

  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] };
}

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) });
}
