const assert = require("assert");
const fs = require("fs");
const os = require("os");
const path = require("path");
const { ToolRegistry } = require("../backend/tool_router");
const { registerManagedTool } = require("../backend/tool_registry");
const { ToolRepoClient, CACHE_TTL_MS } = require("../backend/tool_repo_client");
const { ToolInstaller, validateToolDirectory } = require("../backend/tool_installer");
const { ToolLoader } = require("../backend/tool_loader");
const { ToolManager } = require("../backend/tool_manager");
const { ToolSettings } = require("../backend/tool_settings");
const { isDestructivePath, issueConfirmation, consumeConfirmation } = require("../../../src/services/destructive-confirm");

async function run() {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-ai-tools-"));
  const pluginsDir = path.join(root, "plugins");
  const stagingRoot = path.join(root, "manager-data", "staging");
  const stateFile = path.join(root, "manager-data", "enabled.json");
  const remoteDir = path.join(root, "remote");
  fs.mkdirSync(pluginsDir, { recursive: true });
  fs.mkdirSync(remoteDir, { recursive: true });

  const remoteTools = new Map();
  createTool(path.join(remoteDir, "lumi_ai_weather"), metadata("lumi_ai_weather", "1.0.0"), backendSource());
  remoteTools.set("lumi_ai_weather", metadata("lumi_ai_weather", "1.0.0"));
  const repoClient = {
    calls: 0,
    fail: false,
    async discover() {
      return {
        repository: "https://git.example/owner/lumi",
        branch: "main",
        checked_at: new Date().toISOString(),
        cached: false,
        stale: false,
        tools: [...remoteTools.values()]
      };
    },
    async downloadTool(toolId, destination) {
      this.calls += 1;
      if (this.fail) {
        fs.mkdirSync(destination, { recursive: true });
        fs.writeFileSync(path.join(destination, "tool_info.json"), "{}");
        return;
      }
      copyDirectory(path.join(remoteDir, toolId), destination);
    },
    async readReadme() {
      return "# Weather\nRemote documentation.";
    }
  };

  const registry = new ToolRegistry(() => {});
  const installer = new ToolInstaller({ repoClient, pluginsDir, stagingRoot });
  const settings = { getSetting: (key, fallback) => key === "platform_discord_enabled" ? true : fallback };
  const loader = new ToolLoader({
    registry,
    installer,
    settings,
    stateFile,
    lumiAiVersion: "0.8.0",
    lumiVersion: "0.1.0"
  });
  const toolSettings = new ToolSettings({ installer });
  const manager = new ToolManager({ repoClient, installer, loader, settings: toolSettings });

  let listing = await manager.list();
  assert.equal(listing.tools.length, 1);
  assert.equal(listing.tools[0].installed, false);
  assert.equal(listing.tools[0].update_enabled, false);

  const enabled = await manager.enable("lumi_ai_weather");
  assert.equal(enabled.loaded, true);
  assert.equal(registry.tools.has("lumi_ai_weather.lookup"), true);
  listing = await manager.list();
  assert.equal(listing.tools[0].installed, true);
  assert.equal(listing.tools[0].enabled, true);

  await manager.disable("lumi_ai_weather");
  assert.equal(registry.tools.has("lumi_ai_weather.lookup"), false);
  assert.equal(fs.existsSync(path.join(pluginsDir, "lumi_ai_weather")), true);
  listing = await manager.list();
  assert.equal(listing.tools[0].enabled, false);

  fs.mkdirSync(path.join(pluginsDir, "lumi_ai_weather", "data"), { recursive: true });
  fs.writeFileSync(path.join(pluginsDir, "lumi_ai_weather", "data", "local.json"), "preserve");
  createTool(path.join(remoteDir, "lumi_ai_weather"), metadata("lumi_ai_weather", "1.1.0"), backendSource("updated"));
  remoteTools.set("lumi_ai_weather", metadata("lumi_ai_weather", "1.1.0"));
  listing = await manager.list();
  assert.equal(listing.tools[0].update_available, true);
  await manager.update("lumi_ai_weather");
  assert.equal(validateToolDirectory(path.join(pluginsDir, "lumi_ai_weather")).version, "1.1.0");
  assert.equal(fs.readFileSync(path.join(pluginsDir, "lumi_ai_weather", "data", "local.json"), "utf8"), "preserve");

  const installedWeather = path.join(pluginsDir, "lumi_ai_weather");
  createTool(path.join(remoteDir, "lumi_ai_weather"), metadata("lumi_ai_weather", "1.2.0"), backendSource("updated again"));
  remoteTools.set("lumi_ai_weather", metadata("lumi_ai_weather", "1.2.0"));
  const originalRename = fs.renameSync;
  fs.renameSync = (source, destination) => {
    if (source === installedWeather && path.basename(destination).startsWith(".lumi_ai_weather.backup-")) {
      const error = new Error("simulated locked tool directory");
      error.code = "EPERM";
      throw error;
    }
    return originalRename(source, destination);
  };
  try {
    await assert.rejects(() => manager.update("lumi_ai_weather"), /locked tool directory/);
  } finally {
    fs.renameSync = originalRename;
  }
  assert.equal(validateToolDirectory(installedWeather).version, "1.1.0");
  assert.equal(fs.readFileSync(path.join(installedWeather, "data", "local.json"), "utf8"), "preserve");

  await manager.enable("lumi_ai_weather");
  createTool(
    path.join(remoteDir, "lumi_ai_weather"),
    metadata("lumi_ai_weather", "1.2.0"),
    'module.exports.register = () => { throw new Error("simulated activation failure"); };\n'
  );
  await assert.rejects(() => manager.update("lumi_ai_weather"), /simulated activation failure/);
  assert.equal(validateToolDirectory(installedWeather).version, "1.1.0");
  assert.equal(loader.isEnabled("lumi_ai_weather"), true);
  assert.equal(registry.tools.has("lumi_ai_weather.lookup"), true);

  repoClient.fail = true;
  await assert.rejects(() => manager.update("lumi_ai_weather"), /required|missing/i);
  assert.equal(validateToolDirectory(path.join(pluginsDir, "lumi_ai_weather")).version, "1.1.0");
  repoClient.fail = false;

  createTool(path.join(pluginsDir, "lumi_ai_local"), metadata("lumi_ai_local", "2.0.0"), backendSource());
  listing = await manager.list();
  const localOnly = listing.tools.find((tool) => tool.tool_id === "lumi_ai_local");
  assert.equal(localOnly.remote_missing, true);
  assert.equal(localOnly.update_enabled, false);

  createTool(
    path.join(pluginsDir, "lumi_ai_optional"),
    { ...metadata("lumi_ai_optional", "1.0.0"), dependencies: ["module-that-does-not-exist"] },
    backendSource("ok", "lumi_ai_optional")
  );
  const optionalResult = await loader.enable("lumi_ai_optional");
  assert.equal(optionalResult.loaded, true);
  assert(optionalResult.dependencies.optional.some((entry) => entry.includes("module-that-does-not-exist")));
  assert.match(loader.status("lumi_ai_optional").message, /limitations/);

  createTool(
    path.join(pluginsDir, "lumi_ai_configurable"),
    {
      ...metadata("lumi_ai_configurable", "1.0.0"),
      settings_schema: {
        enabled: { type: "boolean", default: false, label: "Enabled" },
        limit: { type: "integer", default: 5, minimum: 1, maximum: 10, label: "Limit" },
        api_key: { type: "string", default: "", secret: true, label: "API key" }
      }
    },
    backendSource("ok", "lumi_ai_configurable")
  );
  listing = await manager.list();
  assert.equal(listing.tools.find((tool) => tool.tool_id === "lumi_ai_configurable").has_settings, true);
  assert.deepEqual(manager.settingsFor("lumi_ai_configurable").values, { enabled: false, limit: 5, api_key: "" });
  let savedSettings = await manager.saveSettings("lumi_ai_configurable", {
    enabled: true,
    limit: 99,
    api_key: "secret-value"
  });
  assert.deepEqual(savedSettings.values, { enabled: true, limit: 10, api_key: "" });
  assert.equal(savedSettings.configured_secrets.api_key, true);
  savedSettings = await manager.saveSettings("lumi_ai_configurable", {
    enabled: false,
    limit: 4,
    api_key: ""
  });
  assert.equal(toolSettings.readRaw("lumi_ai_configurable").api_key, "secret-value");

  createTool(
    path.join(pluginsDir, "lumi_ai_cross_dependency"),
    { ...metadata("lumi_ai_cross_dependency", "1.0.0"), required_plugins: ["lumi_ai_weather"] },
    backendSource("ok", "lumi_ai_cross_dependency")
  );
  const blockedDependency = await loader.enable("lumi_ai_cross_dependency");
  assert.equal(blockedDependency.unavailable, true);
  assert.match(blockedDependency.message, /cannot depend on AI tool plugin/);

  createTool(
    path.join(pluginsDir, "lumi_ai_partial"),
    metadata("lumi_ai_partial", "1.0.0"),
    `module.exports.register = ({ registerTool }) => {
      registerTool(${serializeDefinition("lumi_ai_partial.first")});
      registerTool(${serializeDefinition("outside_namespace.second")});
    };\n`
  );
  const partialResult = await loader.enable("lumi_ai_partial");
  assert.equal(partialResult.unavailable, true);
  assert.equal(registry.tools.has("lumi_ai_partial.first"), false);

  const strictRegistry = new ToolRegistry(() => {});
  registerManagedTool(strictRegistry, {
    ...metadata("lumi_ai_strict", "1.0.0"),
    permissions: { required_role: "admin" }
  }, definition("lumi_ai_strict.lookup", "user"));
  assert.throws(() => strictRegistry.prepare({
    tool: "lumi_ai_strict.lookup",
    args: {},
    user: { id: "ordinary-user" },
    role: "user",
    sessionId: "session"
  }), /Permission denied/);
  assert.equal(strictRegistry.unregisterOwner("lumi_ai_strict"), 1);

  const contextRegistry = new ToolRegistry(() => {});
  registerManagedTool(contextRegistry, metadata("lumi_ai_context", "1.0.0"), {
    ...definition("lumi_ai_context.lookup"),
    schema: {
      query: { type: "string", required: true },
      freshness: { type: "string", required: false }
    },
    workflow_handler: async ({ arguments: args, ctx }) => ({ query: args.query, origin: ctx.origin })
  });
  const preparedContext = contextRegistry.prepare({
    tool: "lumi_ai_context.lookup",
    args: { query: "current information" },
    user: { id: "ordinary-user" },
    role: "user",
    sessionId: "session",
    context: { origin: "discord" }
  });
  const contextResult = await contextRegistry.execute({
    checked: preparedContext.checked,
    user: { id: "ordinary-user" },
    requestId: "request",
    context: preparedContext.context
  });
  assert.deepEqual(contextResult, { query: "current information", origin: "discord" });

  const unrelated = path.join(pluginsDir, "ordinary-plugin");
  fs.mkdirSync(unrelated);
  assert.equal(isDestructivePath("/plugins/lumi_ai/tools/lumi_ai_weather/delete"), true);
  const request = fakeRequest();
  const confirmation = issueConfirmation(request, "/plugins/lumi_ai/tools/lumi_ai_weather/delete");
  assert.equal(consumeConfirmation(request, "/plugins/lumi_ai/tools/lumi_ai_weather/delete", confirmation.token).reason, "too_early");
  await manager.delete("lumi_ai_weather");
  assert.equal(fs.existsSync(path.join(pluginsDir, "lumi_ai_weather")), false);
  assert.equal(fs.existsSync(unrelated), true);

  repoClient.fail = true;
  remoteTools.set("lumi_ai_broken", metadata("lumi_ai_broken", "1.0.0"));
  await assert.rejects(() => manager.enable("lumi_ai_broken"), /required|missing/i);
  assert.equal(fs.existsSync(path.join(pluginsDir, "lumi_ai_broken")), false);

  await verifyRemoteCache(root);

  const settingsTemplate = fs.readFileSync(path.join(__dirname, "..", "views", "settings.ejs"), "utf8");
  const modalTemplate = fs.readFileSync(path.join(__dirname, "..", "views", "tool-modal.ejs"), "utf8");
  const clientScript = fs.readFileSync(path.join(__dirname, "..", "public", "tool-manager.js"), "utf8");
  const pluginLoader = fs.readFileSync(path.join(__dirname, "..", "..", "..", "src", "services", "plugins.js"), "utf8");
  assert(settingsTemplate.indexOf("data-ai-tools-open") < settingsTemplate.indexOf("Improvement Center"));
  assert(modalTemplate.includes("data-ai-tools-list"));
  assert(modalTemplate.includes("data-ai-tool-readme-modal"));
  assert(modalTemplate.includes("data-ai-tool-settings-modal"));
  assert(clientScript.includes('button("Update"'));
  assert(clientScript.includes('button("Settings"'));
  assert(clientScript.includes("update.disabled = !tool.update_enabled"));
  assert(clientScript.includes('form.dataset.confirmMode = "modal"'));
  assert(clientScript.includes("window.LumiConfirm.destructiveFetch"));
  assert(clientScript.includes("/settings/reset"));
  assert.equal(clientScript.includes("window.confirm(`Reset ${activeSettingsTool.display_name"), false);
  assert(pluginLoader.includes('entry.name, "tool_info.json"'));

  fs.rmSync(root, { recursive: true, force: true });
  console.log("Lumi AI tool manager verification passed.");
}

async function verifyRemoteCache(root) {
  let now = Date.now();
  let requests = 0;
  const info = metadata("lumi_ai_remote", "1.2.3");
  const fetch = async (url) => {
    requests += 1;
    const payload = url.includes("/contents/plugins/lumi_ai_remote/tool_info.json")
      ? { type: "file", encoding: "base64", content: Buffer.from(JSON.stringify(info)).toString("base64") }
      : [{ type: "dir", name: "lumi_ai_remote", path: "plugins/lumi_ai_remote" }];
    return { ok: true, status: 200, async json() { return payload; } };
  };
  const client = new ToolRepoClient({
    settings: { getSetting: (key) => key === "git_remote" ? "https://git.example/owner/lumi" : "main" },
    fetch,
    cacheFile: path.join(root, "remote-cache.json"),
    now: () => now
  });
  await client.discover();
  assert.equal(requests, 2);
  await client.discover();
  assert.equal(requests, 2);
  now += CACHE_TTL_MS + 1;
  await client.discover();
  assert.equal(requests, 4);
}

function metadata(toolId, version) {
  return {
    tool_id: toolId,
    display_name: toolId.replaceAll("_", " "),
    version,
    description: "Test tool",
    scope: "assistant",
    permissions: { required_role: "user" },
    capabilities: ["lookup"],
    limitations: ["test only"],
    tool_type: "lookup",
    entrypoints: { backend: "index.js" },
    confirmation_required: false
  };
}

function definition(toolId, requiredRole = "user") {
  return {
    tool_id: toolId,
    display_name: "Lookup",
    description: "Runs a safe lookup.",
    required_role: requiredRole,
    required_permission: `${toolId}.use`,
    audit_category: "lookup",
    confirmation_required: false,
    risk_level: "low",
    schema: {},
    permission_check: () => true,
    workflow_handler: async () => ({ ok: true })
  };
}

function backendSource(label = "ok", owner = "lumi_ai_weather") {
  return `module.exports.register = ({ registerTool }) => {
    registerTool({
      tool_id: "${owner}.lookup",
      display_name: "Weather lookup",
      description: "Returns test weather.",
      required_role: "user",
      required_permission: "weather.lookup",
      audit_category: "lookup",
      confirmation_required: false,
      risk_level: "low",
      schema: {},
      permission_check: () => true,
      workflow_handler: async () => ({ value: ${JSON.stringify(label)} })
    });
  };\n`;
}

function serializeDefinition(toolId) {
  return `{
    tool_id: ${JSON.stringify(toolId)},
    display_name: "Lookup",
    description: "Safe lookup",
    required_role: "user",
    required_permission: "lookup.use",
    audit_category: "lookup",
    confirmation_required: false,
    risk_level: "low",
    schema: {},
    permission_check: () => true,
    workflow_handler: async () => ({ ok: true })
  }`;
}

function createTool(directory, info, source) {
  fs.rmSync(directory, { recursive: true, force: true });
  fs.mkdirSync(directory, { recursive: true });
  fs.writeFileSync(path.join(directory, "tool_info.json"), `${JSON.stringify(info, null, 2)}\n`);
  fs.writeFileSync(path.join(directory, "readme.md"), `# ${info.display_name}\n`);
  fs.writeFileSync(path.join(directory, "index.js"), source);
}

function copyDirectory(source, destination) {
  fs.mkdirSync(destination, { recursive: true });
  for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
    const from = path.join(source, entry.name);
    const to = path.join(destination, entry.name);
    if (entry.isDirectory()) copyDirectory(from, to);
    else fs.copyFileSync(from, to);
  }
}

function fakeRequest() {
  return {
    session: { user: { id: "admin", isAdmin: true } },
    body: {},
    get() { return null; }
  };
}

run().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
