const crypto = require("crypto");
const path = require("path");
const discord = require("discord.js");
const { Permissions } = discord;
const { ensureUserForIdentity } = require("../../src/services/users");
const { log } = require("../../src/services/logger");

const PLUGIN_ID = "welcome_messages";
const CONFIG_KEY = "config";
const ALLOWED_PLACEHOLDERS = new Set([
  "username",
  "displayname",
  "pronoun",
  "guildmembers"
]);
const LISTENER_KEY = Symbol.for("lumi.welcome_messages.guildMemberAdd");

const DEFAULT_WELCOME_MESSAGES = [
  "Welcome {displayname}! Everyone say hi - {pronoun} just became member #{guildmembers}.",
  "A wild {displayname} appeared. Welcome to the server!",
  "Glad you made it, {displayname}. Make yourself at home!",
  "Welcome in, {displayname}! The server now has {guildmembers} members."
];

const DEFAULT_WELCOME_BACK_MESSAGES = [
  "Welcome back, {displayname}! Good to see {pronoun} again.",
  "Look who's back: {displayname}! Hope you've been well.",
  "Welcome back to the server, {displayname}. We kept the lights on for you.",
  "Hey {displayname}, welcome back!"
];

const PRONOUN_SETS = [
  { value: "they/them", label: "they/them", subject: "they" },
  { value: "he/him", label: "he/him", subject: "he" },
  { value: "she/her", label: "she/her", subject: "she" },
  { value: "it/its", label: "it/its", subject: "it" },
  { value: "he/they", label: "he/they", subject: "he" },
  { value: "she/they", label: "she/they", subject: "she" },
  { value: "they/he", label: "they/he", subject: "they" },
  { value: "they/she", label: "they/she", subject: "they" },
  { value: "any/all", label: "any/all", subject: "they" },
  { value: "use name", label: "use name", subject: "they" }
];

module.exports = {
  id: PLUGIN_ID,
  init({ web, db, discordClient, settings }) {
    ensureTables(db);
    ensureConfig(db);
    log("info", "Welcome Messages plugin initialized");

    const router = web.createRouter();

    router.get("/", async (req, res) => {
      const user = req.session.user || null;
      if (!canModerate(user)) {
        return renderDenied(res);
      }
      const config = getConfig(db);
      const diagnostics = await buildDiagnostics(discordClient, config.channelId);
      res.render(path.join(__dirname, "views", "settings.ejs"), {
        title: "Welcome Messages",
        config,
        diagnostics,
        textChannels: getTextChannels(discordClient),
        isAdmin: Boolean(user?.isAdmin),
        canModerate: true,
        allowedPlaceholders: Array.from(ALLOWED_PLACEHOLDERS),
        pronounSets: PRONOUN_SETS,
        previewMessage
      });
    });

    router.post("/settings", async (req, res) => {
      if (!req.session.user?.isAdmin) {
        return renderDenied(res);
      }
      const config = getConfig(db);
      const channelId = (req.body.channel_id || "").trim();
      if (channelId) {
        const validation = await validateTextChannel(discordClient, channelId);
        if (!validation.valid) {
          req.session.flash = { type: "error", message: validation.message };
          return res.redirect(`/plugins/${PLUGIN_ID}`);
        }
      }
      config.enabled = req.body.enabled === "on";
      config.welcomeBackEnabled = req.body.welcome_back_enabled === "on";
      config.channelId = channelId;
      saveConfig(db, config);
      req.session.flash = { type: "success", message: "Welcome settings saved." };
      res.redirect(`/plugins/${PLUGIN_ID}`);
    });

    router.post("/messages/create", (req, res) => {
      if (!canModerate(req.session.user)) {
        return renderDenied(res);
      }
      const pool = normalizePool(req.body.pool);
      const text = (req.body.text || "").trim();
      const error = validateTemplate(text);
      if (error) {
        req.session.flash = { type: "error", message: error };
        return res.redirect(`/plugins/${PLUGIN_ID}`);
      }
      const config = getConfig(db);
      config[pool].push(newMessage(text, req.body.enabled !== "off"));
      saveConfig(db, config);
      req.session.flash = { type: "success", message: "Message added." };
      res.redirect(`/plugins/${PLUGIN_ID}`);
    });

    router.post("/messages/:id/update", (req, res) => {
      if (!canModerate(req.session.user)) {
        return renderDenied(res);
      }
      const pool = normalizePool(req.body.pool);
      const text = (req.body.text || "").trim();
      const error = validateTemplate(text);
      if (error) {
        req.session.flash = { type: "error", message: error };
        return res.redirect(`/plugins/${PLUGIN_ID}`);
      }
      const config = getConfig(db);
      const message = config[pool].find((item) => item.id === req.params.id);
      if (!message) {
        req.session.flash = { type: "error", message: "Message not found." };
        return res.redirect(`/plugins/${PLUGIN_ID}`);
      }
      message.text = text;
      message.enabled = req.body.enabled === "on";
      message.updatedAt = Date.now();
      saveConfig(db, config);
      req.session.flash = { type: "success", message: "Message updated." };
      res.redirect(`/plugins/${PLUGIN_ID}`);
    });

    router.post("/messages/:id/duplicate", (req, res) => {
      if (!canModerate(req.session.user)) {
        return renderDenied(res);
      }
      const pool = normalizePool(req.body.pool);
      const config = getConfig(db);
      const source = config[pool].find((item) => item.id === req.params.id);
      if (!source) {
        req.session.flash = { type: "error", message: "Message not found." };
        return res.redirect(`/plugins/${PLUGIN_ID}`);
      }
      config[pool].push(newMessage(source.text, source.enabled));
      saveConfig(db, config);
      req.session.flash = { type: "success", message: "Message duplicated." };
      res.redirect(`/plugins/${PLUGIN_ID}`);
    });

    router.post("/messages/:id/archive", (req, res) => {
      if (!canModerate(req.session.user)) {
        return renderDenied(res);
      }
      const pool = normalizePool(req.body.pool);
      const config = getConfig(db);
      const message = config[pool].find((item) => item.id === req.params.id);
      if (message) {
        message.archived = true;
        message.enabled = false;
        message.updatedAt = Date.now();
        saveConfig(db, config);
      }
      req.session.flash = { type: "success", message: "Message archived." };
      res.redirect(`/plugins/${PLUGIN_ID}`);
    });

    router.post("/messages/:id/restore", (req, res) => {
      if (!canModerate(req.session.user)) {
        return renderDenied(res);
      }
      const pool = normalizePool(req.body.pool);
      const config = getConfig(db);
      const message = config[pool].find((item) => item.id === req.params.id);
      if (message) {
        message.archived = false;
        message.updatedAt = Date.now();
        saveConfig(db, config);
      }
      req.session.flash = { type: "success", message: "Message restored." };
      res.redirect(`/plugins/${PLUGIN_ID}`);
    });

    router.post("/profile/pronouns", (req, res) => {
      const user = req.session.user || null;
      if (!user?.id) {
        return renderDenied(res);
      }
      const pronouns = normalizePronounSet(req.body.pronoun_set);
      savePronouns(db, user.id, pronouns);
      req.session.flash = { type: "success", message: "Pronouns saved." };
      res.redirect("/profile");
    });

    web.mount(`/plugins/${PLUGIN_ID}`, router, {
      label: "Welcome Messages",
      role: "mod",
      section: "plugins"
    });
    ensureSidebarNavItem(settings);

    if (typeof web.addProfileSection === "function") {
      web.addProfileSection({
        id: PLUGIN_ID,
        label: "Pronouns",
        view: path.join(__dirname, "views", "profile-pronouns.ejs"),
        order: 40,
        locals: {
          pronounSets: PRONOUN_SETS,
          getPronouns: (userId) => getStoredPronouns(db, userId)
        }
      });
    }

    attachDiscordListener(discordClient, db);
    logStartupDiagnostics(discordClient);
  }
};

function ensureTables(db) {
  db.exec(`
    CREATE TABLE IF NOT EXISTS welcome_message_pronouns (
      user_id TEXT PRIMARY KEY,
      pronoun_set TEXT NOT NULL,
      subject_pronoun TEXT NOT NULL,
      created_at INTEGER NOT NULL,
      updated_at INTEGER NOT NULL
    );
  `);
}

function ensureConfig(db) {
  const existing = db
    .prepare("SELECT value FROM plugin_settings WHERE plugin_id = ? AND key = ?")
    .get(PLUGIN_ID, CONFIG_KEY);
  if (!existing) {
    saveConfig(db, normalizeConfig({}));
  }
}

function getConfig(db) {
  const row = db
    .prepare("SELECT value FROM plugin_settings WHERE plugin_id = ? AND key = ?")
    .get(PLUGIN_ID, CONFIG_KEY);
  if (!row) {
    return normalizeConfig({});
  }
  try {
    return normalizeConfig(JSON.parse(row.value));
  } catch {
    return normalizeConfig({});
  }
}

function saveConfig(db, config) {
  db.prepare(
    "INSERT INTO plugin_settings (plugin_id, key, value, updated_at) VALUES (?, ?, ?, ?) " +
      "ON CONFLICT(plugin_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at"
  ).run(PLUGIN_ID, CONFIG_KEY, JSON.stringify(normalizeConfig(config)), Date.now());
}

function normalizeConfig(config) {
  return {
    enabled: config?.enabled !== false,
    channelId: (config?.channelId || "").toString().trim(),
    welcomeBackEnabled: Boolean(config?.welcomeBackEnabled),
    welcomeMessages: normalizeMessages(config?.welcomeMessages, DEFAULT_WELCOME_MESSAGES),
    welcomeBackMessages: normalizeMessages(
      config?.welcomeBackMessages,
      DEFAULT_WELCOME_BACK_MESSAGES
    )
  };
}

function normalizeMessages(messages, defaults) {
  const source = Array.isArray(messages) && messages.length
    ? messages
    : defaults.map((text) => ({ text, enabled: true }));
  return source
    .map((item) => {
      const text = typeof item === "string" ? item : item?.text;
      const now = Date.now();
      return {
        id: item?.id || crypto.randomUUID(),
        text: (text || "").toString(),
        enabled: item?.enabled !== false,
        archived: Boolean(item?.archived),
        createdAt: Number(item?.createdAt) || now,
        updatedAt: Number(item?.updatedAt) || now
      };
    })
    .filter((item) => item.text.trim());
}

function newMessage(text, enabled) {
  const now = Date.now();
  return {
    id: crypto.randomUUID(),
    text,
    enabled: Boolean(enabled),
    archived: false,
    createdAt: now,
    updatedAt: now
  };
}

function normalizePool(pool) {
  return pool === "welcomeBackMessages" ? "welcomeBackMessages" : "welcomeMessages";
}

function validateTemplate(text) {
  if (!text) {
    return "Message text is required.";
  }
  if (text.length > 1800) {
    return "Message text is too long.";
  }
  const matches = text.matchAll(/\{([^{}]+)\}/g);
  for (const match of matches) {
    const name = match[1].trim().toLowerCase();
    if (!ALLOWED_PLACEHOLDERS.has(name)) {
      return `Unknown placeholder: {${match[1]}}.`;
    }
  }
  return null;
}

function canModerate(user) {
  return Boolean(user?.isAdmin || user?.isMod);
}

function renderDenied(res) {
  return res.status(403).render("error", {
    title: "Access denied",
    message: "You do not have access to that page."
  });
}

function ensureSidebarNavItem(settings) {
  if (!settings?.getSetting || !settings?.setSetting) {
    return;
  }
  const navId = "plugins_welcome_messages";
  const raw = settings.getSetting("nav_structure", null);
  if (!raw) {
    return;
  }
  let structure = raw;
  if (typeof structure === "string") {
    try {
      structure = JSON.parse(structure);
    } catch {
      return;
    }
  }
  if (!structure?.enabled || !Array.isArray(structure.sections)) {
    return;
  }
  if (structure.sections.some((section) => Array.isArray(section.items) && section.items.includes(navId))) {
    return;
  }
  let pluginsSection = structure.sections.find((section) => section.id === "plugins");
  if (!pluginsSection) {
    pluginsSection = {
      id: "plugins",
      label: "Plugins",
      icon: "blocks",
      items: []
    };
    structure.sections.push(pluginsSection);
  }
  pluginsSection.items = Array.isArray(pluginsSection.items)
    ? pluginsSection.items.concat(navId)
    : [navId];
  settings.setSetting("nav_structure", structure);
}

function attachDiscordListener(discordClient, db) {
  if (!discordClient || discordClient[LISTENER_KEY]) {
    if (!discordClient) {
      log("warn", "Welcome Messages listener not attached", {
        reason: "Discord client is unavailable"
      });
    }
    return;
  }
  discordClient[LISTENER_KEY] = true;
  discordClient.on("guildMemberAdd", (member) => {
    handleMemberJoin(member, db).catch((error) => {
      log("error", "Welcome Messages join handler failed", error);
    });
  });
  log("info", "Welcome Messages listener attached", {
    event: "guildMemberAdd",
    memberIntent: detectGuildMembersIntent(discordClient)
  });
}

async function handleMemberJoin(member, db) {
  const config = getConfig(db);
  if (!config.enabled || !config.channelId) {
    log("debug", "Welcome Messages skipped join", {
      reason: !config.enabled ? "Plugin posting disabled" : "No welcome channel configured",
      guildId: member?.guild?.id || null,
      userId: member?.user?.id || member?.id || null
    });
    return;
  }
  const discordId = member?.user?.id || member?.id;
  if (!discordId) {
    log("warn", "Welcome Messages skipped join", {
      reason: "Missing Discord user id",
      guildId: member?.guild?.id || null
    });
    return;
  }
  const existingIdentity = db
    .prepare(
      "SELECT user_id FROM user_identities WHERE provider = 'discord' AND provider_user_id = ?"
    )
    .get(discordId);
  const returning = Boolean(existingIdentity?.user_id);
  const profile = ensureDiscordIdentity(member);
  const channelResult = await validateTextChannel(member.client, config.channelId);
  if (!channelResult.valid || !channelResult.channel) {
    log("warn", "Welcome Messages skipped join", {
      reason: channelResult.message,
      guildId: member?.guild?.id || null,
      userId: discordId,
      channelId: config.channelId
    });
    return;
  }
  const pool =
    returning && config.welcomeBackEnabled
      ? config.welcomeBackMessages
      : config.welcomeMessages;
  const message = chooseMessage(pool) || chooseMessage(config.welcomeMessages);
  if (!message) {
    log("warn", "Welcome Messages skipped join", {
      reason: "No enabled message templates",
      guildId: member?.guild?.id || null,
      userId: discordId
    });
    return;
  }
  const pronoun = getSubjectPronoun(db, profile?.id);
  const rendered = renderTemplate(message.text, member, pronoun);
  try {
    await channelResult.channel.send({
      content: rendered,
      allowedMentions: { parse: [] }
    });
    log("info", "Welcome Messages sent welcome message", {
      guildId: member?.guild?.id || null,
      userId: discordId,
      channelId: config.channelId,
      returning,
      pool: returning && config.welcomeBackEnabled ? "welcomeBackMessages" : "welcomeMessages",
      messageId: message.id
    });
  } catch (error) {
    log("error", "Welcome Messages failed to send message", {
      guildId: member?.guild?.id || null,
      userId: discordId,
      channelId: config.channelId,
      error: error?.stack || error?.message || String(error)
    });
  }
}

function ensureDiscordIdentity(member) {
  const user = member.user || {};
  const displayName =
    member.displayName || user.globalName || user.username || user.tag || "Discord User";
  const avatar =
    typeof user.displayAvatarURL === "function"
      ? user.displayAvatarURL({ format: "png", size: 128 })
      : null;
  return ensureUserForIdentity({
    provider: "discord",
    providerUserId: user.id || member.id,
    displayName,
    avatar,
    fallbackName: user.username || user.tag || "discord-user"
  });
}

function chooseMessage(messages) {
  const active = (messages || []).filter((item) => item.enabled && !item.archived);
  if (!active.length) {
    return null;
  }
  return active[Math.floor(Math.random() * active.length)];
}

function renderTemplate(template, member, pronoun) {
  const user = member.user || {};
  const username = sanitizeMentionText(user.username || user.tag || "Discord User");
  const displayname = sanitizeMentionText(
    member.displayName || user.globalName || user.username || user.tag || "Discord User"
  );
  const guildmembers =
    member.guild?.memberCount || member.guild?.members?.cache?.size || "unknown";
  const values = { username, displayname, pronoun, guildmembers: String(guildmembers) };
  return template.replace(/\{([^{}]+)\}/g, (full, key) => {
    const normalized = key.trim().toLowerCase();
    return Object.prototype.hasOwnProperty.call(values, normalized)
      ? values[normalized]
      : full;
  });
}

function previewMessage(text) {
  return (text || "").replace(/\{([^{}]+)\}/g, (full, key) => {
    const values = {
      username: "lumi_user",
      displayname: "Lumi User",
      pronoun: "they",
      guildmembers: "123"
    };
    return values[key.trim().toLowerCase()] || full;
  });
}

function sanitizeMentionText(value) {
  return (value || "")
    .toString()
    .replace(/@everyone/gi, "@\u200beveryone")
    .replace(/@here/gi, "@\u200bhere")
    .replace(/<@&?\d+>/g, "[mention]")
    .replace(/<#\d+>/g, "[channel]");
}

function getTextChannels(discordClient) {
  const channels = [];
  if (!discordClient?.guilds?.cache) {
    return channels;
  }
  for (const guild of discordClient.guilds.cache.values()) {
    guild.channels?.cache?.forEach((channel) => {
      if (isRegularTextChannel(channel)) {
        channels.push({
          id: channel.id,
          label: `${guild.name} - ${channel.name}`
        });
      }
    });
  }
  return channels.sort((a, b) => a.label.localeCompare(b.label));
}

async function validateTextChannel(discordClient, channelId) {
  if (!discordClient) {
    return { valid: false, message: "Discord client is unavailable." };
  }
  const channel = await resolveChannel(discordClient, channelId);
  if (!channel) {
    return { valid: false, message: "Configured channel was not found." };
  }
  if (!isRegularTextChannel(channel)) {
    return { valid: false, message: "Welcome channel must be a regular text channel." };
  }
  const botMember = await resolveBotMember(channel.guild, discordClient);
  const permissions = botMember ? channel.permissionsFor(botMember) : null;
  const canView = hasPermission(permissions, "VIEW_CHANNEL");
  const canSend = hasPermission(permissions, "SEND_MESSAGES");
  if (permissions && !canView) {
    return { valid: false, message: "Bot cannot view the configured channel.", channel };
  }
  if (permissions && !canSend) {
    return { valid: false, message: "Bot cannot send messages in the configured channel.", channel };
  }
  return { valid: true, message: "Channel is valid.", channel, canView, canSend };
}

function isRegularTextChannel(channel) {
  if (!channel || channel.isThread?.()) {
    return false;
  }
  const type = channel.type;
  return type === "GUILD_TEXT" || type === 0;
}

async function resolveChannel(discordClient, channelId) {
  if (!channelId) {
    return null;
  }
  const cached = discordClient.channels?.cache?.get(channelId) || null;
  if (cached) {
    return cached;
  }
  if (typeof discordClient.channels?.fetch === "function") {
    return discordClient.channels.fetch(channelId).catch(() => null);
  }
  return null;
}

async function resolveBotMember(guild, discordClient) {
  if (!guild || !discordClient?.user?.id) {
    return null;
  }
  return (
    guild.members?.cache?.get(discordClient.user.id) ||
    (typeof guild.members?.fetch === "function"
      ? await guild.members.fetch(discordClient.user.id).catch(() => null)
      : null)
  );
}

function hasPermission(permissions, flag) {
  if (!permissions) {
    return false;
  }
  const value = Permissions?.FLAGS?.[flag] || flag;
  try {
    return Boolean(permissions.has(value));
  } catch {
    return false;
  }
}

async function buildDiagnostics(discordClient, channelId) {
  const channelValidation = channelId
    ? await validateTextChannel(discordClient, channelId)
    : { valid: false, message: "No welcome channel configured." };
  return {
    clientAvailable: Boolean(discordClient),
    ready: Boolean(discordClient?.readyAt),
    memberIntent: detectGuildMembersIntent(discordClient),
    channel: channelValidation
  };
}

function logStartupDiagnostics(discordClient) {
  const memberIntent = detectGuildMembersIntent(discordClient);
  const details = {
    discordClientAvailable: Boolean(discordClient),
    discordClientReady: Boolean(discordClient?.readyAt),
    memberIntent
  };
  if (memberIntent === "configured") {
    log("info", "Welcome Messages diagnostics passed", details);
    return;
  }
  log("warn", "Welcome Messages diagnostics warning", {
    ...details,
    message:
      "Guild member join events require the runtime Discord client to start with GuildMembers/GUILD_MEMBERS and the Discord Developer Portal Server Members Intent enabled."
  });
}

function detectGuildMembersIntent(discordClient) {
  if (!discordClient) {
    return "unknown";
  }
  const bitfield = resolveIntentBitfield(discordClient.options?.intents);
  const guildMembersFlag = resolveGuildMembersFlag();
  if (bitfield === null || guildMembersFlag === null) {
    return "unknown";
  }
  return (bitfield & guildMembersFlag) !== 0n ? "configured" : "missing";
}

function resolveIntentBitfield(intents) {
  if (intents === undefined || intents === null) {
    return null;
  }
  if (typeof intents === "number" || typeof intents === "bigint") {
    return BigInt(intents);
  }
  if (Array.isArray(intents)) {
    return intents.reduce((acc, value) => {
      const resolved = resolveIntentBitfield(value);
      return resolved === null ? acc : acc | resolved;
    }, 0n);
  }
  if (intents.bitfield !== undefined && intents.bitfield !== null) {
    return resolveIntentBitfield(intents.bitfield);
  }
  return null;
}

function resolveGuildMembersFlag() {
  const value =
    discord.GatewayIntentBits?.GuildMembers ||
    discord.IntentsBitField?.Flags?.GuildMembers ||
    discord.Intents?.FLAGS?.GUILD_MEMBERS;
  return value === undefined || value === null ? null : BigInt(value);
}

function normalizePronounSet(input) {
  const raw = (input || "").toString().trim().toLowerCase();
  const normalized = raw
    .replace(/\s+/g, " ")
    .replace(/\s*\/\s*/g, "/");
  return PRONOUN_SETS.find((item) => item.value === normalized) || PRONOUN_SETS[0];
}

function savePronouns(db, userId, pronouns) {
  const now = Date.now();
  db.prepare(
    "INSERT INTO welcome_message_pronouns (user_id, pronoun_set, subject_pronoun, created_at, updated_at) VALUES (?, ?, ?, ?, ?) " +
      "ON CONFLICT(user_id) DO UPDATE SET pronoun_set = excluded.pronoun_set, subject_pronoun = excluded.subject_pronoun, updated_at = excluded.updated_at"
  ).run(userId, pronouns.value, pronouns.subject, now, now);
}

function getStoredPronouns(db, userId) {
  const row = userId
    ? db.prepare("SELECT pronoun_set, subject_pronoun FROM welcome_message_pronouns WHERE user_id = ?").get(userId)
    : null;
  if (!row) {
    return PRONOUN_SETS[0];
  }
  return normalizePronounSet(row.pronoun_set);
}

function getSubjectPronoun(db, userId) {
  return getStoredPronouns(db, userId).subject || "they";
}
