/**
 * ChannelRouter — 频道调度（从 engine.js 搬出）
 *
 * 频道 = 内部 Channel，和 Telegram/飞书一样通过 Hub 路由。
 * 包装 channel-ticker（不改 ticker，只提供回调）。
 *
 * 搬出的方法：
 *   _getChannelAgentOrder  → getAgentOrder()
 *   _executeChannelCheck   → _executeCheck()
 *   _executeChannelReply   → _executeReply()
 *   _channelMemorySummarize → _memorySummarize()
 *   _setupChannelPostHandler → setupPostHandler()
 *   toggleChannels          → toggle()
 */

import fs from "fs";
import path from "path";
import { createChannelTicker } from "../lib/channels/channel-ticker.js";
import { appendMessage, formatMessagesForLLM } from "../lib/channels/channel-store.js";
import { loadConfig } from "../lib/memory/config-loader.js";
import { callText } from "../core/llm-client.js";
import { runAgentSession } from "./agent-executor.js";
import { debugLog } from "../lib/debug-log.js";
import { getLocale } from "../server/i18n.js";

export class ChannelRouter {
  /**
   * @param {object} opts
   * @param {import('./index.js').Hub} opts.hub
   */
  static _AGENT_ORDER_TTL = 30_000; // 30 秒

  constructor({ hub }) {
    this._hub = hub;
    this._ticker = null;
    this._agentOrderCache = null; // { list: string[], ts: number }
  }

  /** @returns {import('../core/engine.js').HanaEngine} */
  get _engine() { return this._hub.engine; }

  _getAgentInstance(agentId) {
    return this._engine.getAgent?.(agentId)
      || this._engine.agents?.get?.(agentId)
      || null;
  }

  _resolveMemoryMasterEnabled(agentId, { agentInstance = null, cfg = null } = {}) {
    if (agentInstance) return agentInstance.memoryMasterEnabled !== false;
    const resolvedCfg = cfg || loadConfig(path.join(this._engine.agentsDir, agentId, "config.yaml"));
    return resolvedCfg?.memory?.enabled !== false;
  }

  // ──────────── 生命周期 ────────────

  start() {
    const engine = this._engine;
    if (!engine.channelsDir) return;

    this._ticker = createChannelTicker({
      channelsDir: engine.channelsDir,
      agentsDir: engine.agentsDir,
      getAgentOrder: () => this.getAgentOrder(),
      executeCheck: (agentId, channelName, newMessages, allUpdates, opts) =>
        this._executeCheck(agentId, channelName, newMessages, allUpdates, opts),
      onMemorySummarize: (agentId, channelName, contextText) =>
        this._memorySummarize(agentId, channelName, contextText),
      onEvent: (event, data) => {
        this._hub.eventBus.emit({ type: event, ...data }, null);
      },
    });
    this._ticker.start();
  }

  async stop() {
    if (this._ticker) {
      await this._ticker.stop();
      this._ticker = null;
    }
  }

  async toggle(enabled) {
    if (enabled) {
      if (this._ticker) return;
      this.start();
      this.setupPostHandler();
    } else {
      await this.stop();
    }
  }

  triggerImmediate(channelName, opts) {
    return this._ticker?.triggerImmediate(channelName, opts);
  }

  /**
   * 注入频道 post 回调到当前 agent
   * agent 用 channel tool 发消息后，触发其他 agent 的 triage
   */
  setupPostHandler() {
    for (const [, agent] of this._engine.agents || []) {
      agent.setChannelPostHandler((channelName, senderId, message) => {
        debugLog()?.log("channel", `agent ${senderId} posted to #${channelName}, triggering triage`);
        if (message) {
          this._hub.eventBus.emit({
            type: "channel_new_message",
            channelName,
            sender: senderId,
            message,
          }, null);
        }
        this.triggerImmediate(channelName)?.catch(err =>
          console.error(`[channel] agent post triage 失败: ${err.message}`)
        );
      });
    }
  }

  // ──────────── 频道 Agent 顺序 ────────────

  /** 获取频道轮转候选 agent 列表；具体频道 membership 由 channel frontmatter 决定 */
  getAgentOrder() {
    const now = Date.now();
    if (this._agentOrderCache && now - this._agentOrderCache.ts < ChannelRouter._AGENT_ORDER_TTL) {
      return this._agentOrderCache.list;
    }
    try {
      const entries = fs.readdirSync(this._engine.agentsDir, { withFileTypes: true });
      const list = entries
        .filter(e => e.isDirectory())
        .filter(e => {
          const configPath = path.join(this._engine.agentsDir, e.name, "config.yaml");
          return fs.existsSync(configPath);
        })
        .map(e => e.name);
      this._agentOrderCache = { list, ts: now };
      return list;
    } catch {
      return [];
    }
  }

  // ──────────── Triage + Reply ────────────

  /**
   * 频道检查回调：triage → 两轮 Agent Session → 写入回复
   * 从 engine._executeChannelCheck 搬入
   */
  async _executeCheck(agentId, channelName, newMessages, _allChannelUpdates, { signal } = {}) {
    const engine = this._engine;
    const msgText = formatMessagesForLLM(newMessages);

    // ── 读 agent 完整上下文 ──
    const readFile = (p) => { try { return fs.readFileSync(p, "utf-8"); } catch { return ""; } };
    const agentDir = path.join(engine.agentsDir, agentId);

    // 复用 Agent 实例的 personality（identity + yuan + ishiki 已在内存中组装）
    const agentInstance = this._getAgentInstance(agentId);
    const cfg = agentInstance?.config || loadConfig(path.join(agentDir, "config.yaml"));
    const agentName = cfg.agent?.name || agentId;

    const agentContext = agentInstance?.personality
      || [readFile(path.join(agentDir, "identity.md")),
          readFile(path.join(engine.productDir, "yuan", `${cfg.agent?.yuan || "hanako"}.md`)),
          readFile(path.join(agentDir, "ishiki.md"))].filter(Boolean).join("\n\n");

    // memory.md 和 user.md 内容会变，仍需从磁盘读取
    // 记忆 master 关闭时跳过 memory.md（user.md 是用户档案，不属于记忆系统）
    const memoryMasterOn = this._resolveMemoryMasterEnabled(agentId, { agentInstance, cfg });
    const memoryMd = memoryMasterOn ? readFile(path.join(agentDir, "memory", "memory.md")) : "";
    const userMd = readFile(path.join(engine.userDir, "user.md"));
    const isZh = getLocale().startsWith("zh");
    const memoryContext = memoryMd?.trim()
      ? (isZh ? `\n\n你的记忆：\n${memoryMd}` : `\n\nYour memory:\n${memoryMd}`)
      : "";
    const userContext = userMd?.trim()
      ? (isZh ? `\n\n用户档案：\n${userMd}` : `\n\nUser profile:\n${userMd}`)
      : "";

    // ── 检测 @ ──
    const isMentioned = msgText.includes(`@${agentName}`) || msgText.includes(`@${agentId}`);

    // ── Step 1: Triage ──
    let shouldReply = isMentioned;

    if (!shouldReply) {
      try {
        const utilCfg = engine.resolveUtilityConfig({ agentId }) || {};
        const { utility_large: model, large_api_key: api_key, large_base_url: base_url, large_api: api } = utilCfg;
        if (api_key && base_url && api) {
          const triageSystem = agentContext + memoryContext + userContext
            + "\n\n---\n\n"
            + (isZh
              ? "你在一个群聊频道里。阅读以下最近的消息，判断你是否要回复。\n"
                + "回答 YES 的情况：有人跟你说话、@你、问了你能回答的问题、或者你有想说的话。\n"
                + "回答 NO 的情况：别人已经充分回答了问题（你没有新的补充）、话题跟你无关、你插不上话、或者你刚回复过且没人追问你。\n"
                + "只回答 YES 或 NO。"
              : "You are in a group chat channel. Read the recent messages below and decide whether you should reply.\n"
                + "Answer YES if: someone is talking to you, @-mentions you, asks a question you can answer, or you have something to say.\n"
                + "Answer NO if: the question has already been adequately answered (you have nothing new to add), the topic is irrelevant to you, you can't contribute, or you just replied and no one followed up.\n"
                + "Answer only YES or NO.");

          const triageTimeout = AbortSignal.timeout(10_000);
          const triageSignal = signal
            ? AbortSignal.any([signal, triageTimeout])
            : triageTimeout;
          const answer = await callText({
            api, model,
            apiKey: api_key,
            baseUrl: base_url,
            systemPrompt: triageSystem,
            messages: [{ role: "user", content: isZh ? `#${channelName} 频道最近消息：\n${msgText}` : `#${channelName} recent messages:\n${msgText}` }],
            temperature: 0,
            maxTokens: 10,
            timeoutMs: 10_000,
            signal: triageSignal,
          });
          shouldReply = answer.trim().toUpperCase().includes("YES");
        } else {
          // utility_large 凭证不完整，跳过 triage 直接回复
          shouldReply = true;
        }
      } catch (err) {
        // utility 模型未配置或 triage 调用失败 → 默认回复（让 agent 自己在 reply 阶段判断要不要说话）
        console.warn(`[channel] triage 不可用，默认回复 (${agentId}/#${channelName}): ${err.message}`);
        shouldReply = true;
      }
    }

    console.log(`\x1b[90m[channel] triage ${agentId}/#${channelName}: ${shouldReply ? "YES" : "NO"}${isMentioned ? " (@)" : ""}\x1b[0m`);
    debugLog()?.log("channel", `triage ${agentId}/#${channelName}: ${shouldReply ? "YES" : "NO"}${isMentioned ? " (mentioned)" : ""} (${newMessages.length} msgs)`);

    if (!shouldReply) {
      return { replied: false };
    }

    // ── Step 2: 两轮 Agent Session 生成回复 ──
    try {
      const replyText = await this._executeReply(agentId, channelName, msgText, { signal });

      if (!replyText) {
        console.log(`\x1b[90m[channel] ${agentId} 回复为空 (#${channelName})\x1b[0m`);
        return { replied: false };
      }

      // 幽灵消息守卫：reply 生成期间若开关被关 / 任务被 abort，丢弃写入
      if (signal?.aborted || !engine.isChannelsEnabled?.()) {
        debugLog()?.log("channel", `${agentId}/#${channelName}: reply discarded (channels disabled or aborted)`);
        return { replied: false };
      }

      // 写入频道文件
      const channelFile = path.join(engine.channelsDir, `${channelName}.md`);
      const { timestamp } = await appendMessage(channelFile, agentId, replyText);

      console.log(`\x1b[90m[channel] ${agentId} replied #${channelName} (${replyText.length} chars)\x1b[0m`);
      debugLog()?.log("channel", `${agentId} replied #${channelName} (${replyText.length} chars)`);

      // WS 广播
      this._hub.eventBus.emit({
        type: "channel_new_message",
        channelName,
        sender: agentId,
        message: { sender: agentId, timestamp, body: replyText },
      }, null);

      return { replied: true, replyContent: replyText };
    } catch (err) {
      console.error(`[channel] 回复失败 (${agentId}/#${channelName}): ${err.message}`);
      debugLog()?.error("channel", `回复失败 (${agentId}/#${channelName}): ${err.message}`);
      return { replied: false };
    }
  }

  /**
   * 两轮 Agent Session 生成频道回复
   */
  async _executeReply(agentId, channelName, msgText, { signal } = {}) {
    const isZh = getLocale().startsWith("zh");
    const text = await runAgentSession(
      agentId,
      [
        {
          text: isZh
            ? `#${channelName} 频道的最近消息：\n\n${msgText}\n\n`
              + `请阅读这些消息，用 search_memory 查阅记忆来了解上下文和真实发生过的事。\n`
              + `注意：你现在的回复用户看不到，这是你的内部思考环节，仅用于查阅资料和理解上下文。下一轮才是你真正发到群聊的内容。`
            : `Recent messages in #${channelName}:\n\n${msgText}\n\n`
              + `Read these messages and use search_memory to look up memories for context and real events.\n`
              + `Note: your reply right now is invisible to users — this is your internal thinking phase, for research and understanding context only. The next round is what actually gets posted to the chat.`,
          capture: false,
        },
        {
          text: isZh
            ? `现在请给出你想在 #${channelName} 群聊中发送的回复。这条回复会直接发送到群聊，所有人都能看到。\n\n`
              + `回复规定：\n`
              + `- 默认30字以内，像在群里说话，简短自然\n`
              + `- 如果话题确实需要展开（比如讲故事、分析问题、详细解释），可以写到1000字\n`
              + `- 直接输出回复内容，不要加任何前缀、解释、MOOD 或代码块\n`
              + `- 不要重复别人已经说过的内容\n`
              + `- 只说真实发生过的事，不要编造你没做过的活动或经历\n`
              + `- 如果你觉得没什么好说的，回复 [NO_REPLY]`
            : `Now give the reply you want to post in #${channelName}. This reply will be sent directly to the group chat — everyone can see it.\n\n`
              + `Reply rules:\n`
              + `- Keep it under 30 words by default — short and natural, like chatting in a group\n`
              + `- If the topic truly requires elaboration (storytelling, analysis, detailed explanation), you may write up to 1000 words\n`
              + `- Output the reply directly — no prefixes, explanations, MOOD blocks, or code fences\n`
              + `- Don't repeat what others have already said\n`
              + `- Only mention things that actually happened — don't fabricate activities or experiences\n`
              + `- If you have nothing to say, reply [NO_REPLY]`,
          capture: true,
        },
      ],
      { engine: this._engine, signal, sessionSuffix: "channel-temp", readOnly: true },
    );

    if (!text || text.includes("[NO_REPLY]")) {
      debugLog()?.log("channel", `${agentId}/#${channelName}: chose not to reply`);
      return null;
    }

    return text;
  }

  /**
   * 频道记忆摘要
   * 从 engine._channelMemorySummarize 搬入
   */
  async _memorySummarize(agentId, channelName, contextText) {
    const engine = this._engine;
    try {
      // 记忆 master 关闭时不写入新记忆（频道摘要是写侧操作）
      const agentInstance = this._getAgentInstance(agentId);
      const memoryMasterOn = this._resolveMemoryMasterEnabled(agentId, { agentInstance });
      if (!memoryMasterOn) {
        console.log(`\x1b[90m[channel] ${agentId} memory master 已关闭，跳过频道记忆摘要\x1b[0m`);
        return;
      }

      const utilCfg = engine.resolveUtilityConfig({ agentId }) || {};
      const { utility: model, api_key, base_url, api } = utilCfg;
      if (!api_key || !base_url || !api) {
        console.log(`\x1b[90m[channel] ${agentId} 无 API 配置，跳过记忆摘要\x1b[0m`);
        return;
      }

      const isZhMem = getLocale().startsWith("zh");
      const summaryText = await callText({
        api, model,
        apiKey: api_key,
        baseUrl: base_url,
        systemPrompt: isZhMem
          ? "将频道对话摘要为一条简短的记忆（一两句话），记录关键信息和结论。直接输出摘要，不要前缀。"
          : "Summarize the channel conversation into a brief memory (one or two sentences), capturing key information and conclusions. Output the summary directly, no prefix.",
        messages: [{ role: "user", content: isZhMem ? `频道 #${channelName}：\n${contextText.slice(0, 2000)}` : `Channel #${channelName}:\n${contextText.slice(0, 2000)}` }],
        temperature: 0.3,
        maxTokens: 200,
      });

      // 写入 agent 的 fact store
      let factStore = null;
      let needClose = false;

      if (agentInstance?.factStore) {
        factStore = agentInstance.factStore;
      } else {
        const { FactStore } = await import("../lib/memory/fact-store.js");
        const dbPath = path.join(engine.agentsDir, agentId, "memory", "facts.db");
        factStore = new FactStore(dbPath);
        needClose = true;
      }

      const now = new Date();
      try {
        factStore.add({
          fact: `[#${channelName}] ${summaryText}`,
          tags: [isZhMem ? "频道" : "channel", channelName],
          time: now.toISOString().slice(0, 16),
          session_id: `channel-${channelName}`,
        });
      } finally {
        if (needClose) factStore.close();
      }

      console.log(`\x1b[90m[channel] ${agentId} memory saved (#${channelName}, ${summaryText.length} chars)\x1b[0m`);
    } catch (err) {
      console.error(`[channel] 记忆摘要失败 (${agentId}/#${channelName}): ${err.message}`);
    }
  }
}
