const path = require("path");
const fs = require("fs");
const { spawnSync } = require("child_process");
const { db } = require("./db");
const placeholders = require("./placeholders");
const { createLogger } = require("./logger");

const pluginLifecycleLog = createLogger("core:plugins", { category: "lifecycle" });

const pluginsDir = path.join(__dirname, "..", "..", "plugins");
const cleanupHandlers = [];
const legacyEconomyStem = ["echo", "nomy"].join("");
const economyPluginAliases = {
  "economy-framework": [`${legacyEconomyStem}-framework`],
  "economy-games": [`${legacyEconomyStem}-games`]
};
const economyCommandAliases = [
  { from: `${legacyEconomyStem}:root`, to: "economy:root" },
  { from: `${legacyEconomyStem}-games:hotpotato`, to: "economy-games:hotpotato" },
  { from: `${legacyEconomyStem}-games:coinflip`, to: "economy-games:coinflip" },
  { from: `${legacyEconomyStem}-games:mystery`, to: "economy-games:mystery" }
];
const pluginCanonicalAliases = Object.fromEntries(
  Object.entries(economyPluginAliases).flatMap(([canonicalId, legacyIds]) =>
    legacyIds.map((legacyId) => [legacyId, canonicalId])
  )
);

function canonicalPluginId(id) {
  const raw = String(id || "").trim();
  return pluginCanonicalAliases[raw] || raw;
}

function pluginLegacyIds(id) {
  return economyPluginAliases[canonicalPluginId(id)] || [];
}

function readJson(filePath) {
  const raw = fs.readFileSync(filePath, "utf8");
  return JSON.parse(raw);
}

function scanPluginDirectories() {
  if (!fs.existsSync(pluginsDir)) {
    return [];
  }
  const entries = fs.readdirSync(pluginsDir, { withFileTypes: true });
  const plugins = new Map();
  for (const entry of entries) {
    if (!entry.isDirectory()) {
      continue;
    }
    if (/^lumi_ai_.+/i.test(entry.name) && fs.existsSync(path.join(pluginsDir, entry.name, "tool_info.json"))) {
      continue;
    }
    const manifestPath = path.join(pluginsDir, entry.name, "plugin.json");
    if (!fs.existsSync(manifestPath)) {
      continue;
    }
    try {
      const manifest = readJson(manifestPath);
      const id = canonicalPluginId(manifest.id);
      const plugin = {
        id,
        manifestId: manifest.id,
        name: manifest.name || manifest.id,
        version: manifest.version || "0.0.0",
        description: manifest.description || "",
        main: manifest.main || "index.js",
        dir: path.join(pluginsDir, entry.name),
        legacyIds: pluginLegacyIds(manifest.id)
      };
      const existing = plugins.get(id);
      if (!existing || isCanonicalPluginDirectory(plugin, entry.name, existing)) {
        plugins.set(id, plugin);
      }
    } catch {
      continue;
    }
  }
  return Array.from(plugins.values());
}

function isCanonicalPluginDirectory(plugin, directoryName, existing) {
  if (directoryName === plugin.id || plugin.manifestId === plugin.id) {
    return true;
  }
  return existing.manifestId !== existing.id && existing.dir !== path.join(pluginsDir, existing.id);
}

function syncPluginRegistry() {
  const now = Date.now();
  const plugins = scanPluginDirectories();
  const insert = db.prepare(
    "INSERT INTO plugins (id, name, version, enabled, source, path, installed_at, updated_at) " +
      "VALUES (?, ?, ?, 1, ?, ?, ?, ?) " +
      "ON CONFLICT(id) DO UPDATE SET name = excluded.name, version = excluded.version, path = excluded.path, updated_at = excluded.updated_at"
  );

  for (const plugin of plugins) {
    migratePluginAliases(plugin, now);
    insert.run(
      plugin.id,
      plugin.name,
      plugin.version,
      "local",
      plugin.dir,
      now,
      now
    );
  }
  migrateCommandUsageAliases();
  return plugins;
}

function migratePluginAliases(plugin, now) {
  for (const legacyId of plugin.legacyIds || []) {
    const existing = db.prepare("SELECT * FROM plugins WHERE id = ?").get(plugin.id);
    const legacy = db.prepare("SELECT * FROM plugins WHERE id = ?").get(legacyId);
    if (legacy && !existing) {
      db.prepare(
        "INSERT INTO plugins (id, name, version, enabled, source, path, installed_at, updated_at) " +
          "VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
      ).run(
        plugin.id,
        plugin.name,
        plugin.version,
        legacy.enabled,
        legacy.source || "local",
        plugin.dir,
        legacy.installed_at || now,
        now
      );
    } else if (legacy && existing) {
      db.prepare("UPDATE plugins SET enabled = ?, updated_at = ? WHERE id = ?").run(
        legacy.enabled,
        now,
        plugin.id
      );
    }

    const settingsRows = db
      .prepare("SELECT key, value, updated_at FROM plugin_settings WHERE plugin_id = ?")
      .all(legacyId);
    const copySetting = db.prepare(
      "INSERT OR IGNORE INTO plugin_settings (plugin_id, key, value, updated_at) VALUES (?, ?, ?, ?)"
    );
    for (const row of settingsRows) {
      copySetting.run(plugin.id, row.key, row.value, row.updated_at || now);
    }
    db.prepare("DELETE FROM plugin_settings WHERE plugin_id = ?").run(legacyId);
    db.prepare("DELETE FROM plugins WHERE id = ?").run(legacyId);
  }
}

function migrateCommandUsageAliases() {
  const table = db
    .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'command_usage'")
    .get();
  if (!table) {
    return;
  }
  for (const alias of economyCommandAliases) {
    const oldRow = db.prepare("SELECT count, updated_at FROM command_usage WHERE command_id = ?").get(alias.from);
    if (!oldRow) {
      continue;
    }
    db.prepare(
      "INSERT INTO command_usage (command_id, count, updated_at) VALUES (?, ?, ?) " +
        "ON CONFLICT(command_id) DO UPDATE SET count = command_usage.count + excluded.count, updated_at = MAX(command_usage.updated_at, excluded.updated_at)"
    ).run(alias.to, oldRow.count || 0, oldRow.updated_at || Date.now());
    db.prepare("DELETE FROM command_usage WHERE command_id = ?").run(alias.from);
  }
}

function getPlugins() {
  return db.prepare("SELECT * FROM plugins ORDER BY name").all();
}

function setPluginEnabled(id, enabled) {
  db.prepare("UPDATE plugins SET enabled = ?, updated_at = ? WHERE id = ?").run(
    enabled ? 1 : 0,
    Date.now(),
    id
  );
  pluginLifecycleLog.info(`Plugin ${enabled ? "enabled" : "disabled"}`, { plugin_id: id }, {
    event: enabled ? "plugin_enabled" : "plugin_disabled"
  });
}

function removePlugin(id) {
  db.prepare("DELETE FROM plugins WHERE id = ?").run(id);
  db.prepare("DELETE FROM plugin_settings WHERE plugin_id = ?").run(id);
  pluginLifecycleLog.warn("Plugin registry entry removed", { plugin_id: id }, { event: "plugin_removed" });
}

function clearPluginCache(pluginPath) {
  for (const key of Object.keys(require.cache)) {
    if (key.startsWith(pluginPath)) {
      delete require.cache[key];
    }
  }
}

function loadEnabled({
  app,
  discordClient,
  twitchClient,
  youtubeClient,
  settings,
  web,
  webhooks,
  commandRouter
}) {
  const installed = scanPluginDirectories();
  syncPluginRegistry();
  const enabled = new Set(
    db
      .prepare("SELECT id FROM plugins WHERE enabled = 1")
      .all()
      .map((row) => row.id)
  );

  for (const plugin of installed) {
    if (!enabled.has(plugin.id)) {
      continue;
    }
    const mainPath = path.join(plugin.dir, plugin.main);
    if (!fs.existsSync(mainPath)) {
      continue;
    }
    clearPluginCache(plugin.dir);
    const pluginLog = createLogger(`plugin:${plugin.id}`, { category: "plugin" });
    try {
      pluginLog.run({ event: "plugin_load" }, () => {
        const mod = require(mainPath);
        if (mod && typeof mod.init === "function") {
          const cleanup = mod.init({
            app,
            discordClient,
            twitchClient,
            youtubeClient,
            settings,
            web,
            webhooks,
            db,
            plugin,
            commandRouter,
            placeholders,
            events: global.lumiFrameworks?.events || null,
            logger: pluginLog
          });
          if (typeof cleanup === "function") {
            cleanupHandlers.push({ id: plugin.id, cleanup });
          } else if (cleanup && typeof cleanup.stop === "function") {
            cleanupHandlers.push({ id: plugin.id, cleanup: () => cleanup.stop() });
          }
        }
      });
      pluginLog.info("Plugin loaded", { version: plugin.version }, { event: "plugin_loaded" });
    } catch (error) {
      pluginLog.error("Plugin failed to load", error, { event: "plugin_load_failed" });
    }
  }
}

async function stopPlugins() {
  const handlers = cleanupHandlers.splice(0).reverse();
  for (const handler of handlers) {
    try {
      await handler.cleanup();
      pluginLifecycleLog.info("Plugin stopped", { plugin_id: handler.id }, { event: "plugin_stopped" });
    } catch (error) {
      createLogger(`plugin:${handler.id}`, { category: "lifecycle" })
        .error("Plugin failed to stop", error, { event: "plugin_stop_failed" });
    }
  }
}

function installFromGit(url, targetFolder) {
  if (!fs.existsSync(pluginsDir)) {
    fs.mkdirSync(pluginsDir, { recursive: true });
  }
  const folderName =
    targetFolder ||
    url
      .split("/")
      .pop()
      .replace(/\.git$/i, "")
      .replace(/[^a-zA-Z0-9-_]/g, "");
  const targetPath = path.join(pluginsDir, folderName);
  if (fs.existsSync(targetPath)) {
    throw new Error("Plugin folder already exists.");
  }
  const result = spawnSync("git", ["clone", url, targetPath], {
    stdio: "pipe",
    encoding: "utf8"
  });
  if (result.status !== 0) {
    throw new Error(result.stderr || "Git clone failed.");
  }
  pluginLifecycleLog.info("Plugin cloned from repository", { plugin_id: folderName }, { event: "plugin_installed" });
  return targetPath;
}

function updatePluginFromGit(pluginPath) {
  const result = spawnSync("git", ["-C", pluginPath, "pull"], {
    stdio: "pipe",
    encoding: "utf8"
  });
  if (result.status !== 0) {
    throw new Error(result.stderr || "Git pull failed.");
  }
  pluginLifecycleLog.info("Plugin repository updated", { plugin_id: path.basename(pluginPath) }, { event: "plugin_updated" });
  return result.stdout;
}

function createLocalPlugin({ id, name, description }) {
  const safeId = id.replace(/[^a-zA-Z0-9-_]/g, "");
  if (!safeId) {
    throw new Error("Invalid plugin id.");
  }
  const pluginDir = path.join(pluginsDir, safeId);
  if (fs.existsSync(pluginDir)) {
    throw new Error("Plugin already exists.");
  }
  fs.mkdirSync(pluginDir, { recursive: true });
  const safeName = name || safeId;
  const manifest = {
    id: safeId,
    name: safeName,
    version: "0.1.0",
    description: description || "",
    main: "index.js"
  };
  const manifestPath = path.join(pluginDir, "plugin.json");
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
  const mainPath = path.join(pluginDir, "index.js");
  const escapedName = safeName.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
  const starter = `module.exports = {
  id: "${safeId}",
  init({ web, commandRouter }) {
    const router = web.createRouter();
    router.get("/", (req, res) => {
      res.render("plugin-page", {
        title: "${escapedName}",
        content: "Edit this plugin to add features."
      });
    });
    web.mount("/plugins/${safeId}", router, {
      label: "${escapedName}",
      role: "admin"
    });
  }
};\n`;
  fs.writeFileSync(mainPath, starter, "utf8");
  pluginLifecycleLog.info("Local plugin created", { plugin_id: safeId }, { event: "plugin_created" });
  return pluginDir;
}

module.exports = {
  pluginsDir,
  scanPluginDirectories,
  syncPluginRegistry,
  getPlugins,
  setPluginEnabled,
  removePlugin,
  loadEnabled,
  stopPlugins,
  installFromGit,
  updatePluginFromGit,
  createLocalPlugin,
  canonicalPluginId,
  pluginLegacyIds
};
