/**
 * lark-client.js — 飞书 Lark SDK 封装
 *
 * 负责：
 *   - WebSocket 长连接（无需公网 IP）
 *   - 接收 im.message.receive_v1 / card.action.trigger / application.bot.menu_v6 事件
 *   - 下载图片 / 音频资源
 *   - 发送 / 回复 / 更新 消息（文本 + 卡片模板）
 *
 * 仅处理单聊（chat_type === 'p2p'），群聊消息直接忽略。
 */

import * as lark from '@larksuiteoapi/node-sdk';
import path from 'path';
import { buildTextOrMarkdownMessage } from './card-builder.js';

/** 检测图片 Buffer 的 MIME 类型（通过魔数） */
function detectMimeType(buf) {
  if (buf.length < 4) return 'image/jpeg';
  if (buf[0] === 0x89 && buf[1] === 0x50) return 'image/png';
  if (buf[0] === 0xff && buf[1] === 0xd8) return 'image/jpeg';
  if (buf.slice(0, 4).toString('ascii') === 'GIF8') return 'image/gif';
  if (buf.slice(0, 4).toString('ascii') === 'RIFF' && buf.slice(8, 12).toString('ascii') === 'WEBP') return 'image/webp';
  return 'image/jpeg';
}

/** 将 ReadableStream 读取为 Buffer */
async function streamToBuffer(stream) {
  const chunks = [];
  for await (const chunk of stream) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
  return Buffer.concat(chunks);
}

export class LarkClient {
  constructor(appId, appSecret, engine) {
    this.appId = appId;
    this.appSecret = appSecret;
    this.engine = engine;
    this.wsClient = null;

    // REST 客户端（用于发消息、下载资源）
    this.client = new lark.Client({ appId, appSecret, disableTokenCache: false });
  }

  async start() {
    this.wsClient = new lark.WSClient({ appId: this.appId, appSecret: this.appSecret });

    const dispatcher = new lark.EventDispatcher({}).register({
      // 接收消息事件
      'im.message.receive_v1': async (data) => {
        try {
          await this._onMessage(data);
        } catch (err) {
          console.error('[Feishu:lark-client] onMessage error:', err.message);
        }
      },
      // 卡片交互回调
      'card.action.trigger': async (data) => {
        try {
          return await this._onCardAction(data);
        } catch (err) {
          console.error('[Feishu:lark-client] onCardAction error:', err.message);
        }
      },
      // 机器人菜单点击回调
      'application.bot.menu_v6': async (data) => {
        try {
          await this._onMenuAction(data);
        } catch (err) {
          console.error('[Feishu:lark-client] onMenuAction error:', err.message);
        }
      },
    });

    this.wsClient.start({ eventDispatcher: dispatcher });
    console.log('[Feishu] Lark WebSocket client started');
  }

  stop() {
    try { this.wsClient?.close?.(); } catch (_) {}
  }

  // ─── 内部事件处理 ────────────────────────────────────────────────────────────

  async _onMessage(data) {
    const { message, sender } = data;
    if (!message || !sender) return;

    const chatType    = message.chat_type;
    const feishuOpenId = sender?.sender_id?.open_id;

    // 只处理单聊
    if (chatType !== 'p2p') return;
    if (!feishuOpenId) return;

    const chatId    = message.chat_id;
    const messageId = message.message_id;
    const msgType   = message.message_type;

    let content = '';
    let images  = null;
    let audio   = null;
    let files   = null;

    switch (msgType) {
      case 'text': {
        const body = JSON.parse(message.content || '{}');
        content = (body.text || '').trim();
        break;
      }
      case 'image': {
        const body = JSON.parse(message.content || '{}');
        const img = await this._downloadImage(messageId, body.image_key).catch(err => {
          console.error('[Feishu] Failed to download image:', err.message);
          return null;
        });
        if (img) images = [img];
        break;
      }
      case 'audio': {
        const body = JSON.parse(message.content || '{}');
        const buf = await this._downloadResource(messageId, body.file_key, 'file').catch(err => {
          console.error('[Feishu] Failed to download audio:', err.message);
          return null;
        });
        if (buf) audio = { data: buf, format: 'ogg', mimeType: 'audio/ogg' };
        break;
      }
      case 'file': {
        const body = JSON.parse(message.content || '{}');
        const buf = await this._downloadResource(messageId, body.file_key, 'file').catch(err => {
          console.error('[Feishu] Failed to download file:', err.message);
          return null;
        });
        if (buf) files = [{ data: buf, filename: body.file_name || 'attachment', fileKey: body.file_key }];
        break;
      }
      case 'media': {
        const body = JSON.parse(message.content || '{}');
        const buf = await this._downloadResource(messageId, body.file_key, 'file').catch(err => {
          console.error('[Feishu] Failed to download media:', err.message);
          return null;
        });
        if (buf) files = [{ data: buf, filename: body.file_name || 'media', fileKey: body.file_key }];
        break;
      }
      default:
        console.log(`[Feishu] Unsupported message type: ${msgType}, ignoring`);
        return;
    }

    // 引用消息处理：提取父消息中的文件元数据（不重复下载已保存的文件）
    let quotedFiles = null;
    if (message.parent_id) {
      quotedFiles = await this._getQuotedFilesMeta(message.parent_id).catch(err => {
        console.error('[Feishu] Failed to fetch quoted message meta:', err.message);
        return null;
      });
    }

    await this.engine.handleMessage({ feishuOpenId, chatId, messageId, content, images, audio, files, quotedFiles });
  }

  async _onCardAction(data) {
    // card.action.trigger v2 事件：open_id 直接在 operator 下
    const openId    = data?.operator?.open_id;
    const action    = data?.action?.value || {};
    // host 是字符串（如 "im_message"），消息 ID 在 context.open_message_id
    const messageId = data?.context?.open_message_id;
    const formValue = data?.action?.form_value || {};

    if (!openId) {
      console.warn('[Feishu:lark-client] _onCardAction: missing open_id, raw data.operator =', JSON.stringify(data?.operator));
      return {};
    }
    // 返回值会被飞书 WS SDK 作为同步响应发回，包含 card 字段时飞书直接更新卡片，
    // 避免不返回导致飞书把卡片恢复到交互前的状态
    return await this.engine.handleCardAction({ feishuOpenId: openId, action, messageId, formValue });
  }

  async _onMenuAction(data) {
    // application.bot.menu_v6：SDK 传入的 data 已是 event body，无需再访问 .event
    const openId   = data?.operator?.operator_id?.open_id;
    const eventKey = data?.event_key;

    if (!openId || !eventKey) {
      console.warn('[Feishu:lark-client] _onMenuAction: missing open_id or event_key, raw data =', JSON.stringify(data));
      return;
    }

    console.log(`[Feishu:lark-client] Menu click: openId=${openId} eventKey=${eventKey}`);
    await this.engine.handleMenuAction({ feishuOpenId: openId, eventKey });
  }

  // ─── 资源下载 ─────────────────────────────────────────────────────────────────

  async _downloadImage(messageId, imageKey) {
    const resp = await this.client.im.messageResource.get({
      path: { message_id: messageId, file_key: imageKey },
      params: { type: 'image' },
    });
    const buf = await streamToBuffer(resp.getReadableStream());
    return { data: buf, mimeType: detectMimeType(buf) };
  }

  async _downloadResource(messageId, fileKey, type = 'file') {
    const resp = await this.client.im.messageResource.get({
      path: { message_id: messageId, file_key: fileKey },
      params: { type },
    });
    return streamToBuffer(resp.getReadableStream());
  }

  // ─── 消息发送 API ─────────────────────────────────────────────────────────────

  /** 发送消息到指定会话（chat_id） */
  async sendMessage(chatId, content, msgType) {
    await this.client.im.message.create({
      params: { receive_id_type: 'chat_id' },
      data: { receive_id: chatId, msg_type: msgType, content },
    });
  }

  /**
   * 根据 ID 前缀自动判断 receive_id_type：
   *   ou_  → open_id（用户 ID）
   *   其他  → chat_id
   */
  _resolveReceiveIdType(receiveId) {
    return receiveId?.startsWith('ou_') ? 'open_id' : 'chat_id';
  }

  /** 发送消息到 chatId 或 open_id（自动识别类型） */
  async sendToTarget(receiveId, content, msgType) {
    await this.client.im.message.create({
      params: { receive_id_type: this._resolveReceiveIdType(receiveId) },
      data: { receive_id: receiveId, msg_type: msgType, content },
    });
  }

  /** 发送文本到 chatId 或 open_id（自动识别类型） */
  async sendTextToTarget(receiveId, text) {
    const { msgType, content } = buildTextOrMarkdownMessage(text);
    return this.sendToTarget(receiveId, content, msgType);
  }

  /** 回复某条消息（线程化） */
  async replyMessage(messageId, content, msgType) {
    await this.client.im.message.reply({
      path: { message_id: messageId },
      data: { msg_type: msgType, content },
    });
  }

  /**
   * 发送普通文本（自动处理 Markdown 格式）
   */
  async sendText(chatId, text) {
    const { msgType, content } = buildTextOrMarkdownMessage(text);
    return this.sendMessage(chatId, content, msgType);
  }

  async replyText(messageId, text) {
    const { msgType, content } = buildTextOrMarkdownMessage(text);
    return this.replyMessage(messageId, content, msgType);
  }

  /**
   * 通过 open_id 直接发送消息给用户（菜单事件等无 chat_id 的场景）
   */
  async sendMessageToUser(openId, content, msgType) {
    await this.client.im.message.create({
      params: { receive_id_type: 'open_id' },
      data: { receive_id: openId, msg_type: msgType, content },
    });
  }

  async sendTextToUser(openId, text) {
    const { msgType, content } = buildTextOrMarkdownMessage(text);
    return this.sendMessageToUser(openId, content, msgType);
  }

  /**
   * 更新已发送的卡片消息
   * @param {string} messageId  需要更新的消息 ID
   * @param {string} cardContent  新的卡片内容 JSON 字符串
   */
  async updateCard(messageId, cardContent) {
    await this.client.im.message.patch({
      path: { message_id: messageId },
      data: { content: cardContent },
    });
  }

  /** 发送直接 JSON 交互卡片并返回消息 ID（用于 AskUserQuestion 等动态卡片） */
  async sendInteractiveAndGetMsgId(target, cardContent) {
    const resp = await this.client.im.message.create({
      params: { receive_id_type: this._resolveReceiveIdType(target) },
      data: { receive_id: target, msg_type: 'interactive', content: cardContent },
    });
    return resp?.data?.message_id || null;
  }

  /**
   * 给消息添加表情回复
   * @param {string} messageId  消息 ID
   * @param {string} emojiType  表情类型，如 'OneSecond'
   * @returns {Promise<string|null>} reaction_id，失败时返回 null
   */
  async addReaction(messageId, emojiType = 'OneSecond') {
    const resp = await this.client.im.messageReaction.create({
      path: { message_id: messageId },
      data: { reaction_type: { emoji_type: emojiType } },
    });
    return resp?.data?.reaction_id || null;
  }

  /**
   * 删除消息表情回复
   * @param {string} messageId   消息 ID
   * @param {string} reactionId  reaction_id（addReaction 返回的）
   */
  async deleteReaction(messageId, reactionId) {
    await this.client.im.messageReaction.delete({
      path: { message_id: messageId, reaction_id: reactionId },
    });
  }

  // ─── 文件 / 图片发送 API ───────────────────────────────────────────────────

  /**
   * 上传图片 Buffer 到飞书，返回 image_key
   * @param {Buffer} imageBuffer
   * @returns {Promise<string>} image_key
   */
  async uploadImage(imageBuffer) {
    const resp = await this.client.im.image.create({
      data: { image_type: 'message', image: imageBuffer },
    });
    const imageKey = resp?.image_key;
    if (!imageKey) throw new Error(`Upload image failed: code=${resp?.code}, msg=${resp?.msg}`);
    return imageKey;
  }

  /**
   * 上传文件 Buffer 到飞书，返回 file_key
   * @param {Buffer} fileBuffer
   * @param {string} filename  带扩展名的文件名
   * @returns {Promise<string>} file_key
   */
  async uploadFile(fileBuffer, filename) {
    const ext = path.extname(filename).toLowerCase().slice(1);
    const FILE_TYPE_MAP = {
      pdf: 'pdf', doc: 'doc', docx: 'doc',
      xls: 'xls', xlsx: 'xls',
      ppt: 'ppt', pptx: 'ppt',
      mp4: 'mp4', opus: 'opus',
    };
    const fileType = FILE_TYPE_MAP[ext] || 'stream';
    const resp = await this.client.im.file.create({
      data: { file_type: fileType, file_name: filename, file: fileBuffer },
    });
    const fileKey = resp?.file_key;
    if (!fileKey) throw new Error(`Upload file failed: code=${resp?.code}, msg=${resp?.msg}`);
    return fileKey;
  }

  /**
   * 发送图片消息（Buffer → 上传 → 发送）
   * @param {string} chatId
   * @param {Buffer} imageBuffer
   */
  async sendImageBuffer(target, imageBuffer) {
    const imageKey = await this.uploadImage(imageBuffer);
    await this.sendToTarget(target, JSON.stringify({ image_key: imageKey }), 'image');
  }

  /**
   * 发送文件消息（Buffer → 上传 → 发送）
   * @param {string} target  chat_id 或 open_id（自动识别类型）
   * @param {Buffer} fileBuffer
   * @param {string} filename
   */
  async sendFileBuffer(target, fileBuffer, filename) {
    const fileKey = await this.uploadFile(fileBuffer, filename);
    await this.sendToTarget(target, JSON.stringify({ file_key: fileKey }), 'file');
  }

  /**
   * 获取引用（父）消息中的附件元数据，不下载内容
   * 支持 file、media、image、audio 类型；其他类型返回 null
   * 返回数组，每项包含 type 字段用于 engine 区分处理方式
   */
  async _getQuotedFilesMeta(parentId) {
    const resp = await this.client.im.message.get({ path: { message_id: parentId } });
    // SDK 返回结构：resp.data.items[0] 或 resp.data.message（兼容两种格式）
    const item = resp?.data?.items?.[0] ?? resp?.data?.message;
    if (!item) return null;

    // im.message.get 返回 msg_type（非事件的 message_type），content 在 body.content 下
    const msgType = item.msg_type;
    const body = JSON.parse(item.body?.content || '{}');
    switch (msgType) {
      case 'file':
        return [{ type: 'file', messageId: item.message_id, fileKey: body.file_key, filename: body.file_name || 'attachment' }];
      case 'media':
        return [{ type: 'file', messageId: item.message_id, fileKey: body.file_key, filename: body.file_name || 'media' }];
      case 'image':
        return [{ type: 'image', messageId: item.message_id, fileKey: body.image_key }];
      case 'audio':
        return [{ type: 'audio', messageId: item.message_id, fileKey: body.file_key }];
      default:
        return null;
    }
  }

  /** 供 engine 调用：下载飞书资源原始数据（Buffer），type 默认 'file'，图片传 'image' */
  async downloadFile(messageId, fileKey, type = 'file') {
    return this._downloadResource(messageId, fileKey, type);
  }
}
