const path = require("path");
const { getSetting, setSetting } = require("../../src/services/settings");
const {
  PERMISSION_LEVELS,
  REVIEW_STATES,
  STATUS_VALUES,
  VISIBILITY_VALUES,
  accessForUser,
  createEntry,
  ensureTables,
  getEditableEntry,
  getEntryBySlug,
  grantPermission,
  listEntries,
  listPermissions,
  listVersions,
  revokePermission,
  restoreVersion,
  searchForAi,
  setEntryWorkflow,
  updateEntry
} = require("./backend/okf_store");
const {
  ensureKnowledgeDirs,
  getCommunityKnowledgeFile,
  getVisibleCommunityKnowledge,
  listCommunityKnowledgeFiles,
  listCorrectionKnowledgeFiles,
  listVisibleCommunityKnowledge,
  loadKnowledgeEntries,
  migrateSingleBracePlaceholders,
  registerKnowledgePlaceholderDefinitions,
  saveCommunityKnowledgeFile,
  saveCorrectionKnowledgeFile,
  searchFileKnowledge
} = require("./backend/file_knowledge");
const { generateKnowledgeFiles } = require("./backend/generate_knowledge");
const { renderMarkdown } = require("./backend/markdown");
const placeholderService = require("../../src/services/placeholders");

const PLUGIN_ID = "okf";

module.exports = {
  id: PLUGIN_ID,
  init({ web, db, placeholders = placeholderService }) {
    if (typeof web.auth?.requireAuth !== "function") {
      throw new Error("OKF requires Lumi's shared WebUI authentication helpers.");
    }
    const requireLogin = web.auth.requireAuth;
    ensureTables(db);
    ensureKnowledgeDirs(process.cwd());
    if (!getSetting("okf_single_brace_placeholder_migration_v1", false)) {
      migrateSingleBracePlaceholders(process.cwd());
      setSetting("okf_single_brace_placeholder_migration_v1", true);
    }
    generateKnowledgeFiles(process.cwd());
    registerOkfPlaceholderFields(placeholders);
    registerKnowledgePlaceholderDefinitions(placeholders, { rootDir: process.cwd() });
    const searchOkfForAi = ({ query = "", user, limit = 5, okf_retrieval = "light" } = {}) => {
      if (okf_retrieval === "none" || Number(limit) === 0) return [];
      const normalizedLimit = Math.max(1, Math.min(Number(limit) || 5, 25));
      const candidateLimit = Math.min(25, Math.max(normalizedLimit * 3, 8));
      const fileResults = searchFileKnowledge({ query, user, limit: candidateLimit, rootDir: process.cwd() });
      const dbResults = searchForAi(db, query || "", user, candidateLimit);
      const candidates = [...fileResults, ...dbResults];
      const selected = rankCombinedKnowledge(candidates, normalizedLimit);
      Object.defineProperty(selected, "candidateCount", {
        value: candidates.length,
        enumerable: false
      });
      return selected;
    };
    const okfContextProvider = ({ message, user, limit, okf_retrieval } = {}) => {
      const normalizedLimit = limit ?? 5;
      const query = message || "";
      const results = searchOkfForAi({ query, user, limit: normalizedLimit, okf_retrieval });
      return {
        blocks: formatAiContext(results),
        diagnostics: {
          provider: "okf",
          kind: "okf",
          query,
          okf_retrieval: okf_retrieval || "light",
          limit: normalizedLimit,
          candidate_count: Number(results.candidateCount) || results.length,
          returned_count: results.length,
          reason: okf_retrieval === "none" || Number(normalizedLimit) === 0 ? "disabled_by_controller" : "search_complete"
        }
      };
    };
    if (!global.lumiFrameworks) {
      global.lumiFrameworks = {};
    }
    global.lumiFrameworks.okf = {
      search: searchOkfForAi,
      context: okfContextProvider,
      accessForUser: (user) => accessForUser(db, user),
      saveCorrection(values, actor) {
        if (!actor?.isAdmin) throw new Error("Only admins can create correction knowledge files.");
        return saveCorrectionKnowledgeFile(process.cwd(), values);
      }
    };
    global.lumiFrameworks.ai?.registerContext?.(PLUGIN_ID, okfContextProvider);

    const router = web.createRouter();

    router.use((req, res, next) => {
      res.locals.okfAccess = accessForUser(db, req.session.user);
      next();
    });

    router.get("/", requireLogin, (req, res) => {
      const filters = {
        q: req.query.q || "",
        category: req.query.category || "",
        tag: req.query.tag || ""
      };
      const generalEntries = listEntries(db, filters, req.session.user)
        .map((entry) => ({
          ...entry,
          source_kind: "general",
          href: `/plugins/${PLUGIN_ID}/${encodeURIComponent(entry.slug)}`
        }));
      const communityEntries = listVisibleCommunityKnowledge({
        user: req.session.user,
        filters,
        rootDir: process.cwd()
      });
      const entries = [...generalEntries, ...communityEntries]
        .sort((left, right) => left.title.localeCompare(right.title));
      const filterEntries = [
        ...listEntries(db, {}, req.session.user),
        ...listVisibleCommunityKnowledge({ user: req.session.user, rootDir: process.cwd() })
      ];
      res.render(view("index"), {
        title: "Knowledge",
        entries,
        filters,
        categories: categoriesFor(filterEntries),
        tags: tagsFor(filterEntries)
      });
    });

    router.get("/admin", requireOkfManagement(db), (req, res) => {
      renderAdmin(req, res, db);
    });

    router.post("/admin/preview", requireOkfManagement(db), (req, res) => {
      const markdown = String(req.body?.markdown || "");
      if (markdown.length > 24000) return res.status(400).json({ error: "Markdown preview is limited to 24,000 characters." });
      return res.json({ html: renderMarkdown(markdown) });
    });

    router.post("/admin/community", requireOkfEdit(db), (req, res) => {
      try {
        validateOkfPlaceholderFields({
          body: req.body.body
        }, req.session.user, placeholders);
        const entry = saveCommunityKnowledgeFile(process.cwd(), communityFileValuesForUser(db, req.session.user, req.body));
        registerKnowledgePlaceholderDefinitions(placeholders, { rootDir: process.cwd() });
        req.session.flash = { type: "success", message: "Community OKF file saved." };
        res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=community&community=${encodeURIComponent(entry.slug)}#okf-community-files`);
      } catch (error) {
        req.session.flash = { type: "error", message: error.message };
        res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=community#okf-community-files`);
      }
    });

    router.post("/admin/community/:slug", requireOkfEdit(db), (req, res) => {
      try {
        validateOkfPlaceholderFields({
          body: req.body.body
        }, req.session.user, placeholders);
        const existing = getCommunityKnowledgeFile(process.cwd(), req.params.slug);
        const entry = saveCommunityKnowledgeFile(process.cwd(), communityFileValuesForUser(db, req.session.user, {
          ...req.body,
          existing_slug: req.params.slug
        }, existing));
        registerKnowledgePlaceholderDefinitions(placeholders, { rootDir: process.cwd() });
        req.session.flash = { type: "success", message: "Community OKF file updated." };
        res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=community&community=${encodeURIComponent(entry.slug)}#okf-community-files`);
      } catch (error) {
        req.session.flash = { type: "error", message: error.message };
        res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=community&community=${encodeURIComponent(req.params.slug)}#okf-community-files`);
      }
    });

    router.post("/admin/entries", requireOkfEdit(db), (req, res) => {
      try {
        validateOkfPlaceholderFields(req.body, req.session.user, placeholders);
        const entry = createEntry(db, req.body, req.session.user);
        req.session.flash = { type: "success", message: "OKF entry created." };
        res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general&edit=${encodeURIComponent(entry.slug)}`);
      } catch (error) {
        req.session.flash = { type: "error", message: error.message };
        res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general`);
      }
    });

    router.post("/admin/entries/:slug", requireOkfEdit(db), (req, res) => {
      try {
        validateOkfPlaceholderFields(req.body, req.session.user, placeholders);
        const entry = updateEntry(db, req.params.slug, req.body, req.session.user);
        if (isEnhancedRequest(req)) {
          return res.json({
            ok: true,
            message: "OKF entry updated.",
            fieldErrors: {},
            data: { entry },
            patch: {},
            redirect: null,
            reloadRequired: false
          });
        }
        req.session.flash = { type: "success", message: "OKF entry updated." };
        res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general&edit=${encodeURIComponent(entry.slug)}`);
      } catch (error) {
        if (isEnhancedRequest(req)) {
          return res.status(422).json({
            ok: false,
            message: error.message,
            fieldErrors: {},
            data: {},
            patch: {},
            redirect: null,
            reloadRequired: false
          });
        }
        req.session.flash = { type: "error", message: error.message };
        res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general&edit=${encodeURIComponent(req.params.slug)}`);
      }
    });

    router.post("/admin/entries/:slug/:action", requireOkfManagement(db), (req, res) => {
      try {
        const entry = setEntryWorkflow(db, req.params.slug, req.params.action, req.session.user, req.body.note || "");
        req.session.flash = { type: "success", message: "OKF workflow updated." };
        res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general&edit=${encodeURIComponent(entry.slug)}`);
      } catch (error) {
        req.session.flash = { type: "error", message: error.message };
        res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general&edit=${encodeURIComponent(req.params.slug)}`);
      }
    });

    router.post("/admin/entries/:slug/versions/:version/restore", requireOkfManagement(db), (req, res) => {
      try {
        const selected = getEditableEntry(db, req.params.slug, req.session.user);
        if (!selected) throw new Error("OKF entry was not found.");
        const entry = restoreVersion(
          db,
          selected.id,
          req.params.version,
          req.session.user,
          req.body.note || ""
        );
        req.session.flash = { type: "success", message: "OKF version restored." };
        res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general&edit=${encodeURIComponent(entry.slug)}`);
      } catch (error) {
        req.session.flash = { type: "error", message: error.message };
        res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general&edit=${encodeURIComponent(req.params.slug)}`);
      }
    });

    router.post("/admin/permissions", requireAdmin, (req, res) => {
      try {
        grantPermission(db, req.body, req.session.user);
        req.session.flash = { type: "success", message: "OKF permission granted." };
      } catch (error) {
        req.session.flash = { type: "error", message: error.message };
      }
      res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general#okf-permissions`);
    });

    router.post("/admin/permissions/:id/revoke", requireAdmin, (req, res) => {
      try {
        revokePermission(db, req.params.id, req.session.user);
        req.session.flash = { type: "success", message: "OKF permission revoked." };
      } catch (error) {
        req.session.flash = { type: "error", message: error.message };
      }
      res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general#okf-permissions`);
    });

    router.get("/community/:slug", requireLogin, (req, res) => {
      const entry = getVisibleCommunityKnowledge({
        user: req.session.user,
        slug: req.params.slug,
        rootDir: process.cwd()
      });
      if (!entry) {
        return res.status(404).render("error", {
          title: "Knowledge entry unavailable",
          message: "That community knowledge file was not found or is not visible to your account."
        });
      }
      return res.render(view("entry"), {
        title: entry.title,
        entry,
        renderMarkdown
      });
    });

    router.get("/:slug", requireLogin, async (req, res) => {
      const entry = await resolveOkfEntryPlaceholders(
        getEntryBySlug(db, req.params.slug, req.session.user),
        req.session.user,
        placeholders
      );
      if (!entry) {
        return res.status(404).render("error", {
          title: "Knowledge entry unavailable",
          message: "That OKF entry was not found or is not visible to your account."
        });
      }
      res.render(view("entry"), {
        title: entry.title,
        entry,
        renderMarkdown
      });
    });

    web.mount(`/plugins/${PLUGIN_ID}`, router, {
      label: "Knowledge",
      role: "public",
      authRequired: true,
      section: "community",
      canAccess: (user) => Boolean(user)
    });
    web.addNavItem({
      label: "OKF Management",
      path: `/plugins/${PLUGIN_ID}/admin`,
      role: "public",
      authRequired: true,
      section: "admin",
      canAccess: (user) => user?.isAdmin === true
    });
    web.addNavItem({
      label: "Contribute to OKF",
      path: `/plugins/${PLUGIN_ID}/admin`,
      role: "public",
      authRequired: true,
      section: "community",
      canAccess: (user) => user?.isAdmin !== true && accessForUser(db, user).canEdit
    });
    return () => {
      global.lumiFrameworks?.ai?.unregisterContext?.(PLUGIN_ID);
      if (global.lumiFrameworks?.okf?.context === okfContextProvider) {
        delete global.lumiFrameworks.okf;
      }
    };
  }
};

function isEnhancedRequest(req) {
  return req.get("X-Lumi-Enhanced") === "1" || req.get("X-Requested-With") === "XMLHttpRequest";
}

function formatAiContext(entries) {
  if (!entries.length) return [];
  const prepared = preferSpecificKnowledgeChunks(entries).slice(0, 3);
  const blocks = [];
  let remaining = 4200;
  for (const entry of prepared) {
    const facts = trimForContext(entry.facts, Math.min(1400, remaining));
    const block = [
    `OKF entry: ${entry.title}`,
    `Evidence tier: ${knowledgeEvidenceTier(entry)}`,
    `Source: ${entry.source}`,
    entry.source_metadata ? `Source metadata: ${formatSourceMetadata(entry.source_metadata)}` : "",
    entry.category ? `Category: ${entry.category}` : "",
      entry.summary ? `Summary: ${trimForContext(entry.summary, 360)}` : "",
      facts ? `Facts:\n${facts}` : ""
    ].filter(Boolean).join("\n");
    if (!block || remaining <= 0) continue;
    const trimmedBlock = trimForContext(block, remaining);
    blocks.push(trimmedBlock);
    remaining -= trimmedBlock.length + 2;
  }
  return blocks;
}

function knowledgeEvidenceTier(entry = {}) {
  const source = String(entry.source_metadata?.path || entry.source || "").replace(/\\/g, "/");
  if (source.includes("knowledge/corrections/")) return "admin-approved correction";
  if (source.includes("knowledge/community/")) return "approved community OKF";
  if (source.startsWith("/plugins/okf/")) return "approved database OKF";
  if (source.includes("knowledge/plugins/")) return "generated plugin reference";
  if (source.includes("knowledge/core/")) return "generated core reference";
  return "approved OKF reference";
}

function rankCombinedKnowledge(entries, limit) {
  const ranked = [...entries].sort((left, right) =>
    Number(right.source_metadata?.score || 0) - Number(left.source_metadata?.score || 0) ||
    String(left.source || "").localeCompare(String(right.source || ""))
  );
  const selected = [];
  const exactSources = new Set();
  const perEntry = new Map();
  for (const entry of ranked) {
    const exactKey = `${entry.source || ""}:${entry.source_metadata?.heading || ""}`;
    if (exactSources.has(exactKey)) continue;
    const entryKey = String(entry.id || entry.source || exactKey);
    const usedForEntry = perEntry.get(entryKey) || 0;
    if (usedForEntry >= 2) continue;
    exactSources.add(exactKey);
    perEntry.set(entryKey, usedForEntry + 1);
    selected.push(entry);
    if (selected.length >= limit) break;
  }
  return selected;
}

function preferSpecificKnowledgeChunks(entries) {
  const hasSpecificRoute = entries.some((entry) => {
    const heading = String(entry.source_metadata?.heading || "");
    return /^(GET|POST|PUT|PATCH|DELETE|MOUNT)\s+\//i.test(heading);
  });
  if (!hasSpecificRoute) return entries;
  return entries.filter((entry) => {
    const heading = String(entry.source_metadata?.heading || "");
    return !["Routes", "Web Routes", "Route Reference"].includes(heading);
  });
}

function trimForContext(value, maximum) {
  const text = String(value || "").trim();
  const limit = Math.max(0, Number(maximum) || 0);
  if (!text || !limit) return "";
  if (text.length <= limit) return text;
  return `${text.slice(0, Math.max(0, limit - 24)).trimEnd()}\n[truncated]`;
}

function formatSourceMetadata(metadata = {}) {
  return [
    metadata.path ? `path=${metadata.path}` : "",
    metadata.id ? `id=${metadata.id}` : "",
    metadata.heading ? `heading=${metadata.heading}` : "",
    Number.isFinite(Number(metadata.score)) ? `score=${Math.round(Number(metadata.score))}` : "",
    metadata.excerpt ? `excerpt=${String(metadata.excerpt).slice(0, 240)}` : ""
  ].filter(Boolean).join("; ");
}

function registerOkfPlaceholderFields(placeholders) {
  if (!placeholders?.registerFieldPolicy) return;
  placeholders.registerFieldPolicy({
    field_id: "okf.markdown",
    label: "OKF Markdown",
    field_type: "okf_markdown",
    output_audience: "user",
    min_editor_role: "user",
    allowed_namespaces: [],
    max_sensitivity: "admin",
    description: "Role-gated OKF Markdown content."
  });
}

function validateOkfPlaceholderFields(values, user, placeholders) {
  if (!placeholders?.validateTemplate) return;
  const checks = [
    ["user_markdown", "user"],
    ["moderator_markdown", "mod"],
    ["admin_markdown", "admin"],
    ["ai_facts_markdown", "admin"],
    ["body", "user"]
  ];
  for (const [field, outputAudience] of checks) {
    if (values[field] === undefined) continue;
    const result = placeholders.validateTemplate({
      fieldId: "okf.markdown",
      template: values[field],
      outputAudience,
      user
    });
    if (!result.ok) {
      const tokens = result.errors
        .map((error) => error.token || error.id || error.reason)
        .filter(Boolean)
        .slice(0, 5)
        .join(", ");
      throw new Error(`Unavailable or unauthorized OKF placeholder${tokens ? `: ${tokens}` : "."}`);
    }
  }
}

async function resolveOkfEntryPlaceholders(entry, user, placeholders) {
  if (!entry || !placeholders?.renderTemplate) return entry;
  const resolved = { ...entry };
  const fields = [
    ["user_markdown", "user"],
    ["moderator_markdown", "mod"],
    ["admin_markdown", "admin"],
    ["ai_facts_markdown", "admin"]
  ];
  for (const [field, outputAudience] of fields) {
    if (!resolved[field]) continue;
    const result = await placeholders.renderTemplate({
      fieldId: "okf.markdown",
      template: resolved[field],
      outputAudience,
      user,
      runtimeContext: {
        runtime: true,
        okf: { entry: resolved }
      },
      fallback: "[unavailable]"
    });
    resolved[field] = result.rendered;
  }
  return resolved;
}

function renderAdmin(req, res, db) {
  const access = accessForUser(db, req.session.user);
  const activeTab = resolveAdminTab(req.query, access.canManagePermissions);
  const filters = {
    q: req.query.q || "",
    status: req.query.status || "",
    category: req.query.category || "",
    tag: req.query.tag || ""
  };
  const entries = listEntries(db, filters, req.session.user, { management: true });
  const allEntries = listEntries(db, {}, req.session.user, { management: true });
  const selected = req.query.edit ? getEditableEntry(db, req.query.edit, req.session.user) : null;
  const communityFiles = listCommunityKnowledgeFiles(process.cwd()).filter((entry) => canReadOkfVisibility(access, entry.visibility));
  const requestedCommunity = req.query.community ? getCommunityKnowledgeFile(process.cwd(), req.query.community) : null;
  const selectedCommunity = requestedCommunity && canReadOkfVisibility(access, requestedCommunity.visibility) ? requestedCommunity : null;
  const systemFiles = listSystemKnowledgeFiles(process.cwd()).filter((entry) => canReadOkfVisibility(access, entry.visibility));
  const selectedSystemFile = req.query.system ? findSystemKnowledgeFile(systemFiles, req.query.system) : null;
  const correctionFiles = access.canManagePermissions
    ? listCorrectionKnowledgeFiles(process.cwd()).filter((entry) => canReadOkfVisibility(access, entry.visibility))
    : [];
  const selectedCorrection = req.query.correction ? findSystemKnowledgeFile(correctionFiles, req.query.correction) : null;
  const suggestionEntries = [...allEntries, ...communityFiles];
  const versions = selected ? listVersions(db, selected.id, req.session.user) : [];
  res.render(view("admin"), {
    title: "OKF Management",
    activeTab,
    entries,
    filters,
    selected,
    communityFiles,
    selectedCommunity,
    systemFiles,
    selectedSystemFile,
    correctionFiles,
    selectedCorrection,
    versions,
    permissions: listPermissions(db, req.session.user),
    levels: PERMISSION_LEVELS,
    statuses: STATUS_VALUES,
    visibilityValues: VISIBILITY_VALUES.filter((value) => canReadOkfVisibility(access, value)),
    reviewStates: REVIEW_STATES,
    categories: categoriesFor(suggestionEntries),
    tags: tagsFor(suggestionEntries),
    renderMarkdown
  });
}

function resolveAdminTab(query = {}, allowCorrections = false) {
  if (query.edit) return "general";
  if (query.community) return "community";
  if (query.system) return "system";
  if (allowCorrections && query.correction) return "corrections";
  const allowed = allowCorrections ? ["general", "community", "system", "corrections"] : ["general", "community", "system"];
  return allowed.includes(query.tab) ? query.tab : "general";
}

function listSystemKnowledgeFiles(rootDir = process.cwd()) {
  return loadKnowledgeEntries(rootDir, { includeHidden: true })
    .filter((entry) => ["core", "plugins"].includes(entry.scope) && (entry.generated || !entry.editable))
    .map((entry) => ({
      ...entry,
      slug: entry.file_slug,
      entry_slug: entry.slug,
      path: `knowledge/${entry.path}`
    }))
    .sort((a, b) => a.scope.localeCompare(b.scope) || a.title.localeCompare(b.title));
}

function canReadOkfVisibility(access, visibility) {
  if (visibility === "admin") return access.capabilities.read_admin_fields;
  if (visibility === "mod") return access.capabilities.read_mod_fields;
  return access.capabilities.read_user_fields;
}

function communityFileValuesForUser(db, user, values, existing = null) {
  const access = accessForUser(db, user);
  if (existing && !canReadOkfVisibility(access, existing.visibility)) {
    throw new Error("That community knowledge file is not available to your account.");
  }
  const requestedVisibility = VISIBILITY_VALUES.includes(values.visibility) ? values.visibility : existing?.visibility || "user";
  if (!canReadOkfVisibility(access, requestedVisibility)) {
    throw new Error(`You cannot create or edit ${requestedVisibility}-only knowledge.`);
  }
  return { ...values, visibility: requestedVisibility };
}

function findSystemKnowledgeFile(files, key) {
  const value = String(key || "");
  return files.find((file) =>
    file.slug === value ||
    file.entry_slug === value ||
    file.id === value ||
    file.path === value
  ) || null;
}

function view(name) {
  return path.join(__dirname, "views", `${name}.ejs`);
}

function requireAdmin(req, res, next) {
  if (req.session.user?.isAdmin) return next();
  return denied(res);
}

function requireOkfManagement(db) {
  return (req, res, next) => {
    if (accessForUser(db, req.session.user).canEdit) return next();
    return denied(res);
  };
}

function requireOkfEdit(db) {
  return (req, res, next) => {
    if (accessForUser(db, req.session.user).canEdit) return next();
    return denied(res);
  };
}

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

function categoriesFor(entries) {
  return Array.from(new Set(entries.map((entry) => entry.category).filter(Boolean))).sort();
}

function tagsFor(entries) {
  return Array.from(new Set(entries.flatMap((entry) => entry.tags || []).filter(Boolean))).sort();
}
