import { Router, type Request } from 'express';
import { ChannelType, EmbedBuilder } from 'discord.js';
import { z } from 'zod';
import { client, makeRolePanelRow } from '../../bot/client.js';
import { env } from '../../config/env.js';
import { addAuditLog, getAuditLogs, getGuildSettings, saveGuildSettings } from '../../database/repositories.js';
import { canManageGuild, fetchDiscordGuilds } from '../../lib/discord-api.js';
import { ensureCsrf, requireAuth, verifyCsrf } from '../middleware/auth.js';

export const dashboardRouter = Router();
dashboardRouter.use(requireAuth);

async function getContext(req: Request) {
  const token = req.session.discord!.accessToken;
  const oauthGuilds = (await fetchDiscordGuilds(token)).filter(canManageGuild);
  const guilds = oauthGuilds
    .filter(guild => client.guilds.cache.has(guild.id))
    .map(guild => ({ ...guild, live: client.guilds.cache.get(guild.id)! }));

  const requestedId = typeof req.query.guild === 'string' ? req.query.guild : null;
  const selected = guilds.find(guild => guild.id === requestedId) ?? guilds[0] ?? null;
  return { guilds, selected };
}

function validateGuildAccess(guildId: string, guilds: Array<{ id: string }>) {
  return guilds.some(guild => guild.id === guildId) && client.guilds.cache.has(guildId);
}

function flash(req: Request, type: 'success' | 'error', message: string) {
  req.session.flash = { type, message };
}

dashboardRouter.get('/dashboard', async (req, res, next) => {
  try {
    const { guilds, selected } = await getContext(req);
    const csrfToken = ensureCsrf(req);
    const currentFlash = req.session.flash;
    req.session.flash = undefined;

    if (!selected) {
      return res.render('dashboard', {
        basePath: env.BASE_PATH,
        user: req.session.discord!.user,
        guilds: [],
        selected: null,
        channels: [],
        roles: [],
        settings: null,
        auditLogs: [],
        csrfToken,
        flash: currentFlash,
        inviteUrl: `https://discord.com/oauth2/authorize?client_id=${env.DISCORD_CLIENT_ID}&scope=bot%20applications.commands&permissions=268528646`
      });
    }

    const textChannels = selected.live.channels.cache
      .filter(channel => channel.type === ChannelType.GuildText || channel.type === ChannelType.GuildAnnouncement)
      .sort((a, b) => a.position - b.position)
      .map(channel => ({ id: channel.id, name: channel.name }));

    const roles = selected.live.roles.cache
      .filter(role => role.id !== selected.live.id && !role.managed)
      .sort((a, b) => b.position - a.position)
      .map(role => ({ id: role.id, name: role.name, color: role.hexColor }));

    res.render('dashboard', {
      basePath: env.BASE_PATH,
      user: req.session.discord!.user,
      guilds,
      selected,
      channels: textChannels,
      roles,
      settings: getGuildSettings(selected.id),
      auditLogs: getAuditLogs(selected.id),
      csrfToken,
      flash: currentFlash,
      inviteUrl: null
    });
  } catch (error) {
    next(error);
  }
});

const settingsSchema = z.object({
  _csrf: z.string(),
  welcomeEnabled: z.string().optional(),
  welcomeChannelId: z.string().optional(),
  welcomeMessage: z.string().trim().min(1).max(1000),
  autoRoleId: z.string().optional(),
  logChannelId: z.string().optional()
});

dashboardRouter.post('/guild/:guildId/settings', verifyCsrf, async (req, res, next) => {
  try {
    const parsed = settingsSchema.parse(req.body);
    const { guilds } = await getContext(req);
    const guildId = String(req.params.guildId);
    if (!validateGuildAccess(guildId, guilds)) return res.status(403).send('Accès refusé.');

    saveGuildSettings({
      guildId,
      welcomeEnabled: parsed.welcomeEnabled === 'on',
      welcomeChannelId: parsed.welcomeChannelId || null,
      welcomeMessage: parsed.welcomeMessage,
      autoRoleId: parsed.autoRoleId || null,
      logChannelId: parsed.logChannelId || null
    });

    addAuditLog({
      guildId,
      actorId: req.session.discord!.user.id,
      actorName: req.session.discord!.user.globalName ?? req.session.discord!.user.username,
      action: 'Paramètres mis à jour',
      details: 'Accueil, autorôle et journalisation.'
    });
    flash(req, 'success', 'Configuration enregistrée.');
    res.redirect(`${env.BASE_PATH}/dashboard?guild=${guildId}`);
  } catch (error) {
    next(error);
  }
});

const embedSchema = z.object({
  _csrf: z.string(),
  channelId: z.string().min(5),
  title: z.string().trim().min(1).max(256),
  description: z.string().trim().min(1).max(4000),
  color: z.string().regex(/^#[0-9a-fA-F]{6}$/),
  footer: z.string().trim().max(2048).optional(),
  imageUrl: z.union([z.literal(''), z.url()]).optional(),
  thumbnailUrl: z.union([z.literal(''), z.url()]).optional(),
  buttonLabel: z.string().trim().max(80).optional(),
  buttonUrl: z.union([z.literal(''), z.url()]).optional()
});

dashboardRouter.post('/guild/:guildId/embed', verifyCsrf, async (req, res, next) => {
  try {
    const parsed = embedSchema.parse(req.body);
    const { guilds } = await getContext(req);
    const guildId = String(req.params.guildId);
    if (!validateGuildAccess(guildId, guilds)) return res.status(403).send('Accès refusé.');

    const guild = client.guilds.cache.get(guildId)!;
    const channel = guild.channels.cache.get(parsed.channelId);
    if (!channel?.isTextBased() || channel.isDMBased()) throw new Error('Salon Discord invalide.');

    const embed = new EmbedBuilder()
      .setColor(Number.parseInt(parsed.color.slice(1), 16))
      .setTitle(parsed.title)
      .setDescription(parsed.description)
      .setTimestamp();
    if (parsed.footer) embed.setFooter({ text: parsed.footer });
    if (parsed.imageUrl) embed.setImage(parsed.imageUrl);
    if (parsed.thumbnailUrl) embed.setThumbnail(parsed.thumbnailUrl);

    const components = [];
    if (parsed.buttonLabel && parsed.buttonUrl) {
      const { ActionRowBuilder, ButtonBuilder, ButtonStyle } = await import('discord.js');
      components.push(new ActionRowBuilder<any>().addComponents(
        new ButtonBuilder().setLabel(parsed.buttonLabel).setURL(parsed.buttonUrl).setStyle(ButtonStyle.Link)
      ));
    }

    await channel.send({ embeds: [embed], components });
    addAuditLog({
      guildId,
      actorId: req.session.discord!.user.id,
      actorName: req.session.discord!.user.globalName ?? req.session.discord!.user.username,
      action: 'Embed publié',
      details: `${parsed.title} dans #${channel.name}`
    });
    flash(req, 'success', `Embed publié dans #${channel.name}.`);
    res.redirect(`${env.BASE_PATH}/dashboard?guild=${guildId}#embeds`);
  } catch (error) {
    next(error);
  }
});

const rolePanelSchema = z.object({
  _csrf: z.string(),
  channelId: z.string().min(5),
  title: z.string().trim().min(1).max(256),
  description: z.string().trim().min(1).max(2000),
  roles: z.string().trim().min(1)
});

dashboardRouter.post('/guild/:guildId/role-panel', verifyCsrf, async (req, res, next) => {
  try {
    const parsed = rolePanelSchema.parse(req.body);
    const { guilds } = await getContext(req);
    const guildId = String(req.params.guildId);
    if (!validateGuildAccess(guildId, guilds)) return res.status(403).send('Accès refusé.');

    const guild = client.guilds.cache.get(guildId)!;
    const channel = guild.channels.cache.get(parsed.channelId);
    if (!channel?.isTextBased() || channel.isDMBased()) throw new Error('Salon Discord invalide.');

    const roles = parsed.roles.split('\n').map(line => {
      const [id, label, emoji] = line.split('|').map(value => value?.trim());
      if (!id || !label || !guild.roles.cache.has(id)) throw new Error(`Rôle invalide : ${line}`);
      return { id, label, emoji: emoji || undefined };
    });
    if (roles.length > 5) throw new Error('Un panneau peut contenir au maximum 5 rôles dans cette version.');

    const embed = new EmbedBuilder()
      .setColor(0x7c5cff)
      .setTitle(parsed.title)
      .setDescription(parsed.description)
      .setFooter({ text: 'Clique sur un bouton pour ajouter ou retirer un rôle.' });

    await channel.send({ embeds: [embed], components: [makeRolePanelRow(roles)] });
    addAuditLog({
      guildId,
      actorId: req.session.discord!.user.id,
      actorName: req.session.discord!.user.globalName ?? req.session.discord!.user.username,
      action: 'Panneau de rôles publié',
      details: `${roles.length} rôle(s) dans #${channel.name}`
    });
    flash(req, 'success', `Panneau de rôles publié dans #${channel.name}.`);
    res.redirect(`${env.BASE_PATH}/dashboard?guild=${guildId}#roles`);
  } catch (error) {
    next(error);
  }
});
