const fs = require("fs");
const path = require("path");
const { db } = require("./db");
const { getSetting } = require("./settings");
const { getEnabledPlatformIds } = require("./platforms");
const { getPluginLeaderboards } = require("./plugin-stats");
const { getPlugins, pluginsDir } = require("./plugins");

const coreProviders = new Map();
const coreOrder = [];
let coreRegistered = false;

function registerTopProvider(provider) {
  if (!provider || !provider.id) {
    return;
  }
  const id = normalizeProviderId(provider.id);
  if (!id) {
    return;
  }
  const entry = {
    id,
    label: provider.label || id,
    section: provider.section || "Community",
    description: provider.description || "",
    valueLabel: provider.valueLabel || "Total",
    rowType: provider.rowType || "user",
    aliases: Array.isArray(provider.aliases) ? provider.aliases : [],
    order: Number.isFinite(provider.order) ? provider.order : coreOrder.length,
    getRows: typeof provider.getRows === "function" ? provider.getRows : null
  };
  if (!coreProviders.has(id)) {
    coreOrder.push(id);
  }
  coreProviders.set(id, entry);
}

function ensureCoreProviders() {
  if (coreRegistered) {
    return;
  }
  coreRegistered = true;
  registerTopProvider({
    id: "messages",
    label: "Top messages",
    section: "Community Interaction",
    valueLabel: "Messages",
    description: "Users with the most messages.",
    getRows: ({ limit }) => buildStatRows("messages", limit)
  });
  registerTopProvider({
    id: "commands",
    label: "Top commands",
    section: "Community Interaction",
    valueLabel: "Commands",
    description: "Users who ran the most commands.",
    getRows: ({ limit }) => buildStatRows("commands", limit)
  });
  registerTopProvider({
    id: "modage",
    label: "Top mod age",
    section: "Moderation",
    valueLabel: "Time",
    description: "Moderators with the longest mod tenure.",
    getRows: ({ limit }) => buildModAgeRows(limit)
  });
  registerTopProvider({
    id: "coins",
    label: "Top currency",
    section: "Economy",
    valueLabel: getCurrencyLabel(),
    description: "Top balances from the currency framework.",
    getRows: ({ limit }) => buildCurrencyRows(limit)
  });
  registerTopProvider({
    id: "interactors",
    label: "Top interactors",
    section: "Expression Interaction",
    valueLabel: "Interactions",
    description: "Most interaction actions given.",
    getRows: ({ limit }) => buildExpressionRows(limit, "given")
  });
  registerTopProvider({
    id: "interactions_received",
    label: "Top interactions received",
    section: "Expression Interaction",
    valueLabel: "Interactions",
    description: "Most interaction actions received.",
    getRows: ({ limit }) => buildExpressionRows(limit, "received")
  });
  registerTopProvider({
    id: "commands_run",
    label: "Top commands run",
    section: "Commands",
    valueLabel: "Runs",
    rowType: "command",
    description: "Most popular commands across platforms.",
    getRows: ({ limit }) => buildCommandUsageRows(limit)
  });
  registerTopProvider({
    id: "followage",
    label: "Top followage",
    section: "Platforms",
    valueLabel: "Days",
    description: "Longest follower durations.",
    getRows: () => ({
      rows: [],
      emptyMessage: "Followage tracking is not configured yet."
    })
  });
  registerTopProvider({
    id: "watchtime",
    label: "Top watchtime",
    section: "Platforms",
    valueLabel: "Hours",
    description: "Most watch time recorded.",
    getRows: () => ({
      rows: [],
      emptyMessage: "Watchtime tracking is not configured yet."
    })
  });
  registerTopProvider({
    id: "games",
    label: "Top games",
    section: "Games",
    valueLabel: "Mentions",
    rowType: "game",
    description: "Most popular games tracked by plugins.",
    getRows: () => ({
      rows: [],
      emptyMessage: "Game tracking is not configured yet."
    })
  });
}

function getTopProviders({ includePlugins = true, limit = 10 } = {}) {
  ensureCoreProviders();
  const providers = coreOrder.map((id) => coreProviders.get(id)).filter(Boolean);
  if (!includePlugins) {
    return providers;
  }
  const pluginProviders = buildPluginProviders({ limit });
  return mergeProviders(providers, pluginProviders);
}

function getTopBoards({ limit = 10 } = {}) {
  const providers = getTopProviders({ includePlugins: true, limit });
  return providers.map((provider) => {
    if (provider.getRows) {
      const result = provider.getRows({ db, limit, settings: { getSetting } });
      return normalizeProviderResult(provider, result);
    }
    return normalizeProviderResult(provider, {
      rows: provider.rows || [],
      rowType: provider.rowType,
      valueLabel: provider.valueLabel,
      emptyMessage: provider.emptyMessage
    });
  });
}

function getLeaderboardSections({ limit = 10 } = {}) {
  const boards = getTopBoards({ limit });
  const sections = [];
  const sectionMap = new Map();
  boards.forEach((board) => {
    const title = board.section || "Leaderboards";
    if (!sectionMap.has(title)) {
      sectionMap.set(title, { title, boards: [] });
      sections.push(sectionMap.get(title));
    }
    sectionMap.get(title).boards.push(board);
  });
  return sections;
}

function getTopCommandOptions() {
  return getTopProviders({ includePlugins: true }).map((provider) => ({
    id: provider.id,
    label: provider.label,
    description: provider.description || "",
    aliases: provider.aliases || []
  }));
}

function registerTopCommand({ commandRouter, settings }) {
  if (!commandRouter) {
    return;
  }
  ensureCoreProviders();
  const platforms = getEnabledPlatformIds();
  commandRouter.registerCommands("core", [
    {
      id: "top",
      triggers: ["top"],
      platforms,
      handler: (ctx) => handleTopCommand({ ctx, settings })
    },
    {
      id: "clip",
      triggers: ["clip"],
      platforms,
      description: "Create a platform clip and save the current OBS replay buffer.",
      defaultPolicy: {
        requirement: { role: "mod" },
        window: { uses: 1, seconds: 10, scope: "global" }
      },
      handler: handleClipCommand
    },
    {
      id: "raid",
      triggers: ["raid"],
      platforms,
      description: "Start or cancel a raid on the current streaming platform.",
      defaultPolicy: {
        requirement: { role: "streamer" },
        window: { uses: 10, seconds: 600, scope: "global" }
      },
      handler: (ctx) => handleRaidCommand(ctx, settings)
    }
  ]);
}

async function handleClipCommand(ctx) {
  const { createPlatformClip } = require("./command-platform");
  const { overlayConnectorManager } = require("./overlay-connectors");
  const requestedName = String(ctx.argsText || "").trim().slice(0, 80);
  const [platformResult, obsResult] = await Promise.allSettled([
    createPlatformClip(ctx),
    overlayConnectorManager.saveReplay({ name: requestedName })
  ]);
  const parts = [];
  const clip = platformResult.status === "fulfilled" ? platformResult.value : null;
  if (clip?.ok) {
    parts.push(`Platform clip created: ${clip.url}`);
  } else {
    parts.push(`Platform clip: ${cleanCommandError(clip?.message || platformResult.reason?.message)}`);
  }
  if (obsResult.status === "fulfilled") {
    parts.push("OBS replay save requested.");
  } else {
    parts.push(`OBS replay: ${cleanCommandError(obsResult.reason?.message)}`);
  }
  if (requestedName) parts.push(`Label: ${requestedName}.`);
  await ctx.reply(parts.join(" "));
  return true;
}

async function handleRaidCommand(ctx, settings) {
  const { cancelPlatformRaid, startPlatformRaid } = require("./command-platform");
  const target = String(ctx.args?.[0] || "").trim();
  if (!target) {
    const prefix = settings?.getSetting?.("command_prefix", "!") || "!";
    await ctx.reply(`Usage: ${prefix}raid <channel> or ${prefix}raid cancel`);
    return true;
  }
  try {
    if (target.toLowerCase() === "cancel") {
      const result = await cancelPlatformRaid(ctx);
      await ctx.reply(result.ok ? "The pending raid was cancelled." : cleanCommandError(result.message));
      return true;
    }
    const result = await startPlatformRaid(ctx, target);
    await ctx.reply(result.ok
      ? `Raid to ${result.target} started. Twitch will show its normal confirmation countdown.`
      : cleanCommandError(result.message));
    return true;
  } catch (error) {
    await ctx.reply(`Raid failed: ${cleanCommandError(error?.message)}`);
    return true;
  }
}

function cleanCommandError(value) {
  return String(value || "not available").replace(/[\r\n]+/g, " ").slice(0, 140);
}

async function handleTopCommand({ ctx, settings }) {
  const prefix = settings.getSetting("command_prefix", "!");
  const rawId = (ctx.args[0] || "").trim().toLowerCase();
  if (!rawId || ["help", "list"].includes(rawId)) {
    await ctx.reply(buildTopHelp(prefix));
    return true;
  }
  const board = findTopBoard(rawId, 5);
  if (!board) {
    await ctx.reply(buildTopHelp(prefix));
    return true;
  }
  if (!board.rows.length) {
    await ctx.reply(board.emptyMessage || "No data recorded yet.");
    return true;
  }
  const list = board.rows
    .slice(0, 5)
    .map((entry, index) => {
      const label = entry.label || entry.username || entry.name || "Unknown";
      return `${index + 1}) ${label} (${entry.value})`;
    })
    .join(" | ");
  await ctx.reply(`${board.label}: ${list}`);
  return true;
}

function findTopBoard(id, limit) {
  const boards = getTopBoards({ limit });
  const normalized = id.toLowerCase();
  return boards.find((board) => {
    if (board.id === normalized) {
      return true;
    }
    return Array.isArray(board.aliases)
      ? board.aliases.map((alias) => alias.toLowerCase()).includes(normalized)
      : false;
  });
}

function buildTopHelp(prefix) {
  const options = getTopCommandOptions().map((entry) => entry.id);
  if (!options.length) {
    return "No top categories are available yet.";
  }
  return `Usage: ${prefix}top <category>. Available: ${options.join(", ")}`;
}

function mergeProviders(coreList, pluginList) {
  const merged = coreList.slice();
  const indexMap = new Map(merged.map((provider, index) => [provider.id, index]));
  for (const provider of pluginList) {
    const existingIndex = indexMap.get(provider.id);
    if (existingIndex !== undefined) {
      if (provider.override) {
        merged[existingIndex] = provider;
      }
      continue;
    }
    indexMap.set(provider.id, merged.length);
    merged.push(provider);
  }
  return merged;
}

function normalizeProviderResult(provider, result) {
  const rows = normalizeRows(result?.rows || []);
  return {
    id: provider.id,
    label: provider.label,
    section: provider.section,
    rowType: result?.rowType || provider.rowType,
    valueLabel: result?.valueLabel || provider.valueLabel || "Total",
    rows,
    emptyMessage:
      result?.emptyMessage || provider.emptyMessage || "No data recorded yet.",
    aliases: result?.aliases || provider.aliases || []
  };
}

function normalizeRows(rows) {
  return (rows || []).map((row) => {
    const value = row.value ?? row.total ?? row.count ?? 0;
    return {
      username: row.username || row.user || row.label || row.name || null,
      label: row.label || row.username || row.name || null,
      href: row.href || row.url || null,
      value: formatNumber(value),
      rawValue: Number(value) || 0
    };
  });
}

function buildPluginProviders({ limit }) {
  const pluginSections = getPluginLeaderboards(limit);
  const providers = [];
  pluginSections.forEach((section) => {
    const sectionId = slugify(section.id || section.title || "plugin");
    const sectionLabel = section.title || "Plugin";
    (section.boards || []).forEach((board) => {
      const boardKey = slugify(board.id || board.title || "board");
      const topId = board.topId ? normalizeProviderId(board.topId) : "";
      const id = topId || `${sectionId}-${boardKey}`;
      const rows = board.rows || [];
      const aliases = Array.isArray(board.topAliases)
        ? board.topAliases.map(normalizeProviderId).filter(Boolean)
        : Array.isArray(board.aliases)
          ? board.aliases.map(normalizeProviderId).filter(Boolean)
          : [];
      providers.push({
        id,
        label: board.title || boardKey,
        section: sectionLabel,
        description: board.description || "",
        rowType: board.rowType || "user",
        valueLabel: board.valueLabel || "Total",
        rows,
        emptyMessage: board.emptyMessage || section.emptyMessage || "No data yet.",
        aliases,
        override: Boolean(board.topOverride)
      });
    });
  });
  return providers;
}

function slugify(value) {
  const slug = (value || "")
    .toString()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .trim();
  return slug || "command";
}

function normalizeProviderId(value) {
  return (value || "")
    .toString()
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9_-]+/g, "-")
    .replace(/^-+|-+$/g, "");
}

function buildStatRows(column, limit) {
  const rows = db
    .prepare(
      `SELECT user_profiles.internal_username AS username, stats.${column} AS value ` +
        "FROM stats " +
        "JOIN user_profiles ON user_profiles.id = stats.user_id " +
        `ORDER BY stats.${column} DESC LIMIT ?`
    )
    .all(limit);
  return { rows };
}

function buildModAgeRows(limit) {
  if (!tableExists("mod_role_periods")) {
    return { rows: [], emptyMessage: "No moderator history recorded yet." };
  }
  const now = Date.now();
  const rows = db
    .prepare(
      "SELECT mod_role_periods.user_id AS user_id, " +
        "SUM(CASE WHEN mod_role_periods.end_at IS NULL THEN ? - mod_role_periods.start_at ELSE mod_role_periods.end_at - mod_role_periods.start_at END) AS total_ms " +
        "FROM mod_role_periods " +
        "GROUP BY mod_role_periods.user_id " +
        "ORDER BY total_ms DESC LIMIT ?"
    )
    .all(now, limit);
  const mapped = rows
    .map((row) => {
      const profile = db
        .prepare("SELECT internal_username FROM user_profiles WHERE id = ?")
        .get(row.user_id);
      return {
        username: profile?.internal_username || row.user_id,
        value: formatDuration(row.total_ms || 0)
      };
    })
    .filter((row) => row.username);
  return { rows: mapped, valueLabel: "Time" };
}

function buildExpressionRows(limit, type) {
  if (!tableExists("expression_user_stats")) {
    return { rows: [], emptyMessage: "No interactions recorded yet." };
  }
  const column = type === "received" ? "received_count" : "given_count";
  const rows = db
    .prepare(
      `SELECT user_profiles.internal_username AS username, SUM(expression_user_stats.${column}) AS value ` +
        "FROM expression_user_stats " +
        "JOIN user_profiles ON user_profiles.id = expression_user_stats.user_id " +
        "GROUP BY expression_user_stats.user_id " +
        `ORDER BY value DESC LIMIT ?`
    )
    .all(limit);
  return { rows };
}

function buildCommandUsageRows(limit) {
  if (!tableExists("command_usage")) {
    return { rows: [], emptyMessage: "No command usage recorded yet." };
  }
  const commandIndex = buildCommandIndex();
  const prefix = getSetting("command_prefix", "!");
  const rows = db
    .prepare("SELECT command_id, count FROM command_usage ORDER BY count DESC LIMIT ?")
    .all(limit)
    .map((row) => {
      const command = commandIndex.get(row.command_id);
      return {
        label: command?.label || formatCommandId(row.command_id, prefix),
        href: command?.href || null,
        value: row.count
      };
    });
  return { rows, rowType: "command" };
}

function buildCurrencyRows(limit) {
  if (!tableExists("economy_accounts")) {
    return { rows: [], emptyMessage: "Currency framework not active." };
  }
  const rows = db
    .prepare(
      "SELECT user_profiles.internal_username AS username, economy_accounts.balance AS value " +
        "FROM economy_accounts " +
        "JOIN user_profiles ON user_profiles.id = economy_accounts.user_id " +
        "ORDER BY economy_accounts.balance DESC LIMIT ?"
    )
    .all(limit);
  return { rows, valueLabel: getCurrencyLabel() };
}

function buildCommandIndex() {
  const prefix = getSetting("command_prefix", "!");
  const index = new Map();
  const addEntry = (ids, label, commandPageId) => {
    const cleanLabel = normalizeCommandDisplay(label, prefix);
    const href = `/commands#cmd-${slugify(commandPageId)}`;
    ids
      .map((id) => (id || "").toString().trim())
      .filter(Boolean)
      .forEach((id) => {
        if (!index.has(id)) {
          index.set(id, { label: cleanLabel, href });
        }
      });
  };

  addEntry(["top"], `${prefix}top`, "top");
  for (const option of getTopCommandOptions()) {
    const subcommand = normalizeSubcommand(option.id);
    if (subcommand) {
      addEntry([`top:${subcommand}`], `${prefix}top ${subcommand}`, `top:${subcommand}`);
    }
  }

  if (tableExists("custom_commands")) {
    const customRows = db
      .prepare("SELECT trigger FROM custom_commands WHERE enabled = 1 ORDER BY trigger")
      .all();
    for (const row of customRows) {
      const trigger = normalizeCommandTrigger(row.trigger);
      if (trigger) {
        addEntry([`custom:${trigger}`], `${prefix}${trigger}`, `custom:${trigger}`);
      }
    }
  }

  for (const plugin of listCommandPlugins()) {
    const manifestPath = path.join(plugin.path, "cmds.json");
    const manifest = readJsonSafe(manifestPath);
    if (!manifest || !Array.isArray(manifest.commands)) {
      continue;
    }
    const pluginSettings = getPluginSettingsMap(plugin.id);
    for (const command of manifest.commands) {
      if (!command || !command.trigger) {
        continue;
      }
      const override = command.triggerKey ? pluginSettings[command.triggerKey] : "";
      const trigger = normalizeCommandTrigger(override, command.trigger);
      if (!trigger) {
        continue;
      }
      const subcommand = normalizeSubcommand(command.subcommand);
      const commandId = `${plugin.id}:${command.id || trigger}`;
      const ids = [commandId, command.id];
      if (command.id && !command.id.toString().includes(":")) {
        ids.push(`${plugin.id}:${command.id}`);
      }
      if (plugin.id === "economy-framework" && command.id === "root") {
        ids.push("economy:root");
      }
      if (plugin.id === "economy-games" && command.id === "mysterybox") {
        ids.push("economy-games:mystery");
      }
      const label = subcommand ? `${prefix}${trigger} ${subcommand}` : `${prefix}${trigger}`;
      addEntry(ids, label, commandId);
    }
  }

  return index;
}

function listCommandPlugins() {
  try {
    const enabled = getPlugins()
      .filter((plugin) => plugin.enabled)
      .map((plugin) => ({
        id: plugin.id,
        name: plugin.name,
        path: plugin.path
      }))
      .filter((plugin) => plugin.id && plugin.path);
    if (enabled.length) {
      return enabled;
    }
  } catch {
    // Fall back to filesystem manifests below.
  }

  if (!fs.existsSync(pluginsDir)) {
    return [];
  }
  return fs
    .readdirSync(pluginsDir, { withFileTypes: true })
    .filter((entry) => entry.isDirectory())
    .map((entry) => {
      const dir = path.join(pluginsDir, entry.name);
      const manifest = readJsonSafe(path.join(dir, "plugin.json"));
      return manifest?.id ? { id: manifest.id, name: manifest.name, path: dir } : null;
    })
    .filter(Boolean);
}

function readJsonSafe(filePath) {
  try {
    return JSON.parse(fs.readFileSync(filePath, "utf8"));
  } catch {
    return null;
  }
}

function getPluginSettingsMap(pluginId) {
  if (!tableExists("plugin_settings")) {
    return {};
  }
  const rows = db
    .prepare("SELECT key, value FROM plugin_settings WHERE plugin_id = ?")
    .all(pluginId);
  return Object.fromEntries(rows.map((row) => [row.key, row.value]));
}

function normalizeCommandTrigger(value, fallback = "") {
  const raw = (value || fallback || "").toString().trim().toLowerCase();
  const firstToken = raw.replace(/^!+/, "").split(/\s+/)[0] || "";
  return firstToken.replace(/[^a-z0-9_-]/g, "");
}

function normalizeSubcommand(value) {
  const raw = (value || "").toString().trim().toLowerCase();
  const firstToken = raw.replace(/^!+/, "").split(/\s+/)[0] || "";
  return firstToken.replace(/[^a-z0-9_-]/g, "");
}

function normalizeCommandDisplay(value, prefix) {
  const raw = (value || "").toString().trim();
  if (!raw) {
    return `${prefix}unknown`;
  }
  return raw.startsWith(prefix) ? raw : `${prefix}${raw.replace(/^!+/, "")}`;
}

function formatCommandId(commandId, prefix = "!") {
  if (!commandId) {
    return "Unknown";
  }
  if (commandId.startsWith("custom:")) {
    return `${prefix}${commandId.slice(7)}`;
  }
  return commandId;
}

function getCurrencyLabel() {
  const plural = getPluginSetting("economy-framework", "currency_name_plural");
  const singular = getPluginSetting("economy-framework", "currency_name");
  return plural || singular || "Coins";
}

function getPluginSetting(pluginId, key) {
  const row = db
    .prepare("SELECT value FROM plugin_settings WHERE plugin_id = ? AND key = ?")
    .get(pluginId, key);
  return row?.value ? row.value.toString() : null;
}

function tableExists(name) {
  const row = db
    .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
    .get(name);
  return Boolean(row);
}

function formatNumber(value) {
  const number = Number(value);
  if (!Number.isFinite(number)) {
    return value ?? "0";
  }
  return number.toLocaleString("en-US");
}

function formatDuration(totalMs) {
  const totalSeconds = Math.max(0, Math.floor(totalMs / 1000));
  const days = Math.floor(totalSeconds / 86400);
  const hours = Math.floor((totalSeconds % 86400) / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  if (days > 0) {
    return `${days}d ${hours}h`;
  }
  if (hours > 0) {
    return `${hours}h ${minutes}m`;
  }
  return `${minutes}m`;
}

module.exports = {
  registerTopProvider,
  getTopProviders,
  getTopBoards,
  getLeaderboardSections,
  getTopCommandOptions,
  registerTopCommand
};
