import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { fetch as undiciFetch } from 'undici';
import { mergeMessageContent } from './message-stream.mjs';
import { reconcileAgentSession } from './session-reconcile.mjs';
import { isScheduleIntent, parseScheduleIntent, shouldUseScheduleAssistant } from './schedule-intent.mjs';
import { PUBLISH_ROOT_DIR } from './user-publish.mjs';
import { downloadTemporaryMedia, persistWechatImage } from './wechat-media.mjs';
import { buildAckText } from './wechat/ack/ack-provider.mjs';

const DEFAULT_WECHAT_TOKEN_URL = 'https://api.weixin.qq.com/cgi-bin/stable_token';
const DEFAULT_WECHAT_CUSTOMER_SERVICE_URL =
  'https://api.weixin.qq.com/cgi-bin/message/custom/send';
const DEFAULT_WECHAT_JSAPI_TICKET_URL = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket';
const DEFAULT_ASR_TARGET = process.env.H5_ASR_TARGET ?? 'https://asr.tkmind.cn';
const DEFAULT_ACK_TEXT = '已收到，正在处理，完成后发到这里。';
const DEFAULT_PROGRESS_TEXT = '还在处理，请稍等片刻。';
const DEFAULT_STATUS_TEXT =
  '我在这边。上一条如果还没完成，我会继续把结果发给你；你也可以直接补一句要求。';
const DEFAULT_UNSUPPORTED_TEXT = '当前先支持文字消息，你可以直接发文字给我。';
const DEFAULT_UNBOUND_TEXT = '先点这里完成绑定，再继续和专属 Agent 对话：';
const DEFAULT_PROGRESS_DELAY_MS = 8000;
const PUBLIC_HTML_LINK_PATTERN =
  /https?:\/\/[^\s<>"')\]]+\/MindSpace\/([0-9a-f-]{36}|[a-z0-9._-]+)\/public\/([^\s<>"')\]]+\.html)/gi;

function parseXmlField(xml, field) {
  const cdataMatch = xml.match(new RegExp(`<${field}><!\\[CDATA\\[([\\s\\S]*?)\\]\\]><\\/${field}>`));
  if (cdataMatch) return cdataMatch[1];
  const plainMatch = xml.match(new RegExp(`<${field}>([\\s\\S]*?)<\\/${field}>`));
  return plainMatch ? plainMatch[1].trim() : '';
}

function parseWechatMessage(xml) {
  return {
    toUserName: parseXmlField(xml, 'ToUserName'),
    fromUserName: parseXmlField(xml, 'FromUserName'),
    createTime: Number(parseXmlField(xml, 'CreateTime') || 0),
    msgType: parseXmlField(xml, 'MsgType').toLowerCase(),
    content: parseXmlField(xml, 'Content'),
    msgId: parseXmlField(xml, 'MsgId'),
    mediaId: parseXmlField(xml, 'MediaId'),
    format: parseXmlField(xml, 'Format'),
    recognition: parseXmlField(xml, 'Recognition'),
    picUrl: parseXmlField(xml, 'PicUrl'),
    locationX: parseXmlField(xml, 'Location_X'),
    locationY: parseXmlField(xml, 'Location_Y'),
    scale: parseXmlField(xml, 'Scale'),
    label: parseXmlField(xml, 'Label'),
    title: parseXmlField(xml, 'Title'),
    description: parseXmlField(xml, 'Description'),
    url: parseXmlField(xml, 'Url'),
    thumbMediaId: parseXmlField(xml, 'ThumbMediaId'),
    event: parseXmlField(xml, 'Event').toLowerCase(),
    eventKey: parseXmlField(xml, 'EventKey'),
    latitude: parseXmlField(xml, 'Latitude'),
    longitude: parseXmlField(xml, 'Longitude'),
    precision: parseXmlField(xml, 'Precision'),
    encrypt: parseXmlField(xml, 'Encrypt'),
    rawXml: xml,
  };
}

function readJsonResponse(response) {
  return response.text().then((text) => {
    if (!response.ok) {
      throw new Error(text || `upstream ${response.status}`);
    }
    return text ? JSON.parse(text) : null;
  });
}

async function readJsonBody(response) {
  const text = await response.text();
  if (!text) return { payload: null, text: '' };
  try {
    return { payload: JSON.parse(text), text };
  } catch {
    return { payload: null, text };
  }
}

function sanitizeAsrMessage(message) {
  if (!message || typeof message !== 'string') return '识别失败，请重试';
  const trimmed = message.trim();
  if (!trimmed) return '识别失败，请重试';
  if (/Failed to load audio|ffmpeg|Invalid data found when processing input|moov atom not found/i.test(trimmed)) {
    return '音频格式无法识别，请重新录制';
  }
  if (/timeout|timed out/i.test(trimmed)) return '识别超时，请重试';
  if (trimmed.length > 160) return `${trimmed.slice(0, 160)}…`;
  return trimmed;
}

function guessVoiceMimeType(format = '', contentType = '') {
  const normalizedContentType = String(contentType ?? '')
    .split(';')[0]
    .trim()
    .toLowerCase();
  if (normalizedContentType) return normalizedContentType;
  const normalizedFormat = String(format ?? '').trim().toLowerCase();
  if (normalizedFormat === 'amr') return 'audio/amr';
  if (normalizedFormat === 'wav') return 'audio/wav';
  if (normalizedFormat === 'mp3') return 'audio/mpeg';
  if (normalizedFormat === 'm4a') return 'audio/mp4';
  if (normalizedFormat === 'ogg') return 'audio/ogg';
  return 'application/octet-stream';
}

function deriveWechatEndpointFromUrl(sourceUrl, pathname) {
  if (!sourceUrl) return '';
  try {
    const url = new URL(sourceUrl);
    url.pathname = pathname;
    url.search = '';
    return url.toString();
  } catch {
    return '';
  }
}

function createUserMessage(text, metadata = {}) {
  return {
    id: crypto.randomUUID(),
    role: 'user',
    created: Math.floor(Date.now() / 1000),
    content: [{ type: 'text', text }],
    metadata: {
      userVisible: true,
      agentVisible: true,
      ...metadata,
    },
  };
}

function messageVisibleText(message) {
  if (!message?.content) return '';
  return message.content
    .filter((item) => item.type === 'text' && typeof item.text === 'string')
    .map((item) => item.text)
    .join('');
}

function pushMessage(messages, incoming) {
  const last = messages[messages.length - 1];
  if (last?.id && incoming?.id && last.id === incoming.id) {
    return [
      ...messages.slice(0, -1),
      {
        ...last,
        content: mergeMessageContent(last.content, incoming.content),
      },
    ];
  }
  return [...messages, incoming];
}

async function executeSessionReply(apiFetch, sessionId, requestId, prompt, metadata = {}) {
  const eventsResponse = await apiFetch(`/sessions/${sessionId}/events`, {
    method: 'GET',
    headers: { Accept: 'text/event-stream' },
  });
  if (!eventsResponse.ok || !eventsResponse.body) {
    const text = await eventsResponse.text().catch(() => '');
    throw new Error(text || '无法建立公众号消息事件流');
  }

  const replyResponse = await apiFetch(`/sessions/${sessionId}/reply`, {
    method: 'POST',
    body: JSON.stringify({
      request_id: requestId,
      user_message: createUserMessage(prompt, metadata),
    }),
  });
  if (!replyResponse.ok) {
    const text = await replyResponse.text().catch(() => '');
    throw new Error(text || 'Agent reply 请求失败');
  }
  replyResponse.body?.cancel().catch?.(() => {});

  const reader = eventsResponse.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';
  let messages = [];

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const frames = buffer.split('\n\n');
    buffer = frames.pop() ?? '';
    for (const frame of frames) {
      let data = '';
      for (const line of frame.split('\n')) {
        if (line.startsWith('data:')) data += line.slice(5).trim();
      }
      if (!data) continue;
      let event;
      try {
        event = JSON.parse(data);
      } catch {
        continue;
      }
      const routingId = event.chat_request_id ?? event.request_id;
      if (routingId && routingId !== requestId) continue;

      if (event.type === 'Message' && event.message?.metadata?.userVisible) {
        const hasActionRequired = event.message.content?.some((item) => item.type === 'actionRequired');
        if (hasActionRequired) {
          throw new Error('当前回复需要人工确认，公众号通道暂不支持');
        }
        messages = pushMessage(messages, event.message);
      } else if (event.type === 'UpdateConversation') {
        messages = (event.conversation ?? []).filter((item) => item.metadata?.userVisible);
      } else if (event.type === 'Error') {
        throw new Error(event.error || '任务执行失败');
      } else if (event.type === 'Finish') {
        const assistant = [...messages].reverse().find((item) => item.role === 'assistant');
        return {
          text: messageVisibleText(assistant),
          tokenState: event.token_state ?? null,
        };
      }
    }
  }

  throw new Error('公众号消息事件流提前结束');
}

const WECHAT_CUSTOMER_TEXT_MAX_BYTES = 2048;

function splitWechatText(text, maxBytes = WECHAT_CUSTOMER_TEXT_MAX_BYTES) {
  const normalized = String(text ?? '').trim();
  if (!normalized) return ['我先收到了，但这次没有生成可发送的文本结果。'];

  const chunks = [];
  let offset = 0;
  while (offset < normalized.length) {
    let byteCount = 0;
    let end = offset;
    while (end < normalized.length) {
      const charBytes = Buffer.byteLength(normalized[end], 'utf8');
      if (byteCount + charBytes > maxBytes) break;
      byteCount += charBytes;
      end += 1;
    }
    if (end === offset) break;
    chunks.push(normalized.slice(offset, end));
    offset = end;
  }
  return chunks.length > 0 ? chunks : ['我先收到了，但这次没有生成可发送的文本结果。'];
}

export { splitWechatText, WECHAT_CUSTOMER_TEXT_MAX_BYTES };

function stripInternalWechatUsername(text) {
  return String(text ?? '').replace(/^\s*wx_[a-z0-9_]{4,64}\s*[,，、:：]\s*/i, '');
}

function replaceLeadingInternalWechatUsername(text, user) {
  const addressName = normalizeWechatName(user?.nickname) || normalizeWechatName(user?.displayName);
  if (!addressName) return stripInternalWechatUsername(text);
  return String(text ?? '').replace(
    /(^|[\s,，、:：])wx_[a-z0-9_]{4,64}\s*([,，、:：])\s*/gi,
    (_match, prefix = '', punctuation = '，') => `${prefix}${addressName}${punctuation}`,
  );
}

function convertMarkdownLinks(text) {
  return String(text ?? '').replace(/\[([^\]\n]{1,160})\]\((https?:\/\/[^\s)]+)\)/g, (_match, label, url) => {
    const cleanLabel = String(label ?? '').trim();
    const cleanUrl = String(url ?? '').trim();
    return cleanLabel ? `${cleanLabel}\n${cleanUrl}` : cleanUrl;
  });
}

function splitMarkdownTableRow(line) {
  const trimmed = String(line ?? '').trim();
  if (!trimmed.includes('|')) return null;
  return trimmed
    .replace(/^\|/, '')
    .replace(/\|$/, '')
    .split('|')
    .map((cell) => cell.trim());
}

function isMarkdownTableSeparator(line) {
  const cells = splitMarkdownTableRow(line);
  return Boolean(cells?.length) && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
}

function convertMarkdownTables(text) {
  const lines = String(text ?? '').split('\n');
  const output = [];
  for (let index = 0; index < lines.length; index += 1) {
    const headers = splitMarkdownTableRow(lines[index]);
    if (!headers || !isMarkdownTableSeparator(lines[index + 1])) {
      output.push(lines[index]);
      continue;
    }

    const readableRows = [];
    index += 2;
    while (index < lines.length) {
      const cells = splitMarkdownTableRow(lines[index]);
      if (!cells) break;
      const parts = cells
        .map((cell, cellIndex) => {
          const header = headers[cellIndex] || `列${cellIndex + 1}`;
          return cell ? `${header}：${cell}` : '';
        })
        .filter(Boolean);
      if (parts.length) readableRows.push(`- ${parts.join('；')}`);
      index += 1;
    }
    index -= 1;
    output.push(...readableRows);
  }
  return output.join('\n');
}

function stripMarkdownEmphasis(text) {
  return String(text ?? '')
    .replace(/\*\*([^*\n]+)\*\*/g, '$1')
    .replace(/__([^_\n]+)__/g, '$1')
    .replace(/`([^`\n]+)`/g, '$1')
    .replace(/\*/g, '')
    .replace(/__/g, '');
}

function formatWechatOutboundText(text, user = null) {
  return replaceLeadingInternalWechatUsername(
    stripMarkdownEmphasis(convertMarkdownTables(convertMarkdownLinks(text)))
      .replace(/^\s*#{1,6}\s+/gm, '')
      .replace(/^\s*---+\s*$/gm, '')
      .replace(/\n{3,}/g, '\n\n'),
    user,
  );
}

function defaultPublicHtmlLinkExists(urlText) {
  let url;
  try {
    url = new URL(urlText);
  } catch {
    return true;
  }
  const parts = url.pathname.split('/').filter(Boolean).map((part) => {
    try {
      return decodeURIComponent(part);
    } catch {
      return part;
    }
  });
  if (parts.length < 4 || parts[0] !== PUBLISH_ROOT_DIR || parts[2] !== 'public') return true;
  const owner = parts[1];
  const rest = parts.slice(3);
  if (!owner || rest.some((part) => !part || part === '.' || part === '..')) return false;
  const root = path.resolve(process.cwd(), PUBLISH_ROOT_DIR, owner, 'public');
  const target = path.resolve(root, ...rest);
  if (target !== root && !target.startsWith(`${root}${path.sep}`)) return false;
  return fs.existsSync(target) && fs.statSync(target).isFile();
}

export async function guardMissingPublicHtmlLinks(
  text,
  { linkExists = defaultPublicHtmlLinkExists } = {},
) {
  const value = String(text ?? '');
  const replacements = [];
  for (const match of value.matchAll(PUBLIC_HTML_LINK_PATTERN)) {
    const url = match[0];
    let exists = true;
    try {
      exists = await linkExists(url);
    } catch {
      exists = false;
    }
    if (!exists) {
      const filename = match[2].split('/').pop() ?? '页面';
      replacements.push([
        url,
        `（页面生成未完成，已阻止发送失效链接：${filename}。请重新生成页面后再打开。）`,
      ]);
    }
  }
  return replacements.reduce((next, [from, to]) => next.replaceAll(from, to), value);
}

function isQuestionStatusProbe(text) {
  return /^[?？]+$/.test(String(text ?? '').trim());
}

function isSimpleGreeting(text) {
  const normalized = String(text ?? '')
    .trim()
    .replace(/[!！。.\s]+$/g, '')
    .toLowerCase();
  return /^(你好|您好|在吗|在不在|嗨|hi|hello|hey)$/.test(normalized);
}

function isConnectivityTest(text) {
  const normalized = String(text ?? '')
    .trim()
    .replace(/[!！。.\s]+$/g, '');
  return /^(测试\s*\d*|test\s*\d*)$/i.test(normalized);
}

function isTopicResetIntent(text) {
  const normalized = String(text ?? '').trim();
  return (
    /^(换(个)?话题|新问题|忽略之前|不管之前|重新开始|reset)$/i.test(normalized) ||
    /忽略.*之前|不要管.*之前|别管.*之前/.test(normalized)
  );
}

function normalizeNumber(value) {
  if (value === '' || value === null || value === undefined) return null;
  const num = Number(value);
  return Number.isFinite(num) ? num : null;
}

function normalizeWechatInboundIntent(inbound) {
  const msgType = String(inbound?.msgType ?? '').toLowerCase();
  const base = {
    appId: '',
    openid: String(inbound?.fromUserName ?? '').trim(),
    msgId: String(inbound?.msgId ?? '').trim(),
    msgType,
    displayText: '',
    agentText: '',
    raw: { ...inbound },
  };

  if (msgType === 'text') {
    const content = String(inbound?.content ?? '').trim();
    return {
      ...base,
      displayText: content,
      agentText: content,
    };
  }

  if (msgType === 'voice') {
    const recognition = String(inbound?.recognition ?? '').trim();
    return {
      ...base,
      displayText: recognition ? `语音：${recognition}` : '语音消息',
      agentText: recognition,
      media: {
        mediaId: inbound?.mediaId || '',
        format: inbound?.format || '',
      },
    };
  }

  if (msgType === 'image') {
    return {
      ...base,
      displayText: '收到图片，请描述你想让我怎么处理',
      agentText: '',
      media: {
        mediaId: inbound?.mediaId || '',
        picUrl: inbound?.picUrl || '',
      },
    };
  }

  if (msgType === 'location') {
    const latitude = normalizeNumber(inbound?.locationX);
    const longitude = normalizeNumber(inbound?.locationY);
    const scale = normalizeNumber(inbound?.scale);
    const label = String(inbound?.label ?? '').trim();
    const fragments = [
      label ? `用户发送了位置：${label}` : '用户发送了位置',
      latitude !== null ? `纬度：${latitude}` : '',
      longitude !== null ? `经度：${longitude}` : '',
      scale !== null ? `缩放：${scale}` : '',
    ].filter(Boolean);
    return {
      ...base,
      displayText: fragments.join('；'),
      agentText: fragments.join('；'),
      location: {
        latitude,
        longitude,
        scale,
        label,
      },
    };
  }

  if (msgType === 'link') {
    const title = String(inbound?.title ?? '').trim();
    const description = String(inbound?.description ?? '').trim();
    const url = String(inbound?.url ?? '').trim();
    const fragments = [
      '用户分享了链接',
      title ? `标题：${title}` : '',
      description ? `描述：${description}` : '',
      url ? `URL：${url}` : '',
    ].filter(Boolean);
    return {
      ...base,
      displayText: fragments.join('；'),
      agentText: fragments.join('；'),
      link: { title, description, url },
    };
  }

  if (msgType === 'video' || msgType === 'shortvideo') {
    return {
      ...base,
      displayText: msgType === 'shortvideo' ? '收到小视频' : '收到视频',
      agentText: '',
      media: {
        mediaId: inbound?.mediaId || '',
        thumbMediaId: inbound?.thumbMediaId || '',
      },
    };
  }

  if (msgType === 'event') {
    return {
      ...base,
      displayText: inbound?.event ? `事件：${inbound.event}` : '事件消息',
      agentText: '',
      location:
        inbound?.event === 'location'
          ? {
              latitude: normalizeNumber(inbound?.latitude),
              longitude: normalizeNumber(inbound?.longitude),
              precision: normalizeNumber(inbound?.precision),
            }
          : undefined,
    };
  }

  return base;
}

function buildWechatAgentPrompt(intent) {
  const msgType = String(intent?.msgType ?? 'text');
  const scheduleAssistantHint = shouldUseScheduleAssistant(intent?.agentText ?? intent?.content)
    ? [
        '【日程技能要求】这条消息涉及待办、提醒或日程。',
        '开始前先加载 `schedule-assistant` skill，并严格按 skill 里的边界执行。',
        '只有在 `schedule_create_item` / `schedule_create_reminder` 等工具成功返回后，才能告诉用户“已经设置好了”。',
        intent?.msgId ? `调用 schedule_create_item 时必须传入 sourceMessageId: ${intent.msgId}` : '',
        '',
      ].filter(Boolean).join('\n')
    : '';
  if (msgType === 'voice') {
    return [
      scheduleAssistantHint,
      '【微信服务号语音消息】用户通过语音输入，以下是微信识别结果。',
      '',
      `用户语音识别文本：${String(intent?.agentText ?? '').trim()}`,
    ]
      .filter(Boolean)
      .join('\n');
  }
  if (msgType === 'image') {
    return [
      scheduleAssistantHint,
      '【微信服务号图片消息】用户发送了图片。',
      '如用户没有明确要求，请先根据图片内容给出简短理解，并询问下一步。',
      '',
      String(intent?.agentText ?? '').trim(),
    ]
      .filter(Boolean)
      .join('\n');
  }
  if (msgType === 'location') {
    const location = intent?.location ?? {};
    return [
      scheduleAssistantHint,
      '【微信服务号位置消息】用户发送了当前位置。',
      location.label ? `地址：${location.label}` : '',
      location.latitude !== null && location.latitude !== undefined
        ? `纬度：${location.latitude}`
        : '',
      location.longitude !== null && location.longitude !== undefined
        ? `经度：${location.longitude}`
        : '',
      '请结合位置回答用户可能的路线、附近、行程或提醒需求；如果意图不明确，先简短询问。',
    ]
      .filter(Boolean)
      .join('\n');
  }
  if (msgType === 'link') {
    const link = intent?.link ?? {};
    return [
      scheduleAssistantHint,
      '【微信服务号链接消息】用户分享了链接。',
      link.title ? `标题：${link.title}` : '',
      link.description ? `描述：${link.description}` : '',
      link.url ? `URL：${link.url}` : '',
    ]
      .filter(Boolean)
      .join('\n');
  }
  const content = String(intent?.agentText ?? intent?.content ?? '').trim();
  const lines = [];
  if (scheduleAssistantHint) lines.push(scheduleAssistantHint);
  lines.push(
    '【微信服务号新消息】请只回答下面这条用户消息，不要主动延续无关的历史话题。',
    '若用户只是在测试连通性，请一句话确认收到即可，不要展开旧任务。',
    '',
    `用户消息：${content}`,
  );
  return lines.join('\n');
}

function looksLikeScheduleConfirmation(text) {
  const compact = String(text ?? '').replace(/\s+/g, '');
  if (!compact) return false;
  if (/(没有|没能|未能|无法|不能|失败|需要你|请补充|请告诉)/.test(compact)) return false;
  return /(已经|已|现在).{0,12}(设置|安排|记录|加上|添加|创建).{0,12}(待办|提醒|日程|安排|闹钟)|设置好了|已经设置好了|已经加上/.test(
    compact,
  );
}

function buildConnectivityTestReply(user) {
  const name = resolveWechatAddressName(user);
  return name
    ? `${name}，公众号消息通道正常，我收到你的测试了。`
    : '公众号消息通道正常，我收到你的测试了。';
}

function normalizeWechatName(value) {
  const name = String(value ?? '').trim();
  if (!name || /^wx_[a-z0-9_]{4,64}$/i.test(name)) return '';
  return name;
}

function resolveWechatAddressName(user) {
  return normalizeWechatName(user?.nickname) || normalizeWechatName(user?.displayName) || '';
}

function buildGreetingText(user) {
  const name = resolveWechatAddressName(user);
  return name ? `你好，${name}！我在呢，有什么需要？` : '你好！我在呢，有什么需要？';
}

function buildStatusText(user, fallbackText) {
  const name = resolveWechatAddressName(user);
  if (!name) return fallbackText;
  return `我在这边，${name}。上一条如果还没完成，我会继续把结果发给你；你也可以直接补一句要求。`;
}

function successResponse(task = null) {
  return {
    ok: true,
    status: 200,
    contentType: 'text/plain; charset=utf-8',
    body: 'success',
    ...(task ? { task } : {}),
  };
}

export function loadWechatMpConfig(env = process.env) {
  const appId = env.H5_WECHAT_MP_APP_ID?.trim() ?? env.H5_WECHAT_APP_ID?.trim() ?? '';
  const appSecret =
    env.H5_WECHAT_MP_APP_SECRET?.trim() ?? env.H5_WECHAT_APP_SECRET?.trim() ?? '';
  const token = env.H5_WECHAT_MP_TOKEN?.trim() ?? '';
  const publicBaseUrl = env.H5_PUBLIC_BASE_URL?.trim()?.replace(/\/$/, '') ?? '';
  const enabledFlag = env.H5_WECHAT_MP_ENABLED === '1';
  const bindPath = env.H5_WECHAT_MP_BIND_PATH?.trim() || '/auth/wechat/authorize?intent=login';
  return {
    enabled: enabledFlag && Boolean(appId && appSecret && token && publicBaseUrl),
    appId,
    appSecret,
    token,
    publicBaseUrl,
    bindPath,
    ackText: env.H5_WECHAT_MP_ACK_TEXT?.trim() || DEFAULT_ACK_TEXT,
    ackRandomEnabled: env.H5_WECHAT_MP_ACK_RANDOM !== '0',
    ackNicknameEnabled: env.H5_WECHAT_MP_ACK_NICKNAME !== '0',
    progressText: env.H5_WECHAT_MP_PROGRESS_TEXT?.trim() || DEFAULT_PROGRESS_TEXT,
    progressDelayMs: Math.max(
      0,
      Number(env.H5_WECHAT_MP_PROGRESS_DELAY_MS ?? DEFAULT_PROGRESS_DELAY_MS),
    ),
    statusText: env.H5_WECHAT_MP_STATUS_TEXT?.trim() || DEFAULT_STATUS_TEXT,
    unsupportedText: env.H5_WECHAT_MP_UNSUPPORTED_TEXT?.trim() || DEFAULT_UNSUPPORTED_TEXT,
    unboundTextPrefix: env.H5_WECHAT_MP_UNBOUND_TEXT_PREFIX?.trim() || DEFAULT_UNBOUND_TEXT,
    tokenUrl: env.H5_WECHAT_MP_TOKEN_URL?.trim() || DEFAULT_WECHAT_TOKEN_URL,
    customerServiceUrl:
      env.H5_WECHAT_MP_CUSTOMER_SERVICE_URL?.trim() || DEFAULT_WECHAT_CUSTOMER_SERVICE_URL,
    jsapiTicketUrl:
      env.H5_WECHAT_MP_JSAPI_TICKET_URL?.trim() ||
      deriveWechatEndpointFromUrl(env.H5_WECHAT_MP_TOKEN_URL?.trim(), '/cgi-bin/ticket/getticket') ||
      DEFAULT_WECHAT_JSAPI_TICKET_URL,
    mediaPublicBaseUrl:
      env.H5_WECHAT_MP_MEDIA_PUBLIC_BASE_URL?.trim()?.replace(/\/$/, '') || publicBaseUrl,
    maxImageBytes: Math.max(1, Number(env.H5_WECHAT_MP_MAX_IMAGE_BYTES ?? 10 * 1024 * 1024)),
    acceptVoice: env.H5_WECHAT_MP_ACCEPT_VOICE !== '0',
    acceptImage: env.H5_WECHAT_MP_ACCEPT_IMAGE !== '0',
    acceptLocation: env.H5_WECHAT_MP_ACCEPT_LOCATION !== '0',
    acceptLink: env.H5_WECHAT_MP_ACCEPT_LINK !== '0',
    encodingAesKey: env.H5_WECHAT_MP_ENCODING_AES_KEY?.trim() ?? '',
  };
}

function sha1Hex(parts) {
  return crypto
    .createHash('sha1')
    .update([...parts].sort().join(''))
    .digest('hex');
}

export function verifyWechatMpSignature({ token, timestamp, nonce, signature }) {
  return sha1Hex([token, timestamp, nonce]) === String(signature ?? '');
}

export function verifyWechatMpMsgSignature({ token, timestamp, nonce, echoStr, msgSignature }) {
  return sha1Hex([token, timestamp, nonce, echoStr]) === String(msgSignature ?? '');
}

function decodeWechatMpAesKey(encodingAesKey) {
  const key = Buffer.from(`${String(encodingAesKey ?? '').trim()}=`, 'base64');
  if (key.length !== 32) {
    throw new Error('invalid encoding aes key');
  }
  return key;
}

export function decryptWechatMpPayload({ encodingAesKey, appId, encrypted }) {
  const key = decodeWechatMpAesKey(encodingAesKey);
  const iv = key.subarray(0, 16);
  const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
  decipher.setAutoPadding(false);
  const cipherBuf = Buffer.from(String(encrypted ?? ''), 'base64');
  let decoded = Buffer.concat([decipher.update(cipherBuf), decipher.final()]);
  const pad = decoded.at(-1);
  if (!Number.isInteger(pad) || pad < 1 || pad > 32) {
    throw new Error('invalid padding');
  }
  decoded = decoded.subarray(0, decoded.length - pad);
  const content = decoded.subarray(16);
  const msgLen = content.readUInt32BE(0);
  const msg = content.subarray(4, 4 + msgLen).toString('utf8');
  const receivedAppId = content.subarray(4 + msgLen).toString('utf8');
  if (receivedAppId !== String(appId ?? '')) {
    throw new Error('appid mismatch');
  }
  return msg;
}

export function verifyWechatMpUrlChallenge(query = {}, config = {}) {
  const timestamp = String(query.timestamp ?? '');
  const nonce = String(query.nonce ?? '');
  const echostr = String(query.echostr ?? '');
  const encryptType = String(query.encrypt_type ?? '').toLowerCase();

  if (encryptType === 'aes') {
    const msgSignature = String(query.msg_signature ?? '');
    if (!config?.token || !config?.encodingAesKey || !config?.appId) {
      return { ok: false, status: 503, body: 'wechat mp aes mode not configured' };
    }
    if (
      !verifyWechatMpMsgSignature({
        token: config.token,
        timestamp,
        nonce,
        echoStr: echostr,
        msgSignature,
      })
    ) {
      return { ok: false, status: 403, body: 'invalid signature' };
    }
    try {
      const plain = decryptWechatMpPayload({
        encodingAesKey: config.encodingAesKey,
        appId: config.appId,
        encrypted: echostr,
      });
      return { ok: true, status: 200, body: plain };
    } catch {
      return { ok: false, status: 403, body: 'invalid echostr' };
    }
  }

  if (
    !verifyWechatMpSignature({
      token: config?.token,
      timestamp,
      nonce,
      signature: String(query.signature ?? ''),
    })
  ) {
    return { ok: false, status: 403, body: 'invalid signature' };
  }
  return { ok: true, status: 200, body: echostr };
}

export function buildWechatTextReply({
  toUserName,
  fromUserName,
  content,
  createTime = Math.floor(Date.now() / 1000),
}) {
  return [
    '<xml>',
    `<ToUserName><![CDATA[${toUserName ?? ''}]]></ToUserName>`,
    `<FromUserName><![CDATA[${fromUserName ?? ''}]]></FromUserName>`,
    `<CreateTime>${Number(createTime) || Math.floor(Date.now() / 1000)}</CreateTime>`,
    '<MsgType><![CDATA[text]]></MsgType>',
    `<Content><![CDATA[${String(content ?? '')}]]></Content>`,
    '</xml>',
  ].join('');
}

export function createWechatMpService({
  config,
  userAuth,
  apiFetch,
  sessionApiFetch = null,
  scheduleService = null,
  applySessionLlmProvider = null,
  wechatFetch = undiciFetch,
  linkExists = defaultPublicHtmlLinkExists,
  logger = console,
}) {
  if (!config?.enabled) {
    return {
      enabled: false,
    };
  }
  config = {
    ...config,
    tokenUrl: config.tokenUrl || DEFAULT_WECHAT_TOKEN_URL,
    customerServiceUrl: config.customerServiceUrl || DEFAULT_WECHAT_CUSTOMER_SERVICE_URL,
    jsapiTicketUrl: config.jsapiTicketUrl || DEFAULT_WECHAT_JSAPI_TICKET_URL,
    progressText: config.progressText ?? DEFAULT_PROGRESS_TEXT,
    progressDelayMs: Math.max(0, Number(config.progressDelayMs ?? DEFAULT_PROGRESS_DELAY_MS)),
    statusText: config.statusText || DEFAULT_STATUS_TEXT,
    mediaPublicBaseUrl: config.mediaPublicBaseUrl || config.publicBaseUrl,
    maxImageBytes: Math.max(1, Number(config.maxImageBytes ?? 10 * 1024 * 1024)),
    acceptVoice: config.acceptVoice !== false,
    acceptImage: config.acceptImage !== false,
    acceptLocation: config.acceptLocation !== false,
    acceptLink: config.acceptLink !== false,
    asrTarget: config.asrTarget || DEFAULT_ASR_TARGET,
  };

  let accessTokenCache = {
    token: null,
    expiresAt: 0,
  };
  let jsapiTicketCache = {
    ticket: null,
    expiresAt: 0,
  };

  const fetchForSession = (sessionId, pathname, init) =>
    sessionApiFetch ? sessionApiFetch(sessionId, pathname, init) : apiFetch(pathname, init);

  const buildBindUrl = () => {
    if (/^https?:\/\//i.test(config.bindPath)) return config.bindPath;
    const path = config.bindPath.startsWith('/') ? config.bindPath : `/${config.bindPath}`;
    return `${config.publicBaseUrl}${path}`;
  };

  const verifyRequest = (query = {}) =>
    verifyWechatMpSignature({
      token: config.token,
      timestamp: String(query.timestamp ?? ''),
      nonce: String(query.nonce ?? ''),
      signature: String(query.signature ?? ''),
    });

  const verifyUrlChallenge = (query = {}) => verifyWechatMpUrlChallenge(query, config);

  const getStableAccessToken = async () => {
    if (accessTokenCache.token && accessTokenCache.expiresAt > Date.now() + 60_000) {
      return accessTokenCache.token;
    }
    const payload = await readJsonResponse(
      await wechatFetch(config.tokenUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          grant_type: 'client_credential',
          appid: config.appId,
          secret: config.appSecret,
          force_refresh: false,
        }),
      }),
    );
    if (!payload?.access_token) {
      throw new Error(payload?.errmsg || '获取微信 access_token 失败');
    }
    const expiresIn = Number(payload.expires_in ?? 7200);
    accessTokenCache = {
      token: payload.access_token,
      expiresAt: Date.now() + expiresIn * 1000,
    };
    return accessTokenCache.token;
  };

  const getJsapiTicket = async () => {
    if (jsapiTicketCache.ticket && jsapiTicketCache.expiresAt > Date.now() + 60_000) {
      return jsapiTicketCache.ticket;
    }
    const accessToken = await getStableAccessToken();
    const url = new URL(config.jsapiTicketUrl);
    url.searchParams.set('access_token', accessToken);
    url.searchParams.set('type', 'jsapi');
    const payload = await readJsonResponse(
      await wechatFetch(url.toString(), {
        method: 'GET',
        headers: { Accept: 'application/json' },
      }),
    );
    if (Number(payload?.errcode ?? 0) !== 0 || !payload?.ticket) {
      throw new Error(payload?.errmsg || `获取微信 jsapi_ticket 失败 (${payload?.errcode})`);
    }
    const expiresIn = Number(payload.expires_in ?? 7200);
    jsapiTicketCache = {
      ticket: payload.ticket,
      expiresAt: Date.now() + expiresIn * 1000,
    };
    return jsapiTicketCache.ticket;
  };

  const createJsSdkSignature = async (pageUrl) => {
    const normalizedUrl = String(pageUrl ?? '').split('#')[0];
    if (!/^https?:\/\//i.test(normalizedUrl)) {
      throw new Error('签名 URL 必须是完整 http(s) 地址');
    }
    const ticket = await getJsapiTicket();
    const nonceStr = crypto.randomBytes(12).toString('hex');
    const timestamp = Math.floor(Date.now() / 1000);
    const signature = crypto
      .createHash('sha1')
      .update(
        [
          `jsapi_ticket=${ticket}`,
          `noncestr=${nonceStr}`,
          `timestamp=${timestamp}`,
          `url=${normalizedUrl}`,
        ].join('&'),
      )
      .digest('hex');
    return {
      appId: config.appId,
      timestamp,
      nonceStr,
      signature,
      url: normalizedUrl,
      jsApiList: ['startRecord', 'stopRecord', 'onVoiceRecordEnd', 'translateVoice'],
    };
  };

  const sendCustomerServiceText = async (openid, content, user = null) => {
    const formatted = formatWechatOutboundText(content, user);
    const guarded = await guardMissingPublicHtmlLinks(formatted, { linkExists });
    const chunks = splitWechatText(guarded);
    const accessToken = await getStableAccessToken();
    for (const chunk of chunks) {
      const payload = await readJsonResponse(
        await wechatFetch(
          `${config.customerServiceUrl}?access_token=${encodeURIComponent(accessToken)}`,
          {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              touser: openid,
              msgtype: 'text',
              text: {
                content: chunk,
              },
            }),
          },
        ),
      );
      if (Number(payload?.errcode ?? 0) !== 0) {
        const errcode = Number(payload?.errcode ?? 0);
        const errmsg = String(payload?.errmsg ?? '').trim() || 'unknown_error';
        throw new Error(`微信客服消息发送失败 errcode=${errcode} errmsg=${errmsg}`);
      }
    }
  };

  const sendTextToUser = async (userId, content) => {
    const openid = await userAuth.getWechatOpenidForUser(userId, config.appId);
    if (!openid) {
      throw new Error('用户尚未绑定服务号，无法推送提醒');
    }
    await sendCustomerServiceText(openid, content);
  };

  const ensureSessionProvider = async (sessionId) => {
    if (!applySessionLlmProvider || !sessionId) return;
    const applied = await applySessionLlmProvider(sessionId);
    if (applied && applied.ok === false) {
      throw new Error(applied.message || '专属 Agent Provider 未配置');
    }
  };

  const transcribeWechatVoiceMedia = async (mediaId, format) => {
    if (!mediaId) return '';
    const accessToken = await getStableAccessToken();
    const downloaded = await downloadTemporaryMedia(accessToken, mediaId, { wechatFetch });
    if (!downloaded.buffer?.length) return '';

    const extension = String(format ?? '').trim().toLowerCase() || 'amr';
    const form = new FormData();
    form.append(
      'file',
      new Blob([downloaded.buffer], {
        type: guessVoiceMimeType(format, downloaded.contentType),
      }),
      `wechat-voice.${extension}`,
    );

    const response = await wechatFetch(`${config.asrTarget.replace(/\/$/, '')}/asr/oneshot`, {
      method: 'POST',
      body: form,
    });
    const { payload, text } = await readJsonBody(response);

    if (!response.ok) {
      throw new Error(sanitizeAsrMessage(payload?.message ?? text ?? `ASR HTTP ${response.status}`));
    }
    if (Number(payload?.code ?? 200) !== 200) {
      throw new Error(sanitizeAsrMessage(payload?.message ?? text ?? '识别失败，请重试'));
    }
    return String(payload?.data?.text ?? '').trim();
  };

  const ensureWechatAgentSession = async ({
    userId,
    openid,
    forceNew = false,
    userContext = null,
  }) => {
    if (forceNew) {
      await userAuth.clearWechatAgentRoute(config.appId, openid);
    }
    const existingRoute = await userAuth.getWechatAgentRoute(config.appId, openid);
    if (existingRoute?.agentSessionId) {
      return existingRoute.agentSessionId;
    }

    const gate = await userAuth.canUseChat(userId);
    if (!gate.ok) {
      throw new Error(gate.message || '当前用户无法使用聊天能力');
    }

    const workingDir = await userAuth.resolveWorkingDir(userId);
    const sessionPolicy = await userAuth.getAgentSessionPolicy(userId);
    const publishLayout = await userAuth.getUserPublishLayout(userId);
    const started = await readJsonResponse(
      await apiFetch('/agent/start', {
        method: 'POST',
        body: JSON.stringify({
          working_dir: workingDir,
          enable_context_memory: sessionPolicy.enableContextMemory,
          ...(sessionPolicy.extensionOverrides
            ? { extension_overrides: sessionPolicy.extensionOverrides }
            : {}),
        }),
      }),
    );
    const sessionId = started?.id;
    if (!sessionId) {
      throw new Error('公众号专属 Agent 会话创建失败');
    }

    // `/agent/start` already persists the owning user and goosed node via the
    // portal proxy. Re-registering here without the node can overwrite the
    // correct mapping back to node 0 in multi-goosed production.
    const addressName = resolveWechatAddressName(userContext);
    await reconcileAgentSession(
      (pathname, init) => fetchForSession(sessionId, pathname, init),
      sessionId,
      {
      workingDir,
      sessionPolicy,
      sandboxConstraints: publishLayout?.constraints ?? null,
      userContext: publishLayout
        ? {
            userId,
            displayName: addressName || publishLayout.displayName,
            username: addressName || null,
            slug: null,
          }
        : null,
      tolerateInvalidWorkingDir: true,
      },
    );
    await userAuth.upsertWechatAgentRoute({
      userId,
      appId: config.appId,
      openid,
      agentSessionId: sessionId,
    });
    return sessionId;
  };

  const rememberWechatUserContext = async (sessionId, user) => {
    const addressName = resolveWechatAddressName(user);
    if (!addressName) return;
    try {
      await readJsonResponse(
        await fetchForSession(sessionId, '/agent/harness_remember', {
          method: 'POST',
          body: JSON.stringify({
            sessionId,
            title: '微信服务号用户',
            content: [
              `当前服务号用户名称：${addressName}`,
              '这是通过微信 openid 在 H5 绑定库中查询到的用户名称。',
              '回复时可以自然使用该名称称呼用户，不要使用 openid 或 wx_ 开头的内部用户名。',
            ].join('\n'),
          }),
        }),
      );
      await readJsonResponse(
        await fetchForSession(sessionId, '/agent/harness_bootstrap', {
          method: 'POST',
          body: JSON.stringify({ sessionId, force: true }),
        }),
      );
    } catch (err) {
      logger.warn?.('WeChat MP user context remember failed:', err);
    }
  };

  const buildIntentMetadata = (intent) => ({
    source: 'wechat_mp',
    msgType: intent.msgType,
    originalMsgId: intent.msgId || null,
    displayText: intent.displayText || '',
    mediaPublicUrl: intent.media?.publicUrl || null,
    recognition: intent.msgType === 'voice' ? intent.agentText || null : null,
    location: intent.location || null,
    link: intent.link || null,
  });

  const persistIntentDetail = async ({ intent, userId = null, rawXmlHash = '' }) => {
    if (typeof userAuth.insertWechatMpMessageDetail !== 'function') return;
    await userAuth.insertWechatMpMessageDetail({
      appId: config.appId,
      openid: intent.openid,
      msgId: intent.msgId || null,
      userId,
      msgType: intent.msgType,
      displayText: intent.displayText || '',
      agentText: intent.agentText || '',
      mediaId: intent.media?.mediaId || null,
      mediaUrl: intent.media?.picUrl || null,
      mediaPublicUrl: intent.media?.publicUrl || null,
      mediaFormat: intent.media?.format || null,
      locationLat: intent.location?.latitude ?? null,
      locationLng: intent.location?.longitude ?? null,
      locationLabel: intent.location?.label || null,
      linkUrl: intent.link?.url || null,
      linkTitle: intent.link?.title || null,
      rawXmlHash,
      rawJson: intent.raw,
    });
  };

  const runIntentMessage = async ({ inbound, intent, user }) => {
    const resetCandidate =
      intent.msgType === 'text' || intent.msgType === 'voice' ? intent.agentText : '';
    const forceNew = isTopicResetIntent(resetCandidate);
    let sessionId = await ensureWechatAgentSession({
      userId: user.userId,
      openid: inbound.fromUserName,
      userContext: user,
      forceNew,
    });
    await ensureSessionProvider(sessionId);
    await rememberWechatUserContext(sessionId, user);
    let finished = false;
    let progressTimer = null;
    if (config.progressDelayMs > 0 && config.progressText) {
      progressTimer = setTimeout(() => {
        if (finished) return;
        sendCustomerServiceText(inbound.fromUserName, config.progressText, user).catch((err) => {
          logger.warn?.('WeChat MP progress reply failed:', err);
        });
      }, config.progressDelayMs);
      progressTimer.unref?.();
    }

    const requestId = crypto.randomUUID();
    const guardScheduleReply = async (replyText) => {
      if (!scheduleService || !looksLikeScheduleConfirmation(replyText)) return replyText;
      const sourceMessageId = String(intent.msgId ?? '').trim();
      if (!sourceMessageId || typeof scheduleService.listItemsBySourceMessage !== 'function') {
        return replyText;
      }
      const items = await scheduleService
        .listItemsBySourceMessage({
          userId: user.userId,
          sourceMessageId,
          limit: 5,
        })
        .catch((err) => {
          logger.warn?.('Schedule confirmation guard failed:', err);
          return [];
        });
      if (items.length > 0) return replyText;
      return [
        '我刚才没有确认到待办/提醒已经写入系统，所以这次不能算设置成功。',
        '请再发一次完整安排，比如“明天早上 6 点跑步，5 点半提醒我”。我会在工具写入成功后再确认。',
      ].join('\n');
    };
    try {
      const reply = await executeSessionReply(
        (pathname, init) => fetchForSession(sessionId, pathname, init),
        sessionId,
        requestId,
        buildWechatAgentPrompt(intent),
        buildIntentMetadata(intent),
      );
      if (reply.tokenState) {
        await userAuth.billSessionUsage(user.userId, sessionId, reply.tokenState, requestId);
      }
      await sendCustomerServiceText(inbound.fromUserName, await guardScheduleReply(reply.text), user);
      return { sessionId };
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      const mayBeStaleSession =
        /403|404|not found|无权访问|session/i.test(message) && sessionId;
      if (mayBeStaleSession) {
        sessionId = await ensureWechatAgentSession({
          userId: user.userId,
          openid: inbound.fromUserName,
          forceNew: true,
          userContext: user,
        });
        await ensureSessionProvider(sessionId);
        await rememberWechatUserContext(sessionId, user);
        const retryId = crypto.randomUUID();
        const reply = await executeSessionReply(
          (pathname, init) => fetchForSession(sessionId, pathname, init),
          sessionId,
          retryId,
          buildWechatAgentPrompt(intent),
          buildIntentMetadata(intent),
        );
        if (reply.tokenState) {
          await userAuth.billSessionUsage(user.userId, sessionId, reply.tokenState, retryId);
        }
        await sendCustomerServiceText(inbound.fromUserName, await guardScheduleReply(reply.text), user);
        return { sessionId };
      }
      throw err;
    } finally {
      finished = true;
      if (progressTimer) clearTimeout(progressTimer);
    }
  };

  const handleScheduleIntent = async ({ intent, user }) => {
    if (!scheduleService) return null;
    const scheduleIntent = parseScheduleIntent(intent.agentText);
    if (!isScheduleIntent(scheduleIntent)) return null;

    if (scheduleIntent.action === 'create_todo') {
      if (scheduleIntent.needsClarification?.includes('todo_title')) {
        return '可以。你想让我记哪一条待办？例如“帮我记一下 跟段吃饭”。';
      }
      const item = await scheduleService.createItem({
        userId: user.userId,
        kind: 'task',
        title: scheduleIntent.title,
        timezone: process.env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai',
        sourceChannel: 'wechat',
        sourceMessageId: intent.msgId || null,
        sourceText: intent.agentText,
        metadata: {
          source: 'wechat_mp',
        },
      });
      return `已记录到待办列表：${item.title}，未设置提醒。`;
    }

    if (scheduleIntent.action === 'create_daily_todo_digest') {
      if (scheduleIntent.needsClarification?.includes('digest_time')) {
        return '可以。你想每天几点收到当天待办记录？比如“每天早上 7 点发给我”。';
      }
      const subscription = await scheduleService.createDailyTodoDigest({
        userId: user.userId,
        hour: scheduleIntent.hour,
        minute: scheduleIntent.minute,
        timezone: process.env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai',
        channel: 'wechat',
        sourceChannel: 'wechat',
        sourceMessageId: intent.msgId || null,
        sourceText: intent.agentText,
      });
      const minuteText = subscription.minute === 0 ? '' : `${String(subscription.minute).padStart(2, '0')}分`;
      return `已设置：我会每天早上 ${subscription.hour}点${minuteText} 通过服务号把当天待办记录发给你。`;
    }

    if (scheduleIntent.action === 'create_balance_alert') {
      if (scheduleIntent.needsClarification?.includes('threshold')) {
        return '可以。你想在余额低于多少时提醒我？例如“余额低于 20 元提醒我”。';
      }
      const subscription = await scheduleService.createBalanceLowAlert({
        userId: user.userId,
        thresholdCents: scheduleIntent.thresholdCents,
        channel: 'wechat',
        sourceChannel: 'wechat',
        sourceMessageId: intent.msgId || null,
        sourceText: intent.agentText,
      });
      return `已设置：当余额低于 ${(subscription.thresholdCents / 100).toFixed(2)} 元时，我会通过服务号提醒你。`;
    }

    if (scheduleIntent.action === 'query_schedule') {
      const text = await scheduleService.buildTodoDigestText({
        userId: user.userId,
        timezone: process.env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai',
      });
      return text;
    }

    return null;
  };

  const handleInboundMessage = async (bodyText, query = {}) => {
    if (!verifyRequest(query)) {
      return { ok: false, status: 403, body: 'invalid signature' };
    }
    const inbound = parseWechatMessage(String(bodyText ?? ''));
    const rawXmlHash = crypto.createHash('sha1').update(inbound.rawXml || '').digest('hex');
    const intent = normalizeWechatInboundIntent(inbound);
    intent.appId = config.appId;
    if (!inbound.fromUserName || !inbound.toUserName || !inbound.msgType) {
      return { ok: false, status: 400, body: 'invalid xml' };
    }
    if (String(query.encrypt_type ?? '').toLowerCase() === 'aes' || inbound.encrypt) {
      return {
        ok: true,
        status: 200,
        contentType: 'application/xml; charset=utf-8',
        body: buildWechatTextReply({
          toUserName: inbound.fromUserName,
          fromUserName: inbound.toUserName,
          content: '当前公众号回调需使用明文模式接入，请联系管理员切换后再试。',
        }),
      };
    }

    if (inbound.msgType === 'event' && inbound.event === 'location') {
      await persistIntentDetail({ intent, rawXmlHash });
      return successResponse();
    }

    if (inbound.msgType === 'event' && inbound.event === 'subscribe') {
      await persistIntentDetail({ intent, rawXmlHash });
      return {
        ok: true,
        status: 200,
        contentType: 'application/xml; charset=utf-8',
        body: buildWechatTextReply({
          toUserName: inbound.fromUserName,
          fromUserName: inbound.toUserName,
          content: `${config.unboundTextPrefix}\n${buildBindUrl()}`,
        }),
      };
    }

    if (inbound.msgType === 'event') {
      await persistIntentDetail({ intent, rawXmlHash });
      return successResponse();
    }

    const supportedByConfig =
      (intent.msgType === 'voice' && config.acceptVoice) ||
      (intent.msgType === 'image' && config.acceptImage) ||
      (intent.msgType === 'location' && config.acceptLocation) ||
      (intent.msgType === 'link' && config.acceptLink) ||
      intent.msgType === 'text' ||
      intent.msgType === 'video' ||
      intent.msgType === 'shortvideo';

    if (!supportedByConfig) {
      await persistIntentDetail({ intent, rawXmlHash });
      return {
        ok: true,
        status: 200,
        contentType: 'application/xml; charset=utf-8',
        body: buildWechatTextReply({
          toUserName: inbound.fromUserName,
          fromUserName: inbound.toUserName,
          content: config.unsupportedText,
        }),
      };
    }

    const boundUser = await userAuth.findWechatUserByOpenid(config.appId, inbound.fromUserName);
    if (!boundUser) {
      await persistIntentDetail({ intent, rawXmlHash });
      return {
        ok: true,
        status: 200,
        contentType: 'application/xml; charset=utf-8',
        body: buildWechatTextReply({
          toUserName: inbound.fromUserName,
          fromUserName: inbound.toUserName,
          content: `${config.unboundTextPrefix}\n${buildBindUrl()}`,
        }),
      };
    }
    if (boundUser.status === 'disabled') {
      await persistIntentDetail({ intent, userId: boundUser.userId, rawXmlHash });
      return {
        ok: true,
        status: 200,
        contentType: 'application/xml; charset=utf-8',
        body: buildWechatTextReply({
          toUserName: inbound.fromUserName,
          fromUserName: inbound.toUserName,
          content: '当前账号不可用，请联系管理员处理。',
        }),
      };
    }

    if (intent.msgType === 'voice' && !intent.agentText.trim() && intent.media?.mediaId) {
      try {
        const fallbackText = await transcribeWechatVoiceMedia(intent.media.mediaId, intent.media.format);
        if (fallbackText) {
          intent.agentText = fallbackText;
          intent.displayText = `语音：${fallbackText}`;
        }
      } catch (err) {
        logger.warn?.('WeChat MP voice ASR fallback failed:', err);
      }
    }

    if (intent.msgType === 'text' && !intent.agentText.trim()) {
      await persistIntentDetail({ intent, userId: boundUser.userId, rawXmlHash });
      return {
        ok: true,
        status: 200,
        contentType: 'application/xml; charset=utf-8',
        body: buildWechatTextReply({
          toUserName: inbound.fromUserName,
          fromUserName: inbound.toUserName,
          content: config.unsupportedText,
        }),
      };
    }

    if (intent.msgType === 'voice' && !intent.agentText.trim()) {
      await persistIntentDetail({ intent, userId: boundUser.userId, rawXmlHash });
      return {
        ok: true,
        status: 200,
        contentType: 'application/xml; charset=utf-8',
        body: buildWechatTextReply({
          toUserName: inbound.fromUserName,
          fromUserName: inbound.toUserName,
          content: '未识别到语音文字，请再说一次或输入文字。',
        }),
      };
    }

    if (intent.msgType === 'image') {
      try {
        const accessToken = await getStableAccessToken();
        const persisted = await persistWechatImage(
          {
            userId: boundUser.userId,
            appId: config.appId,
            openid: inbound.fromUserName,
            msgId: inbound.msgId,
            mediaId: inbound.mediaId,
            picUrl: inbound.picUrl,
            publicBaseUrl: config.mediaPublicBaseUrl,
            maxImageBytes: config.maxImageBytes,
          },
          {
            wechatFetch,
            accessToken,
          },
        );
        intent.media = {
          ...intent.media,
          mediaId: inbound.mediaId || intent.media?.mediaId || '',
          picUrl: inbound.picUrl || intent.media?.picUrl || '',
          publicUrl: persisted.publicUrl,
          format: persisted.contentType,
          source: persisted.source,
        };
        intent.agentText = `[图片1]: ${persisted.publicUrl}`;
      } catch (error) {
        await persistIntentDetail({ intent, userId: boundUser.userId, rawXmlHash });
        return {
          ok: true,
          status: 200,
          contentType: 'application/xml; charset=utf-8',
          body: buildWechatTextReply({
            toUserName: inbound.fromUserName,
            fromUserName: inbound.toUserName,
            content: '图片处理失败，请稍后重试或直接发送文字说明。',
          }),
        };
      }
    }

    if (
      intent.msgType === 'location' &&
      (intent.location?.latitude == null || intent.location?.longitude == null)
    ) {
      await persistIntentDetail({ intent, userId: boundUser.userId, rawXmlHash });
      return {
        ok: true,
        status: 200,
        contentType: 'application/xml; charset=utf-8',
        body: buildWechatTextReply({
          toUserName: inbound.fromUserName,
          fromUserName: inbound.toUserName,
          content: '这次位置字段不完整，请重新发送位置或直接输入地点。',
        }),
      };
    }

    if (intent.msgType === 'link' && !intent.link?.url) {
      await persistIntentDetail({ intent, userId: boundUser.userId, rawXmlHash });
      return {
        ok: true,
        status: 200,
        contentType: 'application/xml; charset=utf-8',
        body: buildWechatTextReply({
          toUserName: inbound.fromUserName,
          fromUserName: inbound.toUserName,
          content: '这次没有拿到完整链接，请重新发送一次。',
        }),
      };
    }

    if (intent.msgType === 'video' || intent.msgType === 'shortvideo') {
      await persistIntentDetail({ intent, userId: boundUser.userId, rawXmlHash });
      return {
        ok: true,
        status: 200,
        contentType: 'application/xml; charset=utf-8',
        body: buildWechatTextReply({
          toUserName: inbound.fromUserName,
          fromUserName: inbound.toUserName,
          content: '已收到视频，当前先支持文本、语音、图片、定位和链接。',
        }),
      };
    }

    if (intent.msgType === 'text' && isQuestionStatusProbe(intent.agentText)) {
      await persistIntentDetail({ intent, userId: boundUser.userId, rawXmlHash });
      return {
        ok: true,
        status: 200,
        contentType: 'application/xml; charset=utf-8',
        body: buildWechatTextReply({
          toUserName: inbound.fromUserName,
          fromUserName: inbound.toUserName,
          content: buildStatusText(boundUser, config.statusText),
        }),
      };
    }

    if (intent.msgType === 'text' && isSimpleGreeting(intent.agentText)) {
      await persistIntentDetail({ intent, userId: boundUser.userId, rawXmlHash });
      return {
        ok: true,
        status: 200,
        contentType: 'application/xml; charset=utf-8',
        body: buildWechatTextReply({
          toUserName: inbound.fromUserName,
          fromUserName: inbound.toUserName,
          content: buildGreetingText(boundUser),
        }),
      };
    }

    if ((intent.msgType === 'text' || intent.msgType === 'voice') && isConnectivityTest(intent.agentText)) {
      await persistIntentDetail({ intent, userId: boundUser.userId, rawXmlHash });
      return {
        ok: true,
        status: 200,
        contentType: 'application/xml; charset=utf-8',
        body: buildWechatTextReply({
          toUserName: inbound.fromUserName,
          fromUserName: inbound.toUserName,
          content: buildConnectivityTestReply(boundUser),
        }),
      };
    }

    if (inbound.msgId && typeof userAuth.recordWechatMpMessage === 'function') {
      const recorded = await userAuth.recordWechatMpMessage({
        appId: config.appId,
        openid: inbound.fromUserName,
        msgId: inbound.msgId,
      });
      if (!recorded?.inserted) {
        return {
          ok: true,
          status: 200,
          contentType: 'application/xml; charset=utf-8',
          body: buildWechatTextReply({
            toUserName: inbound.fromUserName,
            fromUserName: inbound.toUserName,
            content: buildStatusText(boundUser, config.statusText),
          }),
        };
      }
    }

    await persistIntentDetail({ intent, userId: boundUser.userId, rawXmlHash });

    const scheduleReply =
      intent.msgType === 'text' || intent.msgType === 'voice'
        ? await handleScheduleIntent({ intent, user: boundUser })
        : null;
    if (scheduleReply) {
      if (inbound.msgId && typeof userAuth.finishWechatMpMessage === 'function') {
        await userAuth.finishWechatMpMessage({
          appId: config.appId,
          openid: inbound.fromUserName,
          msgId: inbound.msgId,
          status: 'done',
          agentSessionId: null,
        });
      }
      return {
        ok: true,
        status: 200,
        contentType: 'application/xml; charset=utf-8',
        body: buildWechatTextReply({
          toUserName: inbound.fromUserName,
          fromUserName: inbound.toUserName,
          content: scheduleReply,
        }),
      };
    }

    const task = runIntentMessage({
      inbound,
      intent,
      user: boundUser,
    })
      .then(async ({ sessionId } = {}) => {
        if (inbound.msgId && typeof userAuth.finishWechatMpMessage === 'function') {
          await userAuth.finishWechatMpMessage({
            appId: config.appId,
            openid: inbound.fromUserName,
            msgId: inbound.msgId,
            status: 'done',
            agentSessionId: sessionId,
          });
        }
      })
      .catch(async (err) => {
        if (inbound.msgId && typeof userAuth.finishWechatMpMessage === 'function') {
          await userAuth.finishWechatMpMessage({
            appId: config.appId,
            openid: inbound.fromUserName,
            msgId: inbound.msgId,
            status: 'failed',
          }).catch(() => {});
        }
        logger.error?.('WeChat MP background reply failed:', err);
        return sendCustomerServiceText(
          inbound.fromUserName,
          `这次转发到专属 Agent 失败了：${err instanceof Error ? err.message : String(err)}`,
          boundUser,
        ).catch(() => {});
      });

    return {
      ok: true,
      status: 200,
      contentType: 'application/xml; charset=utf-8',
      body: buildWechatTextReply({
        toUserName: inbound.fromUserName,
        fromUserName: inbound.toUserName,
        content: buildAckText({
          intent,
          nickname: resolveWechatAddressName(boundUser),
          config,
          fallbackText: config.ackText,
        }),
      }),
      task,
    };
  };

  const getRouteStatusForUser = async (userId) => {
    const openid = await userAuth.getWechatOpenidForUser(userId, config.appId);
    if (!openid) {
      return {
        enabled: true,
        bound: false,
        appId: config.appId,
        openid: null,
        agentSessionId: null,
        routeStatus: null,
        updatedAt: null,
      };
    }
    const route = await userAuth.getWechatAgentRoute(config.appId, openid);
    return {
      enabled: true,
      bound: true,
      appId: config.appId,
      openid,
      agentSessionId: route?.agentSessionId ?? null,
      routeStatus: route?.status ?? null,
      updatedAt: route?.updatedAt ?? null,
    };
  };

  const recreateRouteForUser = async (userId) => {
    const openid = await userAuth.getWechatOpenidForUser(userId, config.appId);
    if (!openid) {
      return {
        ok: false,
        message: '当前账号尚未绑定该服务号',
      };
    }
    const sessionId = await ensureWechatAgentSession({
      userId,
      openid,
      forceNew: true,
    });
    const route = await userAuth.getWechatAgentRoute(config.appId, openid);
    return {
      ok: true,
      route: {
        enabled: true,
        bound: true,
        appId: config.appId,
        openid,
        agentSessionId: sessionId,
        routeStatus: route?.status ?? 'active',
        updatedAt: route?.updatedAt ?? null,
      },
    };
  };

  return {
    enabled: true,
    verifyRequest,
    verifyUrlChallenge,
    handleInboundMessage,
    getRouteStatusForUser,
    recreateRouteForUser,
    createJsSdkSignature,
    sendTextToUser,
  };
}
