/**
 * OpenClaw KnowHub Plugin
 *
 * Knowledge management integration for OpenClaw agents.
 * Provides tools for searching and saving knowledge, with automatic reminders.
 */

import { Type } from "@sinclair/typebox";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";

// ============================================================================
// Types
// ============================================================================

type KnowHubConfig = {
  apiUrl: string;
  submittedBy: string;
  reminderMode: "off" | "minimal" | "normal" | "aggressive";
  enableServerExtraction: boolean;
  privacyMode: "strict" | "relaxed";
};

type KnowledgeSearchResult = {
  id: string;
  task: string;
  content: string;
  types: string[];
  eval: {
    score: number;
    helpful: number;
    harmful: number;
    confidence: number;
  };
  quality_score: number;
  source?: {
    name?: string;
    urls?: string[];
  };
};

// ============================================================================
// Security
// ============================================================================

const PROMPT_INJECTION_PATTERNS = [
  /ignore (all|any|previous|above|prior) instructions/i,
  /do not follow (the )?(system|developer)/i,
  /system prompt/i,
  /<\s*(system|assistant|developer|tool)\b/i,
];

function looksLikePromptInjection(text: string): boolean {
  const normalized = text.replace(/\s+/g, " ").trim();
  return PROMPT_INJECTION_PATTERNS.some((pattern) => pattern.test(normalized));
}

export { looksLikePromptInjection };

const PROMPT_ESCAPE_MAP: Record<string, string> = {
  "&": "&amp;",
  "<": "&lt;",
  ">": "&gt;",
  '"': "&quot;",
  "'": "&#39;",
};

function escapeForPrompt(text: string): string {
  return text.replace(/[&<>"']/g, (char) => PROMPT_ESCAPE_MAP[char] ?? char);
}

export { escapeForPrompt };

function sanitizeMessage(msg: unknown, mode: "strict" | "relaxed"): object {
  if (!msg || typeof msg !== "object") {
    return {};
  }

  const msgObj = msg as Record<string, unknown>;
  const content = msgObj.content;

  if (mode === "relaxed") {
    return {
      role: msgObj.role,
      content,
    };
  }

  // strict mode: redact sensitive info
  if (typeof content === "string") {
    return {
      role: msgObj.role,
      content: redactSensitiveInfo(content),
    };
  }

  return {
    role: msgObj.role,
    content,
  };
}

function redactSensitiveInfo(text: string): string {
  return text
    .replace(/\/Users\/[^\/\s]+/g, "/Users/[REDACTED]")
    .replace(/\/home\/[^\/\s]+/g, "/home/[REDACTED]")
    .replace(/C:\\Users\\[^\\\s]+/g, "C:\\Users\\[REDACTED]")
    .replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, "[EMAIL]")
    .replace(/\b\d{3}-\d{3}-\d{4}\b/g, "[PHONE]")
    .replace(/\+\d{10,}/g, "[PHONE]")
    .replace(/sk-[a-zA-Z0-9]{32,}/g, "[API_KEY]")
    .replace(/Bearer\s+[a-zA-Z0-9_-]+/g, "Bearer [TOKEN]")
    .replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, "[IP]");
}

export { redactSensitiveInfo };

// ============================================================================
// Helper Functions
// ============================================================================

function getReminderInterval(mode: string): number {
  switch (mode) {
    case "minimal":
      return 5;
    case "normal":
      return 3;
    case "aggressive":
      return 2;
    default:
      return 3;
  }
}

export { getReminderInterval };

function formatKnowledgeResults(results: KnowledgeSearchResult[]): string {
  if (results.length === 0) {
    return "未找到相关知识";
  }

  return results
    .map((k, idx) => {
      // Security: check for prompt injection
      if (
        looksLikePromptInjection(k.task) ||
        looksLikePromptInjection(k.content)
      ) {
        return null;
      }

      const typesStr = k.types.join(", ");
      const sourceName = k.source?.name ? ` (来源: ${escapeForPrompt(k.source.name)})` : "";
      const contentPreview = escapeForPrompt(k.content.substring(0, 150));

      return `${idx + 1}. [${escapeForPrompt(k.task)}]${sourceName}\n   类型: ${typesStr}\n   内容: ${contentPreview}${k.content.length > 150 ? "..." : ""}\n   评分: ${k.eval.score}/5 (质量分: ${k.quality_score.toFixed(1)})`;
    })
    .filter(Boolean)
    .join("\n\n");
}

// ============================================================================
// Plugin Definition
// ============================================================================

const knowhubPlugin = {
  id: "knowhub",
  name: "KnowHub",
  description: "Knowledge management integration for OpenClaw agents",
  kind: "knowledge" as const,

  register(api: OpenClawPluginApi) {
    const cfg = api.pluginConfig as KnowHubConfig;

    // Validate config
    if (!cfg.apiUrl) {
      throw new Error("knowhub: apiUrl is required");
    }

    api.logger.info(`knowhub: plugin registered (server: ${cfg.apiUrl})`);

    // State for reminder counter
    const llmCallCount = new Map<string, number>();

    // ========================================================================
    // Tools
    // ========================================================================

    api.registerTool(
      {
        name: "kb_search",
        label: "KnowHub Search",
        description:
          "搜索 KnowHub 知识库。在遇到复杂任务、不确定用什么工具、多次失败时使用。",
        parameters: Type.Object({
          query: Type.String({ description: "搜索查询" }),
          top_k: Type.Optional(Type.Number({ description: "返回数量 (默认: 5)" })),
          min_score: Type.Optional(Type.Number({ description: "最低评分 (默认: 3)" })),
          types: Type.Optional(
            Type.Array(Type.String(), { description: "知识类型过滤，如 ['tool', 'strategy']" })
          ),
        }),
        async execute(_toolCallId, params) {
          const { query, top_k = 5, min_score = 3, types } = params as {
            query: string;
            top_k?: number;
            min_score?: number;
            types?: string[];
          };

          try {
            let url = `${cfg.apiUrl}/api/knowledge/search?q=${encodeURIComponent(query)}&top_k=${top_k}&min_score=${min_score}`;
            if (types && types.length > 0) {
              url += `&types=${types.join(",")}`;
            }

            const response = await fetch(url);

            if (!response.ok) {
              return {
                content: [
                  {
                    type: "text",
                    text: `搜索失败: ${response.statusText}`,
                  },
                ],
                details: { error: response.statusText },
              };
            }

            const data = (await response.json()) as {
              results: KnowledgeSearchResult[];
              count: number;
            };

            const formatted = formatKnowledgeResults(data.results);

            return {
              content: [
                {
                  type: "text",
                  text: `找到 ${data.count} 条知识：\n\n${formatted}`,
                },
              ],
              details: { count: data.count, results: data.results },
            };
          } catch (err) {
            api.logger.warn(`knowhub: search failed: ${String(err)}`);
            return {
              content: [
                {
                  type: "text",
                  text: `搜索失败: ${String(err)}`,
                },
              ],
              details: { error: String(err) },
            };
          }
        },
      },
      { name: "kb_search" }
    );

    api.registerTool(
      {
        name: "kb_save",
        label: "KnowHub Save",
        description:
          "保存知识到 KnowHub。在使用资源后、获得用户反馈后、搜索过程有发现时使用。",
        parameters: Type.Object({
          task: Type.String({ description: "任务场景描述" }),
          content: Type.String({ description: "核心知识内容" }),
          types: Type.Array(Type.String(), {
            description:
              "知识类型，如 ['tool', 'strategy']。可选: user_profile, strategy, tool, usecase, definition, plan",
          }),
          score: Type.Optional(Type.Number({ description: "评分 1-5 (默认: 3)" })),
          source_name: Type.Optional(Type.String({ description: "资源名称" })),
          source_urls: Type.Optional(
            Type.Array(Type.String(), { description: "参考链接" })
          ),
        }),
        async execute(_toolCallId, params, ctx) {
          const {
            task,
            content,
            types,
            score = 3,
            source_name,
            source_urls,
          } = params as {
            task: string;
            content: string;
            types: string[];
            score?: number;
            source_name?: string;
            source_urls?: string[];
          };

          // Validate
          if (!task || !content || !types || types.length === 0) {
            return {
              content: [
                {
                  type: "text",
                  text: "缺少必需参数: task, content, types",
                },
              ],
              details: { error: "missing_params" },
            };
          }

          if (score < 1 || score > 5) {
            return {
              content: [
                {
                  type: "text",
                  text: "评分必须在 1-5 之间",
                },
              ],
              details: { error: "invalid_score" },
            };
          }

          try {
            const body = {
              task,
              content,
              types,
              score,
              scopes: ["org:openclaw"],
              owner: `agent:${ctx?.agentId || "unknown"}`,
              source: {
                name: source_name || "",
                category: "exp",
                urls: source_urls || [],
                agent_id: ctx?.agentId || "unknown",
                submitted_by: cfg.submittedBy,
                message_id: "",
              },
            };

            const response = await fetch(`${cfg.apiUrl}/api/knowledge`, {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify(body),
            });

            if (!response.ok) {
              return {
                content: [
                  {
                    type: "text",
                    text: `保存失败: ${response.statusText}`,
                  },
                ],
                details: { error: response.statusText },
              };
            }

            const data = (await response.json()) as { id: string };

            return {
              content: [
                {
                  type: "text",
                  text: `✅ 知识已保存 (ID: ${data.id})`,
                },
              ],
              details: { id: data.id },
            };
          } catch (err) {
            api.logger.warn(`knowhub: save failed: ${String(err)}`);
            return {
              content: [
                {
                  type: "text",
                  text: `保存失败: ${String(err)}`,
                },
              ],
              details: { error: String(err) },
            };
          }
        },
      },
      { name: "kb_save" }
    );

    api.registerTool(
      {
        name: "kb_update",
        label: "KnowHub Update",
        description: "更新知识的有效性反馈。使用知识后提供反馈。",
        parameters: Type.Object({
          knowledge_id: Type.String({ description: "知识 ID" }),
          is_helpful: Type.Boolean({ description: "是否有用" }),
          feedback: Type.Optional(Type.String({ description: "反馈说明" })),
        }),
        async execute(_toolCallId, params) {
          const { knowledge_id, is_helpful, feedback } = params as {
            knowledge_id: string;
            is_helpful: boolean;
            feedback?: string;
          };

          try {
            const body = is_helpful
              ? {
                  add_helpful_case: {
                    task: feedback || "使用成功",
                    outcome: "有效",
                    timestamp: new Date().toISOString(),
                  },
                }
              : {
                  add_harmful_case: {
                    task: feedback || "使用失败",
                    outcome: "无效",
                    reason: feedback || "未说明",
                    timestamp: new Date().toISOString(),
                  },
                };

            const response = await fetch(
              `${cfg.apiUrl}/api/knowledge/${knowledge_id}`,
              {
                method: "PUT",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(body),
              }
            );

            if (!response.ok) {
              return {
                content: [
                  {
                    type: "text",
                    text: `更新失败: ${response.statusText}`,
                  },
                ],
                details: { error: response.statusText },
              };
            }

            return {
              content: [
                {
                  type: "text",
                  text: "✅ 反馈已提交",
                },
              ],
              details: { success: true },
            };
          } catch (err) {
            api.logger.warn(`knowhub: update failed: ${String(err)}`);
            return {
              content: [
                {
                  type: "text",
                  text: `更新失败: ${String(err)}`,
                },
              ],
              details: { error: String(err) },
            };
          }
        },
      },
      { name: "kb_update" }
    );

    // ========================================================================
    // Lifecycle Hooks
    // ========================================================================

    // Initial reminder
    if (cfg.reminderMode !== "off") {
      api.on("before_agent_start", async () => {
        return {
          prependContext: `
💡 KnowHub 知识库已启用

可用工具：
- kb_search: 搜索知识（遇到复杂任务、不确定用什么工具、多次失败时）
- kb_save: 保存知识（使用资源后、获得用户反馈后、搜索过程有发现时）
- kb_update: 反馈知识有效性（使用知识后）

建议：开始任务前先搜索相关知识，完成后及时保存新发现。
`.trim(),
        };
      });
    }

    // Periodic reminder
    if (cfg.reminderMode !== "off") {
      api.on("before_prompt_build", async (_event, ctx) => {
        const sessionKey = ctx.sessionKey ?? "default";

        // Increment counter
        const count = (llmCallCount.get(sessionKey) ?? 0) + 1;
        llmCallCount.set(sessionKey, count);

        // Check interval
        const interval = getReminderInterval(cfg.reminderMode);
        if (count % interval !== 0) {
          return;
        }

        return {
          prependContext:
            "💡 提醒：如果遇到复杂问题、使用了工具/资源、或获得了用户反馈，记得用 kb_save 保存知识到 KnowHub。",
        };
      });
    }

    // Cleanup and optional server extraction
    api.on("agent_end", async (event, ctx) => {
      const sessionKey = ctx.sessionKey ?? "default";

      // Cleanup counter
      llmCallCount.delete(sessionKey);

      // Optional: server extraction
      if (cfg.enableServerExtraction && event.messages && event.messages.length > 0) {
        // Async submission, don't block
        submitForExtraction(event.messages, ctx, cfg, api).catch((err) => {
          api.logger.warn(`knowhub: extraction failed: ${String(err)}`);
        });
      }
    });

    // ========================================================================
    // Service
    // ========================================================================

    api.registerService({
      id: "knowhub",
      start: () => {
        api.logger.info(
          `knowhub: initialized (server: ${cfg.apiUrl}, reminder: ${cfg.reminderMode})`
        );
      },
      stop: () => {
        api.logger.info("knowhub: stopped");
      },
    });
  },
};

// ============================================================================
// Server Extraction
// ============================================================================

async function submitForExtraction(
  messages: unknown[],
  ctx: { agentId?: string; sessionKey?: string },
  cfg: KnowHubConfig,
  api: OpenClawPluginApi
): Promise<void> {
  const sanitized = messages.map((msg) => sanitizeMessage(msg, cfg.privacyMode));

  const response = await fetch(`${cfg.apiUrl}/api/extract`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      messages: sanitized,
      agent_id: ctx.agentId,
      submitted_by: cfg.submittedBy,
      session_key: ctx.sessionKey,
    }),
  });

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }

  const result = (await response.json()) as { extracted_count: number };
  api.logger.info?.(`knowhub: extracted ${result.extracted_count} knowledge items`);
}

export default knowhubPlugin;
