import assert from 'node:assert/strict';
import crypto from 'node:crypto';
import fs from 'node:fs';
import test from 'node:test';
import {
  buildWechatTextReply,
  createWechatMpService,
  guardMissingPublicHtmlLinks,
  loadWechatMpConfig,
  splitWechatText,
  verifyWechatMpSignature,
  verifyWechatMpUrlChallenge,
  decryptWechatMpPayload,
  WECHAT_CUSTOMER_TEXT_MAX_BYTES,
} from './wechat-mp.mjs';

function signatureFor(token, timestamp, nonce) {
  return crypto
    .createHash('sha1')
    .update([token, timestamp, nonce].sort().join(''))
    .digest('hex');
}

function inboundXml({
  toUserName = 'gh_test',
  fromUserName = 'openid-1',
  msgType = 'text',
  content = 'hello',
  event = '',
  extraFields = {},
} = {}) {
  const renderedExtras = Object.entries(extraFields)
    .map(([key, value]) => {
      if (value === '' || value === null || value === undefined) return '';
      return typeof value === 'number'
        ? `<${key}>${value}</${key}>`
        : `<${key}><![CDATA[${value}]]></${key}>`;
    })
    .filter(Boolean)
    .join('');
  return [
    '<xml>',
    `<ToUserName><![CDATA[${toUserName}]]></ToUserName>`,
    `<FromUserName><![CDATA[${fromUserName}]]></FromUserName>`,
    '<CreateTime>1710000000</CreateTime>',
    `<MsgType><![CDATA[${msgType}]]></MsgType>`,
    content ? `<Content><![CDATA[${content}]]></Content>` : '',
    event ? `<Event><![CDATA[${event}]]></Event>` : '',
    renderedExtras,
    '<MsgId>10001</MsgId>',
    '</xml>',
  ].join('');
}

function createBoundWechatService({
  token = 'token',
  wechatFetch,
  sessionApiFetch,
  userAuth = {},
  config = {},
  scheduleService = null,
  applySessionLlmProvider = null,
}) {
  return createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      mediaPublicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
      progressDelayMs: 0,
      maxImageBytes: 1024 * 1024,
      ...config,
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active', nickname: '毕升' };
      },
      async getWechatAgentRoute() {
        return { agentSessionId: 'session-1' };
      },
      async clearWechatAgentRoute() {},
      async canUseChat() {
        return { ok: true };
      },
      async resolveWorkingDir() {
        return '/tmp/user-1';
      },
      async getAgentSessionPolicy() {
        return { enableContextMemory: false, extensionOverrides: [], unrestricted: true };
      },
      async getUserPublishLayout() {
        return { displayName: 'John', username: 'john', slug: 'john', constraints: null };
      },
      async registerAgentSession() {},
      async upsertWechatAgentRoute() {},
      async billSessionUsage() {},
      async recordWechatMpMessage() {
        return { inserted: true };
      },
      async finishWechatMpMessage() {},
      async insertWechatMpMessageDetail() {},
      ...userAuth,
    },
    sessionApiFetch,
    scheduleService,
    applySessionLlmProvider,
    wechatFetch,
    apiFetch: async () => new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }),
  });
}

test('splitWechatText respects WeChat 2048-byte customer service limit', () => {
  const longChinese = '中'.repeat(900);
  const chunks = splitWechatText(longChinese);
  assert.ok(chunks.length > 1);
  for (const chunk of chunks) {
    assert.ok(Buffer.byteLength(chunk, 'utf8') <= WECHAT_CUSTOMER_TEXT_MAX_BYTES);
  }
  assert.equal(chunks.join(''), longChinese);
});

test('guardMissingPublicHtmlLinks blocks missing MindSpace public html links', async () => {
  const text =
    '页面已创建：\nhttps://m.tkmind.cn/MindSpace/a70ff537-8908-486e-9b6c-042e07cc25db/public/missing.html';
  const guarded = await guardMissingPublicHtmlLinks(text, {
    linkExists: async () => false,
  });
  assert.doesNotMatch(guarded, /https:\/\/g2\.tkmind\.cn\/MindSpace/);
  assert.match(guarded, /页面生成未完成/);
  assert.match(guarded, /missing\.html/);
});

test('wechat mp service splits long agent replies into multiple customer messages', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const longReply = '答'.repeat(900);
  const wechatCalls = [];
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      progressDelayMs: 0,
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active', nickname: '毕升' };
      },
      async getWechatAgentRoute() {
        return { agentSessionId: 'session-1' };
      },
      async clearWechatAgentRoute() {},
      async canUseChat() {
        return { ok: true };
      },
      async resolveWorkingDir() {
        return '/tmp/user-1';
      },
      async getAgentSessionPolicy() {
        return { enableContextMemory: false, extensionOverrides: [], unrestricted: true };
      },
      async getUserPublishLayout() {
        return { displayName: 'John', username: 'john', slug: 'john', constraints: null };
      },
      async registerAgentSession() {},
      async upsertWechatAgentRoute() {},
      async billSessionUsage() {},
    },
    sessionApiFetch: async (sessionId, pathname, init = {}) => {
      assert.equal(sessionId, 'session-1');
      if (pathname === '/sessions/session-1/events') {
        return new Response(
          [
            `data: {"type":"Message","request_id":"req-long","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":${JSON.stringify(longReply)}}]}}\n\n`,
            'data: {"type":"Finish","request_id":"req-long","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
          ].join(''),
          { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
        );
      }
      if (pathname === '/sessions/session-1/reply') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      throw new Error(`unexpected api path: ${pathname}`);
    },
    wechatFetch: async (url, init = {}) => {
      wechatCalls.push([url, init.method ?? 'GET', init.body ?? null]);
      if (String(url).includes('/cgi-bin/stable_token')) {
        return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (String(url).includes('/cgi-bin/message/custom/send')) {
        const payload = JSON.parse(init.body);
        assert.ok(Buffer.byteLength(payload.text.content, 'utf8') <= WECHAT_CUSTOMER_TEXT_MAX_BYTES);
        return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      throw new Error(`unexpected wechat url: ${url}`);
    },
  });

  const originalRandomUuid = crypto.randomUUID;
  crypto.randomUUID = () => 'req-long';
  try {
    const result = await service.handleInboundMessage(inboundXml({ content: '给我长回复' }), {
      timestamp,
      nonce,
      signature: signatureFor(token, timestamp, nonce),
    });
    assert.equal(result.status, 200);
    await result.task;
  } finally {
    crypto.randomUUID = originalRandomUuid;
  }

  const sendCalls = wechatCalls.filter(([url]) => String(url).includes('/cgi-bin/message/custom/send'));
  assert.ok(sendCalls.length > 1);
  const combined = sendCalls
    .map(([, , body]) => JSON.parse(body).text.content)
    .join('');
  assert.equal(combined, longReply);
});

test('wechat mp service surfaces wechat customer-service errcode and errmsg', async () => {
  const service = createBoundWechatService({
    userAuth: {
      async getWechatOpenidForUser() {
        return 'openid-1';
      },
    },
    wechatFetch: async (url) => {
      if (String(url).includes('/cgi-bin/stable_token')) {
        return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      return new Response(JSON.stringify({ errcode: 45047, errmsg: 'out of response count limit rid: test-rid' }), {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
      });
    },
  });

  await assert.rejects(
    service.sendTextToUser('user-1', 'hello'),
    /errcode=45047.*out of response count limit/,
  );
});

test('wechat mp service strips markdown emphasis around outbound links', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const wechatCalls = [];
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      progressDelayMs: 0,
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active', nickname: '毕升' };
      },
      async getWechatAgentRoute() {
        return { agentSessionId: 'session-1' };
      },
      async clearWechatAgentRoute() {},
      async canUseChat() {
        return { ok: true };
      },
      async resolveWorkingDir() {
        return '/tmp/user-1';
      },
      async getAgentSessionPolicy() {
        return { enableContextMemory: false, extensionOverrides: [], unrestricted: true };
      },
      async getUserPublishLayout() {
        return { displayName: 'John', username: 'john', slug: 'john', constraints: null };
      },
      async registerAgentSession() {},
      async upsertWechatAgentRoute() {},
      async billSessionUsage() {},
    },
    sessionApiFetch: async (sessionId, pathname) => {
      assert.equal(sessionId, 'session-1');
      if (pathname === '/sessions/session-1/events') {
        return new Response(
          [
            `data: {"type":"Message","request_id":"req-link-md","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"👉 **跑步路书**\\nhttps://example.com/running-route.html**"}]}}\n\n`,
            'data: {"type":"Finish","request_id":"req-link-md","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
          ].join(''),
          { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
        );
      }
      if (pathname === '/sessions/session-1/reply') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      throw new Error(`unexpected api path: ${pathname}`);
    },
    wechatFetch: async (url, init = {}) => {
      wechatCalls.push([url, init.method ?? 'GET', init.body ?? null]);
      if (String(url).includes('/cgi-bin/stable_token')) {
        return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (String(url).includes('/cgi-bin/message/custom/send')) {
        return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      throw new Error(`unexpected wechat url: ${url}`);
    },
  });

  const originalRandomUuid = crypto.randomUUID;
  crypto.randomUUID = () => 'req-link-md';
  try {
    const result = await service.handleInboundMessage(inboundXml({ content: '给我链接' }), {
      timestamp,
      nonce,
      signature: signatureFor(token, timestamp, nonce),
    });
    assert.equal(result.status, 200);
    await result.task;
  } finally {
    crypto.randomUUID = originalRandomUuid;
  }

  const sendCall = wechatCalls.find(([url]) => String(url).includes('/cgi-bin/message/custom/send'));
  const payload = JSON.parse(sendCall[2]);
  assert.equal(payload.text.content.includes('**'), false);
  assert.equal(payload.text.content.includes('https://example.com/running-route.html**'), false);
  assert.match(payload.text.content, /https:\/\/example\.com\/running-route\.html/);
});

test('loadWechatMpConfig requires full config and enable flag', () => {
  const config = loadWechatMpConfig({
    H5_WECHAT_MP_ENABLED: '1',
    H5_WECHAT_MP_APP_ID: 'wx123',
    H5_WECHAT_MP_APP_SECRET: 'secret',
    H5_WECHAT_MP_TOKEN: 'token',
    H5_PUBLIC_BASE_URL: 'https://example.com',
  });
  assert.equal(config.enabled, true);
  assert.equal(config.bindPath, '/auth/wechat/authorize?intent=login');
});

test('verifyWechatMpSignature accepts valid signature', () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  assert.equal(
    verifyWechatMpSignature({
      token,
      timestamp,
      nonce,
      signature: signatureFor(token, timestamp, nonce),
    }),
    true,
  );
});

test('verifyWechatMpUrlChallenge supports aes mode echostr', () => {
  const token = 'memindprodmp';
  const appId = 'wxaa5f3cbb9ee009c2';
  const encodingAesKey = 'oovBs3npy6NdflERl0LObynzVwH8UgwtCKrfngwfBod';
  const timestamp = '1710000000';
  const nonce = 'nonce123';
  const plainEcho = '77889900';
  const key = Buffer.from(`${encodingAesKey}=`, 'base64');
  const iv = key.subarray(0, 16);
  const random16 = Buffer.alloc(16, 0x61);
  const msgBuf = Buffer.from(plainEcho, 'utf8');
  const appIdBuf = Buffer.from(appId, 'utf8');
  const lenBuf = Buffer.alloc(4);
  lenBuf.writeUInt32BE(msgBuf.length, 0);
  const raw = Buffer.concat([random16, lenBuf, msgBuf, appIdBuf]);
  const pad = 32 - (raw.length % 32);
  const padded = Buffer.concat([raw, Buffer.alloc(pad, pad)]);
  const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
  cipher.setAutoPadding(false);
  const encrypted = Buffer.concat([cipher.update(padded), cipher.final()]).toString('base64');
  const msgSignature = crypto
    .createHash('sha1')
    .update([token, timestamp, nonce, encrypted].sort().join(''))
    .digest('hex');
  const result = verifyWechatMpUrlChallenge(
    {
      encrypt_type: 'aes',
      msg_signature: msgSignature,
      timestamp,
      nonce,
      echostr: encrypted,
    },
    { token, appId, encodingAesKey },
  );
  assert.equal(result.ok, true);
  assert.equal(result.body, plainEcho);
});

test('buildWechatTextReply includes both parties and content', () => {
  const xml = buildWechatTextReply({
    toUserName: 'openid-1',
    fromUserName: 'gh_test',
    content: '收到',
    createTime: 123,
  });
  assert.match(xml, /<ToUserName><!\[CDATA\[openid-1\]\]><\/ToUserName>/);
  assert.match(xml, /<Content><!\[CDATA\[收到\]\]><\/Content>/);
});

test('wechat mp service prompts binding for unknown openid', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return null;
      },
    },
    apiFetch: async () => new Response('{}', { status: 200 }),
  });

  const result = await service.handleInboundMessage(inboundXml(), {
    timestamp,
    nonce,
    signature: signatureFor(token, timestamp, nonce),
  });
  assert.equal(result.status, 200);
  assert.match(result.body, /请先绑定/);
  assert.match(result.body, /https:\/\/example.com\/auth\/wechat\/authorize\?intent=login/);
});

test('wechat mp service routes text to dedicated session and sends customer service reply', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const apiCalls = [];
  const wechatCalls = [];
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active', nickname: '毕升' };
      },
      async getWechatAgentRoute() {
        return { agentSessionId: 'session-1' };
      },
      async clearWechatAgentRoute() {},
      async canUseChat() {
        return { ok: true };
      },
      async resolveWorkingDir() {
        return '/tmp/user-1';
      },
      async getAgentSessionPolicy() {
        return { enableContextMemory: false, extensionOverrides: [], unrestricted: true };
      },
      async getUserPublishLayout() {
        return { displayName: 'John', username: 'john', slug: 'john', constraints: null };
      },
      async registerAgentSession() {},
      async upsertWechatAgentRoute() {},
      async billSessionUsage(userId, sessionId, tokenState, requestId) {
        apiCalls.push(['bill', userId, sessionId, tokenState, requestId]);
      },
    },
    apiFetch: async (pathname, init = {}) => {
      apiCalls.push(['fallback', pathname, init.method ?? 'GET']);
      return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
    },
    sessionApiFetch: async (sessionId, pathname, init = {}) => {
      assert.equal(sessionId, 'session-1');
      apiCalls.push([pathname, init.method ?? 'GET']);
      if (pathname === '/sessions/session-1/events') {
        return new Response(
          [
            'data: {"type":"Message","request_id":"req-1","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"wx_abcd1234，页面已创建完毕！\\n[标题](https://example.com/page.html)\\n\\n| 项 | 值 |\\n| --- | --- |\\n| A | B |"}]}}\n\n',
            'data: {"type":"Finish","request_id":"req-1","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
          ].join(''),
          { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
        );
      }
      if (pathname === '/sessions/session-1/reply') {
        const body = JSON.parse(init.body);
        assert.equal(body.user_message.content[0].text, '【微信服务号新消息】请只回答下面这条用户消息，不要主动延续无关的历史话题。\n若用户只是在测试连通性，请一句话确认收到即可，不要展开旧任务。\n\n用户消息：帮我做个页面');
        body.request_id && (apiCalls[apiCalls.length - 1][2] = body.request_id);
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      if (pathname === '/agent/harness_remember') {
        const body = JSON.parse(init.body);
        assert.equal(body.sessionId, 'session-1');
        assert.match(body.content, /当前服务号用户名称/);
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      if (pathname === '/agent/session/reconcile') {
        const body = JSON.parse(init.body);
        assert.equal(body.user_context.displayName, '毕升');
        assert.equal(body.user_context.username, '毕升');
        assert.equal(body.user_context.slug, null);
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      if (pathname === '/agent/harness_bootstrap') {
        const body = JSON.parse(init.body);
        assert.equal(body.sessionId, 'session-1');
        assert.equal(body.force, true);
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      throw new Error(`unexpected api path: ${pathname}`);
    },
    wechatFetch: async (url, init = {}) => {
      wechatCalls.push([url, init.method ?? 'GET', init.body ?? null]);
      if (String(url).includes('/cgi-bin/stable_token')) {
        return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (String(url).includes('/cgi-bin/message/custom/send')) {
        const payload = JSON.parse(init.body);
        assert.equal(payload.touser, 'openid-1');
        assert.match(payload.text.content, /页面已创建完毕/);
        assert.doesNotMatch(payload.text.content, /^wx_/);
        assert.match(payload.text.content, /^毕升，页面已创建完毕/);
        assert.doesNotMatch(payload.text.content, /\[标题\]\(/);
        assert.match(payload.text.content, /标题\nhttps:\/\/example.com\/page.html/);
        assert.match(payload.text.content, /项：A；值：B/);
        return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      throw new Error(`unexpected wechat url: ${url}`);
    },
  });

  const originalRandomUuid = crypto.randomUUID;
  let uuidCalls = 0;
  crypto.randomUUID = () => {
    uuidCalls += 1;
    return uuidCalls === 1 ? 'req-1' : `uuid-${uuidCalls}`;
  };
  try {
    const result = await service.handleInboundMessage(inboundXml({ content: '帮我做个页面' }), {
      timestamp,
      nonce,
      signature: signatureFor(token, timestamp, nonce),
    });
    assert.equal(result.status, 200);
    assert.match(result.body, /<Content>/);
    await result.task;
  } finally {
    crypto.randomUUID = originalRandomUuid;
  }

  assert.equal(
    apiCalls.some(([pathname]) => pathname === '/sessions/session-1/reply'),
    true,
  );
  assert.equal(
    apiCalls.some(([kind]) => kind === 'fallback'),
    false,
  );
  assert.equal(
    wechatCalls.some(([url]) => String(url).includes('/cgi-bin/message/custom/send')),
    true,
  );
});

test('wechat mp service rewrites internal wx username mentions that appear mid-message', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const sentPayloads = [];
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
      progressDelayMs: 0,
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active', nickname: 'Lumi' };
      },
      async getWechatAgentRoute() {
        return { agentSessionId: 'session-1' };
      },
      async clearWechatAgentRoute() {},
      async canUseChat() {
        return { ok: true };
      },
      async resolveWorkingDir() {
        return '/tmp/user-1';
      },
      async getAgentSessionPolicy() {
        return { enableContextMemory: false, extensionOverrides: [], unrestricted: true };
      },
      async getUserPublishLayout() {
        return { displayName: 'Lumi', username: 'wx_ul610et8', slug: 'wx_ul610et8', constraints: null };
      },
      async registerAgentSession() {},
      async upsertWechatAgentRoute() {},
      async billSessionUsage() {},
    },
    sessionApiFetch: async (sessionId, pathname, init = {}) => {
      assert.equal(sessionId, 'session-1');
      if (pathname === '/sessions/session-1/events') {
        return new Response(
          [
            'data: {"type":"Message","request_id":"req-mid","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"LumiMind 这个感觉很好。\\n\\nwx_ul610et8，这个感觉对了。\\n好的，wx_ul610et8，为你写了一首。"}]}}\n\n',
            'data: {"type":"Finish","request_id":"req-mid","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
          ].join(''),
          { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
        );
      }
      if (pathname === '/sessions/session-1/reply') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      throw new Error(`unexpected api path: ${pathname}`);
    },
    wechatFetch: async (url, init = {}) => {
      if (String(url).includes('/cgi-bin/stable_token')) {
        return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (String(url).includes('/cgi-bin/message/custom/send')) {
        sentPayloads.push(JSON.parse(init.body));
        return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      throw new Error(`unexpected wechat url: ${url}`);
    },
  });

  const originalRandomUuid = crypto.randomUUID;
  crypto.randomUUID = () => 'req-mid';
  try {
    const result = await service.handleInboundMessage(
      inboundXml({ msgType: 'text', content: '这个名字怎么样' }),
      {
        timestamp,
        nonce,
        signature: signatureFor(token, timestamp, nonce),
      },
    );
    await result.task;
  } finally {
    crypto.randomUUID = originalRandomUuid;
  }
  assert.equal(sentPayloads.length, 1);
  assert.doesNotMatch(sentPayloads[0].text.content, /wx_ul610et8/);
  assert.match(sentPayloads[0].text.content, /\nLumi，这个感觉对了。/);
  assert.match(sentPayloads[0].text.content, /好的，Lumi，为你写了一首。/);
});

test('wechat mp service applies provider before forwarding to dedicated session', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const appliedSessions = [];
  const service = createBoundWechatService({
    token,
    applySessionLlmProvider: async (sessionId) => {
      appliedSessions.push(sessionId);
      return { ok: true, providerId: 'custom_tkmind_relay_deepseek', model: 'deepseek-chat' };
    },
    sessionApiFetch: async (sessionId, pathname) => {
      assert.equal(sessionId, 'session-1');
      if (pathname === '/sessions/session-1/events') {
        return new Response(
          [
            'data: {"type":"Message","request_id":"req-provider","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"收到。"}]}}\n\n',
            'data: {"type":"Finish","request_id":"req-provider","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
          ].join(''),
          { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
        );
      }
      if (pathname === '/sessions/session-1/reply') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      throw new Error(`unexpected api path: ${pathname}`);
    },
    wechatFetch: async (url) => {
      if (String(url).includes('/cgi-bin/stable_token')) {
        return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (String(url).includes('/cgi-bin/message/custom/send')) {
        return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      throw new Error(`unexpected wechat url: ${url}`);
    },
  });

  const originalRandomUuid = crypto.randomUUID;
  crypto.randomUUID = () => 'req-provider';
  try {
    const result = await service.handleInboundMessage(inboundXml({ content: '帮我推荐一个跑步路线' }), {
      timestamp,
      nonce,
      signature: signatureFor(token, timestamp, nonce),
    });
    assert.equal(result.status, 200);
    await result.task;
  } finally {
    crypto.randomUUID = originalRandomUuid;
  }

  assert.deepEqual(appliedSessions, ['session-1']);
});

test('wechat mp service injects schedule skill instructions for reminder-like requests', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const service = createBoundWechatService({
    token,
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active', nickname: '毕升' };
      },
      async recordWechatMpMessage() {
        return { inserted: true };
      },
      async insertWechatMpMessageDetail() {},
      async finishWechatMpMessage() {},
      async resolveWorkingDir() {
        return '/tmp/user-1';
      },
      async getAgentSessionPolicy() {
        return { enableContextMemory: false, extensionOverrides: [], unrestricted: true };
      },
      async getUserPublishLayout() {
        return { displayName: 'John', username: 'john', slug: 'john', constraints: null };
      },
      async registerAgentSession() {},
      async upsertWechatAgentRoute() {},
      async billSessionUsage() {},
    },
    sessionApiFetch: async (sessionId, pathname, init = {}) => {
      assert.equal(sessionId, 'session-1');
      if (pathname === '/sessions/session-1/events') {
        return new Response(
          [
            'data: {"type":"Message","request_id":"req-2","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"收到，我先帮你处理。"}]}}\n\n',
            'data: {"type":"Finish","request_id":"req-2","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
          ].join(''),
          { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
        );
      }
      if (pathname === '/sessions/session-1/reply') {
        const body = JSON.parse(init.body);
        assert.match(body.user_message.content[0].text, /加载 `schedule-assistant` skill/);
        assert.match(body.user_message.content[0].text, /schedule_create_item/);
        assert.match(body.user_message.content[0].text, /sourceMessageId: 10001/);
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      throw new Error(`unexpected api path: ${pathname}`);
    },
    wechatFetch: async (url) => {
      if (String(url).includes('/cgi-bin/stable_token')) {
        return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (String(url).includes('/cgi-bin/message/custom/send')) {
        return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      throw new Error(`unexpected wechat url: ${url}`);
    },
  });

  const originalRandomUuid = crypto.randomUUID;
  crypto.randomUUID = () => 'req-2';
  try {
    const result = await service.handleInboundMessage(
      inboundXml({ content: '明天早上六点去跑步，五点半提醒我' }),
      {
        timestamp,
        nonce,
        signature: signatureFor(token, timestamp, nonce),
      },
    );
    assert.equal(result.status, 200);
    await result.task;
  } finally {
    crypto.randomUUID = originalRandomUuid;
  }
});

test('wechat mp service blocks schedule confirmation when no schedule item was written', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const outbound = [];
  const service = createBoundWechatService({
    token,
    scheduleService: {
      async listItemsBySourceMessage(input) {
        assert.equal(input.userId, 'user-1');
        assert.equal(input.sourceMessageId, '10001');
        return [];
      },
    },
    sessionApiFetch: async (sessionId, pathname, init = {}) => {
      assert.equal(sessionId, 'session-1');
      if (pathname === '/sessions/session-1/events') {
        return new Response(
          [
            'data: {"type":"Message","request_id":"req-guard","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"已经设置好了！明早 5:30 提醒起床，6:00 去跑步。"}]}}\n\n',
            'data: {"type":"Finish","request_id":"req-guard","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
          ].join(''),
          { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
        );
      }
      if (pathname === '/sessions/session-1/reply') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      throw new Error(`unexpected api path: ${pathname}`);
    },
    wechatFetch: async (url, init = {}) => {
      if (String(url).includes('/cgi-bin/stable_token')) {
        return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (String(url).includes('/cgi-bin/message/custom/send')) {
        outbound.push(JSON.parse(init.body).text.content);
        return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      throw new Error(`unexpected wechat url: ${url}`);
    },
  });

  const originalRandomUuid = crypto.randomUUID;
  crypto.randomUUID = () => 'req-guard';
  try {
    const result = await service.handleInboundMessage(
      inboundXml({ content: '明天早上六点去跑步，五点半提醒我' }),
      {
        timestamp,
        nonce,
        signature: signatureFor(token, timestamp, nonce),
      },
    );
    assert.equal(result.status, 200);
    await result.task;
  } finally {
    crypto.randomUUID = originalRandomUuid;
  }

  assert.equal(outbound.length, 1);
  assert.match(outbound[0], /没有确认到待办\/提醒已经写入系统/);
  assert.doesNotMatch(outbound[0], /已经设置好了/);
});

test('wechat mp service creates daily todo digest without routing to agent', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const apiCalls = [];
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active', nickname: '毕升' };
      },
      async recordWechatMpMessage() {
        return { inserted: true };
      },
      async finishWechatMpMessage(input) {
        apiCalls.push(['finish', input.status, input.agentSessionId]);
      },
    },
    scheduleService: {
      async createDailyTodoDigest(input) {
        apiCalls.push(['digest', input.userId, input.hour, input.minute, input.channel]);
        return {
          id: 'sub-1',
          userId: input.userId,
          hour: input.hour,
          minute: input.minute,
          timezone: input.timezone,
          channel: input.channel,
        };
      },
    },
    apiFetch: async (pathname) => {
      apiCalls.push(['agent', pathname]);
      return new Response('{}', { status: 200 });
    },
  });

  const result = await service.handleInboundMessage(
    inboundXml({ content: '每天早上 7 点给我发一天的待办记录' }),
    {
      timestamp,
      nonce,
      signature: signatureFor(token, timestamp, nonce),
    },
  );

  assert.equal(result.status, 200);
  assert.match(result.body, /每天早上 7点/);
  assert.deepEqual(apiCalls, [
    ['digest', 'user-1', 7, 0, 'wechat'],
    ['finish', 'done', null],
  ]);
});

test('wechat mp service records plain todo without routing to agent', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const apiCalls = [];
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active', nickname: '毕升' };
      },
      async recordWechatMpMessage() {
        return { inserted: true };
      },
      async finishWechatMpMessage(input) {
        apiCalls.push(['finish', input.status, input.agentSessionId]);
      },
    },
    scheduleService: {
      async createItem(input) {
        apiCalls.push(['todo', input.userId, input.kind, input.title, input.sourceChannel]);
        return {
          id: 'item-1',
          title: input.title,
        };
      },
    },
    apiFetch: async (pathname) => {
      apiCalls.push(['agent', pathname]);
      return new Response('{}', { status: 200 });
    },
  });

  const result = await service.handleInboundMessage(
    inboundXml({ content: '帮我记一下「🍽️ 跟段吃饭」' }),
    {
      timestamp,
      nonce,
      signature: signatureFor(token, timestamp, nonce),
    },
  );

  assert.equal(result.status, 200);
  assert.match(result.body, /已记录到待办列表/);
  assert.deepEqual(apiCalls, [
    ['todo', 'user-1', 'task', '🍽️ 跟段吃饭', 'wechat'],
    ['finish', 'done', null],
  ]);
});

test('wechat mp service creates balance low alert without routing to agent', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const apiCalls = [];
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active', nickname: '毕升' };
      },
      async recordWechatMpMessage() {
        return { inserted: true };
      },
      async finishWechatMpMessage(input) {
        apiCalls.push(['finish', input.status, input.agentSessionId]);
      },
    },
    scheduleService: {
      async createBalanceLowAlert(input) {
        apiCalls.push(['balance', input.userId, input.thresholdCents, input.sourceChannel]);
        return {
          id: 'alert-1',
          thresholdCents: input.thresholdCents,
        };
      },
    },
    apiFetch: async (pathname) => {
      apiCalls.push(['agent', pathname]);
      return new Response('{}', { status: 200 });
    },
  });

  const result = await service.handleInboundMessage(inboundXml({ content: '余额低于 20 元提醒我' }), {
    timestamp,
    nonce,
    signature: signatureFor(token, timestamp, nonce),
  });

  assert.equal(result.status, 200);
  assert.match(result.body, /已设置：当余额低于 20\.00 元时，我会通过服务号提醒你。/);
  assert.deepEqual(apiCalls, [
    ['balance', 'user-1', 2000, 'wechat'],
    ['finish', 'done', null],
  ]);
});

test('wechat mp service asks user to retry when voice recognition is empty', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  let replyCalled = false;
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active', nickname: '毕升' };
      },
      async insertWechatMpMessageDetail() {},
    },
    apiFetch: async (pathname) => {
      if (pathname.includes('/reply')) replyCalled = true;
      return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
    },
  });

  const result = await service.handleInboundMessage(
    inboundXml({
      msgType: 'voice',
      content: '',
      extraFields: { Format: 'amr' },
    }),
    {
      timestamp,
      nonce,
      signature: signatureFor(token, timestamp, nonce),
    },
  );

  assert.equal(result.status, 200);
  assert.match(result.body, /未识别到语音文字/);
  assert.equal(replyCalled, false);
});

test('wechat mp service falls back to ASR when voice recognition is empty', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  let replyCalled = false;
  const prompts = [];
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
      asrTarget: 'https://asr.example.com',
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active', nickname: '毕升' };
      },
      async getWechatAgentRoute() {
        return { agentSessionId: 'session-1' };
      },
      async clearWechatAgentRoute() {},
      async canUseChat() {
        return { ok: true };
      },
      async resolveWorkingDir() {
        return '/tmp/user-1';
      },
      async getAgentSessionPolicy() {
        return { enableContextMemory: false, extensionOverrides: [], unrestricted: true };
      },
      async getUserPublishLayout() {
        return { displayName: 'John', username: 'john', slug: 'john', constraints: null };
      },
      async registerAgentSession() {},
      async upsertWechatAgentRoute() {},
      async billSessionUsage() {},
      async insertWechatMpMessageDetail() {},
    },
    sessionApiFetch: async (sessionId, pathname, init = {}) => {
      assert.equal(sessionId, 'session-1');
      if (pathname === '/sessions/session-1/events') {
        return new Response(
          [
            'data: {"type":"Message","request_id":"req-voice-fallback","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"我收到你的语音了。"}]}}\n\n',
            'data: {"type":"Finish","request_id":"req-voice-fallback","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
          ].join(''),
          { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
        );
      }
      if (pathname === '/sessions/session-1/reply') {
        replyCalled = true;
        const body = JSON.parse(init.body);
        prompts.push(body.user_message.content[0].text);
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      throw new Error(`unexpected api path: ${pathname}`);
    },
    wechatFetch: async (url, init = {}) => {
      if (String(url).includes('/cgi-bin/stable_token')) {
        return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (String(url).includes('/cgi-bin/media/get')) {
        return new Response(Buffer.from('fake-amr-audio'), {
          status: 200,
          headers: { 'Content-Type': 'audio/amr' },
        });
      }
      if (String(url).includes('https://asr.example.com/asr/oneshot')) {
        assert.equal(init.method, 'POST');
        return new Response(JSON.stringify({ code: 200, data: { text: '帮我整理今天的会议纪要' } }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (String(url).includes('/cgi-bin/message/custom/send')) {
        return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      throw new Error(`unexpected wechat url: ${url}`);
    },
  });

  const originalRandomUuid = crypto.randomUUID;
  crypto.randomUUID = () => 'req-voice-fallback';
  try {
    const result = await service.handleInboundMessage(
      inboundXml({
        msgType: 'voice',
        content: '',
        extraFields: { MediaId: 'media-1', Format: 'amr' },
      }),
      {
        timestamp,
        nonce,
        signature: signatureFor(token, timestamp, nonce),
      },
    );

    assert.equal(result.status, 200);
    assert.doesNotMatch(result.body, /未识别到语音文字/);
    await result.task;
    assert.equal(replyCalled, true);
    assert.match(prompts[0], /帮我整理今天的会议纪要/);
  } finally {
    crypto.randomUUID = originalRandomUuid;
  }
});

test('wechat mp service persists image and routes image url into agent prompt', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const testUserId = 'test-user-image';
  const prompts = [];
  const detailCalls = [];
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      mediaPublicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
      progressDelayMs: 0,
      maxImageBytes: 1024 * 1024,
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: testUserId, status: 'active', nickname: '毕升' };
      },
      async getWechatAgentRoute() {
        return { agentSessionId: 'session-1' };
      },
      async clearWechatAgentRoute() {},
      async canUseChat() {
        return { ok: true };
      },
      async resolveWorkingDir() {
        return '/tmp/user-1';
      },
      async getAgentSessionPolicy() {
        return { enableContextMemory: false, extensionOverrides: [], unrestricted: true };
      },
      async getUserPublishLayout() {
        return { displayName: 'John', username: 'john', slug: 'john', constraints: null };
      },
      async registerAgentSession() {},
      async upsertWechatAgentRoute() {},
      async billSessionUsage() {},
      async recordWechatMpMessage() {
        return { inserted: true };
      },
      async finishWechatMpMessage() {},
      async insertWechatMpMessageDetail(input) {
        detailCalls.push(input);
      },
    },
    sessionApiFetch: async (sessionId, pathname, init = {}) => {
      assert.equal(sessionId, 'session-1');
      if (pathname === '/sessions/session-1/events') {
        return new Response(
          [
            'data: {"type":"Message","request_id":"req-image","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"请告诉我希望怎么处理这张图片。"}]}}\n\n',
            'data: {"type":"Finish","request_id":"req-image","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
          ].join(''),
          { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
        );
      }
      if (pathname === '/sessions/session-1/reply') {
        const body = JSON.parse(init.body);
        prompts.push(body.user_message.content[0].text);
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      throw new Error(`unexpected api path: ${pathname}`);
    },
    wechatFetch: async (url, init = {}) => {
      if (String(url).includes('/cgi-bin/stable_token')) {
        return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (String(url).includes('/cgi-bin/media/get')) {
        return new Response(Buffer.from([0x89, 0x50, 0x4e, 0x47]), {
          status: 200,
          headers: { 'Content-Type': 'image/png' },
        });
      }
      if (String(url).includes('/cgi-bin/message/custom/send')) {
        return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      throw new Error(`unexpected wechat url: ${url}`);
    },
  });

  const originalRandomUuid = crypto.randomUUID;
  crypto.randomUUID = () => 'req-image';
  try {
    const result = await service.handleInboundMessage(
      inboundXml({
        msgType: 'image',
        content: '',
        extraFields: { MediaId: 'media-1', PicUrl: 'https://wx.example.com/image.png' },
      }),
      {
        timestamp,
        nonce,
        signature: signatureFor(token, timestamp, nonce),
      },
    );
    assert.equal(result.status, 200);
    assert.match(result.body, /<Content>/);
    await result.task;
  } finally {
    crypto.randomUUID = originalRandomUuid;
    fs.rmSync(`/Users/john/PycharmProjects/test/test-memind/MindSpace/${testUserId}`, {
      recursive: true,
      force: true,
    });
  }

  assert.equal(prompts.length, 1);
  assert.match(prompts[0], /【微信服务号图片消息】/);
  assert.match(
    prompts[0],
    /\[图片1\]: https:\/\/example\.com\/MindSpace\/test-user-image\/public\/wechat-mp\//,
  );
  assert.equal(detailCalls.length, 1);
  assert.match(detailCalls[0].mediaPublicUrl, /\/wechat-mp\//);
});

test('wechat mp service accepts full voice xml payload and routes recognition text into agent', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const prompts = [];
  const metadataCalls = [];
  const wechatCalls = [];
  const service = createBoundWechatService({
    token,
    userAuth: {
      async insertWechatMpMessageDetail(input) {
        metadataCalls.push(input);
      },
    },
    sessionApiFetch: async (sessionId, pathname, init = {}) => {
      assert.equal(sessionId, 'session-1');
      if (pathname === '/sessions/session-1/events') {
        return new Response(
          [
            'data: {"type":"Message","request_id":"req-voice","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"我收到你的语音了。"}]}}\n\n',
            'data: {"type":"Finish","request_id":"req-voice","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
          ].join(''),
          { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
        );
      }
      if (pathname === '/sessions/session-1/reply') {
        const body = JSON.parse(init.body);
        prompts.push(body.user_message.content[0].text);
        metadataCalls.push(body.user_message.metadata);
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      throw new Error(`unexpected api path: ${pathname}`);
    },
    wechatFetch: async (url, init = {}) => {
      wechatCalls.push([url, init.method ?? 'GET', init.body ?? null]);
      if (String(url).includes('/cgi-bin/stable_token')) {
        return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (String(url).includes('/cgi-bin/message/custom/send')) {
        return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      throw new Error(`unexpected wechat url: ${url}`);
    },
  });

  const originalRandomUuid = crypto.randomUUID;
  crypto.randomUUID = () => 'req-voice';
  try {
    const result = await service.handleInboundMessage(
      inboundXml({
        msgType: 'voice',
        content: '',
        extraFields: {
          MediaId: 'voice-media-1',
          Format: 'amr',
          Recognition: '帮我整理今天的会议纪要',
        },
      }),
      {
        timestamp,
        nonce,
        signature: signatureFor(token, timestamp, nonce),
      },
    );
    assert.equal(result.status, 200);
    assert.match(result.body, /<Content>/);
    await result.task;
  } finally {
    crypto.randomUUID = originalRandomUuid;
  }

  assert.equal(prompts.length, 1);
  assert.match(prompts[0], /【微信服务号语音消息】/);
  assert.match(prompts[0], /用户语音识别文本：帮我整理今天的会议纪要/);
  assert.equal(metadataCalls.some((item) => item?.msgType === 'voice'), true);
  assert.equal(
    wechatCalls.some(([, , body]) => String(body ?? '').includes('我收到你的语音了')),
    true,
  );
});

test('wechat mp service accepts full location xml payload and routes structured location prompt', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const prompts = [];
  const detailCalls = [];
  const service = createBoundWechatService({
    token,
    userAuth: {
      async insertWechatMpMessageDetail(input) {
        detailCalls.push(input);
      },
    },
    sessionApiFetch: async (sessionId, pathname, init = {}) => {
      assert.equal(sessionId, 'session-1');
      if (pathname === '/sessions/session-1/events') {
        return new Response(
          [
            'data: {"type":"Message","request_id":"req-location","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"你附近有几家咖啡馆。"}]}}\n\n',
            'data: {"type":"Finish","request_id":"req-location","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
          ].join(''),
          { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
        );
      }
      if (pathname === '/sessions/session-1/reply') {
        const body = JSON.parse(init.body);
        prompts.push(body.user_message.content[0].text);
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      throw new Error(`unexpected api path: ${pathname}`);
    },
    wechatFetch: async (url) => {
      if (String(url).includes('/cgi-bin/stable_token')) {
        return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (String(url).includes('/cgi-bin/message/custom/send')) {
        return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      throw new Error(`unexpected wechat url: ${url}`);
    },
  });

  const originalRandomUuid = crypto.randomUUID;
  crypto.randomUUID = () => 'req-location';
  try {
    const result = await service.handleInboundMessage(
      inboundXml({
        msgType: 'location',
        content: '',
        extraFields: {
          Location_X: '31.2304',
          Location_Y: '121.4737',
          Scale: '15',
          Label: '上海市黄浦区人民广场',
        },
      }),
      {
        timestamp,
        nonce,
        signature: signatureFor(token, timestamp, nonce),
      },
    );
    assert.equal(result.status, 200);
    assert.match(result.body, /<Content>/);
    await result.task;
  } finally {
    crypto.randomUUID = originalRandomUuid;
  }

  assert.equal(prompts.length, 1);
  assert.match(prompts[0], /【微信服务号位置消息】/);
  assert.match(prompts[0], /地址：上海市黄浦区人民广场/);
  assert.match(prompts[0], /纬度：31.2304/);
  assert.match(prompts[0], /经度：121.4737/);
  assert.equal(detailCalls.length, 1);
  assert.equal(detailCalls[0].locationLabel, '上海市黄浦区人民广场');
});

test('wechat mp service accepts full link xml payload and routes structured link prompt', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const prompts = [];
  const detailCalls = [];
  const service = createBoundWechatService({
    token,
    userAuth: {
      async insertWechatMpMessageDetail(input) {
        detailCalls.push(input);
      },
    },
    sessionApiFetch: async (sessionId, pathname, init = {}) => {
      assert.equal(sessionId, 'session-1');
      if (pathname === '/sessions/session-1/events') {
        return new Response(
          [
            'data: {"type":"Message","request_id":"req-link","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"这个链接看起来是在介绍 TKMind 的新功能。"}]}}\n\n',
            'data: {"type":"Finish","request_id":"req-link","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
          ].join(''),
          { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
        );
      }
      if (pathname === '/sessions/session-1/reply') {
        const body = JSON.parse(init.body);
        prompts.push(body.user_message.content[0].text);
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
        return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
      }
      throw new Error(`unexpected api path: ${pathname}`);
    },
    wechatFetch: async (url) => {
      if (String(url).includes('/cgi-bin/stable_token')) {
        return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (String(url).includes('/cgi-bin/message/custom/send')) {
        return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      throw new Error(`unexpected wechat url: ${url}`);
    },
  });

  const originalRandomUuid = crypto.randomUUID;
  crypto.randomUUID = () => 'req-link';
  try {
    const result = await service.handleInboundMessage(
      inboundXml({
        msgType: 'link',
        content: '',
        extraFields: {
          Title: 'TKMind 发布说明',
          Description: '一篇关于多模态服务号入口的介绍',
          Url: 'https://example.com/articles/wechat-multimodal',
        },
      }),
      {
        timestamp,
        nonce,
        signature: signatureFor(token, timestamp, nonce),
      },
    );
    assert.equal(result.status, 200);
    assert.match(result.body, /<Content>/);
    await result.task;
  } finally {
    crypto.randomUUID = originalRandomUuid;
  }

  assert.equal(prompts.length, 1);
  assert.match(prompts[0], /【微信服务号链接消息】/);
  assert.match(prompts[0], /标题：TKMind 发布说明/);
  assert.match(prompts[0], /描述：一篇关于多模态服务号入口的介绍/);
  assert.match(prompts[0], /URL：https:\/\/example\.com\/articles\/wechat-multimodal/);
  assert.equal(detailCalls.length, 1);
  assert.equal(detailCalls[0].linkTitle, 'TKMind 发布说明');
});

test('wechat mp service records location events without routing to agent', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const detailCalls = [];
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
    },
    userAuth: {
      async insertWechatMpMessageDetail(input) {
        detailCalls.push(input);
      },
    },
    apiFetch: async () => {
      throw new Error('should not call agent api');
    },
  });

  const result = await service.handleInboundMessage(
    inboundXml({
      msgType: 'event',
      content: '',
      event: 'LOCATION',
      extraFields: { Latitude: 31.2, Longitude: 121.5, Precision: 15 },
    }),
    {
      timestamp,
      nonce,
      signature: signatureFor(token, timestamp, nonce),
    },
  );

  assert.equal(result.status, 200);
  assert.equal(result.body, 'success');
  assert.equal(detailCalls.length, 1);
  assert.equal(detailCalls[0].msgType, 'event');
});

test('wechat mp service returns status text for question mark probes', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      statusText: '我在这边',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active', nickname: '毕升' };
      },
    },
    apiFetch: async () => new Response('{}', { status: 200 }),
  });

  const result = await service.handleInboundMessage(inboundXml({ content: '?' }), {
    timestamp,
    nonce,
    signature: signatureFor(token, timestamp, nonce),
  });
  assert.equal(result.status, 200);
  assert.match(result.body, /我在这边/);
  assert.match(result.body, /毕升/);
});

test('wechat mp service greets bound users with stored wechat nickname', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  let replyCalled = false;
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active', nickname: '毕升' };
      },
    },
    apiFetch: async (pathname) => {
      if (pathname === '/sessions/session-1/reply') replyCalled = true;
      return new Response('{}', { status: 200 });
    },
  });

  const result = await service.handleInboundMessage(inboundXml({ content: '你好' }), {
    timestamp,
    nonce,
    signature: signatureFor(token, timestamp, nonce),
  });
  assert.equal(result.status, 200);
  assert.match(result.body, /你好，毕升/);
  assert.equal(replyCalled, false);
});

test('wechat mp service replies to connectivity test without agent', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  let replyCalled = false;
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active', nickname: '毕升' };
      },
    },
    apiFetch: async (pathname) => {
      if (pathname.includes('/reply')) replyCalled = true;
      return new Response('{}', { status: 200 });
    },
  });

  const result = await service.handleInboundMessage(inboundXml({ content: '测试999' }), {
    timestamp,
    nonce,
    signature: signatureFor(token, timestamp, nonce),
  });
  assert.equal(result.status, 200);
  assert.match(result.body, /公众号消息通道正常/);
  assert.equal(replyCalled, false);
});

test('wechat mp service ignores duplicated message ids', async () => {
  const token = 'token';
  const timestamp = '1710000000';
  const nonce = 'nonce';
  let replyCalled = false;
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token,
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
    },
    userAuth: {
      async findWechatUserByOpenid() {
        return { userId: 'user-1', status: 'active' };
      },
      async recordWechatMpMessage() {
        return { inserted: false };
      },
    },
    apiFetch: async (pathname) => {
      if (pathname === '/sessions/session-1/reply') replyCalled = true;
      return new Response('{}', { status: 200 });
    },
  });

  const result = await service.handleInboundMessage(inboundXml({ content: '帮我继续处理' }), {
    timestamp,
    nonce,
    signature: signatureFor(token, timestamp, nonce),
  });
  assert.equal(result.status, 200);
  assert.match(result.body, /上一条如果还没完成/);
  assert.equal(replyCalled, false);
});

test('wechat mp service exposes route status and can recreate route for bound user', async () => {
  const calls = [];
  let routeCleared = false;
  let routeUpserted = false;
  const service = createWechatMpService({
    config: {
      enabled: true,
      appId: 'wx123',
      appSecret: 'secret',
      token: 'token',
      publicBaseUrl: 'https://example.com',
      bindPath: '/auth/wechat/authorize?intent=login',
      ackText: 'ack',
      unsupportedText: 'unsupported',
      unboundTextPrefix: '请先绑定',
    },
    userAuth: {
      async getWechatOpenidForUser() {
        return 'openid-1';
      },
      async getWechatAgentRoute(_appId, _openid) {
        if (routeCleared && !routeUpserted) return null;
        return {
          agentSessionId: routeUpserted ? 'session-new' : 'session-old',
          status: 'active',
          updatedAt: 1710000000000,
        };
      },
      async clearWechatAgentRoute() {
        routeCleared = true;
        calls.push('cleared');
      },
      async canUseChat() {
        return { ok: true };
      },
      async resolveWorkingDir() {
        return '/tmp/user-1';
      },
      async getAgentSessionPolicy() {
        return { enableContextMemory: false, extensionOverrides: [], unrestricted: true };
      },
      async getUserPublishLayout() {
        return { displayName: 'John', username: 'john', slug: 'john', constraints: null };
      },
      async registerAgentSession(_userId, sessionId) {
        calls.push(`register:${sessionId}`);
      },
      async upsertWechatAgentRoute() {
        routeUpserted = true;
        calls.push('upserted');
      },
    },
    apiFetch: async (pathname) => {
      if (pathname === '/agent/start') {
        return new Response(JSON.stringify({ id: 'session-new' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      throw new Error(`unexpected fallback api path: ${pathname}`);
    },
    sessionApiFetch: async (_sessionId, pathname) => {
      if (pathname === '/sessions/session-new') {
        return new Response(JSON.stringify({ working_dir: '/tmp/user-1' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (pathname === '/sessions/session-new/extensions') {
        return new Response(JSON.stringify({ extensions: [] }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      if (
        pathname === '/agent/restart'
      ) {
        return new Response('{}', {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        });
      }
      throw new Error(`unexpected api path: ${pathname}`);
    },
  });

  const status = await service.getRouteStatusForUser('user-1');
  assert.deepEqual(status, {
    enabled: true,
    bound: true,
    appId: 'wx123',
    openid: 'openid-1',
    agentSessionId: 'session-old',
    routeStatus: 'active',
    updatedAt: 1710000000000,
  });

  const recreated = await service.recreateRouteForUser('user-1');
  assert.equal(recreated.ok, true);
  assert.equal(recreated.route.agentSessionId, 'session-new');
  assert.equal(calls.includes('cleared'), true);
  assert.equal(calls.some((item) => item === 'register:session-new'), false);
  assert.equal(calls.includes('upserted'), true);
});
