/**
 * wangwang-rpa.js
 *
 * 旺旺 IM RPA 公共函数。
 * 从 run-1688.js / run-supabase.js 中提取公共逻辑，并新增消息抓取能力。
 */

import crypto from "node:crypto";
import { resolveChatTaskContactIdentity } from "./contact-identity.js";
import { createResolvedSupabaseClient } from "./supabase-client.js";
import { humanClick, humanType, randomPause, antiDetectPause } from "./human.js";

// ══════════════════════════════════════════════════════
//  登录检测 & 手动登录辅助
// ══════════════════════════════════════════════════════

export function isLoginUrl(url) {
  return url.includes("login.taobao.com") || url.includes("login.1688.com");
}

/**
 * 弹出终端提示，让用户手动完成登录后按 Enter 继续。
 * 仅在非 headless 模式下有效。
 */
export async function promptForManualLogin(page, targetUrl, accountName = null) {
  const { createInterface } = await import("node:readline/promises");
  const { stdin, stdout } = await import("node:process");
  const rl = createInterface({ input: stdin, output: stdout });
  console.log("\n⚠️  1688/淘宝登录是前置条件，请在浏览器窗口完成登录，然后回到终端按 Enter 继续。");
  try {
    await rl.question("");
  } catch (error) {
    if (error && typeof error === "object" && "code" in error && error.code === "ABORT_ERR") {
      if (accountName) {
        await reportLoginStatusInline(accountName, "failed", "已取消等待手动登录");
      }
      throw new Error("已取消等待手动登录");
    }
    if (accountName) {
      await reportLoginStatusInline(accountName, "failed", error.message || String(error));
    }
    throw error;
  } finally {
    rl.close();
  }
  await page.goto(targetUrl, { waitUntil: "domcontentloaded" });
  await antiDetectPause();
}

/** Report login status to Supabase procurement_accounts_1688 table */
async function reportLoginStatusInline(accountName, status, error = null) {
  try {
    if (!accountName) return;
    const supabase = createResolvedSupabaseClient(process.env);
    await supabase
      .from("procurement_accounts_1688")
      .update({ login_status: status, login_error: error, login_checked_at: new Date().toISOString() })
      .eq("account_name", accountName);
  } catch (err) {
    console.warn("[wangwang-rpa] 写入登录状态失败:", err.message);
  }
}

// ══════════════════════════════════════════════════════
//  旺旺聊天 iframe 操作
// ══════════════════════════════════════════════════════

/**
 * 等待并获取旺旺聊天 iframe
 */
export async function getChatFrame(page, maxAttempts = 25) {
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
    const frame = page.frames().find((f) => f.url().includes("def_cbu_web_im_core"));
    if (frame) return frame;
    await randomPause(300, 600);
  }
  return null;
}

/**
 * 关闭聊天新手引导
 */
export async function dismissChatGuide(frame) {
  const know = frame.getByText("我知道了").first();
  if (await clickIfVisible(know, { force: true })) {
    await antiDetectPause();
  }
}

/**
 * 选择"1688网页版聊天"并确认
 */
export async function chooseWebChatIfNeeded(page) {
  const webChat = page.getByText("1688网页版聊天").first();
  if (await clickIfVisible(webChat)) await antiDetectPause();
  const confirm = page.getByText("确定").first();
  if (await clickIfVisible(confirm)) await antiDetectPause();
}

/**
 * 跳过联系方式完善弹窗
 */
export async function skipContactInfoIfNeeded(page) {
  if (!page.url().includes("modify_evolve.htm")) return false;
  const later = page.getByText("下次再说").first();
  if (await clickIfVisible(later)) { await antiDetectPause(); return true; }
  const never = page.getByText("不再提醒").first();
  if (await clickIfVisible(never)) { await antiDetectPause(); return true; }
  return false;
}

// ══════════════════════════════════════════════════════
//  聊天输入 & 发送
// ══════════════════════════════════════════════════════

export async function findChatInput(scope) {
  const candidates = [
    scope.locator("textarea").first(),
    scope.locator('[contenteditable="true"]').last(),
    scope.getByPlaceholder(/请输入|发送|输入/i).first(),
    scope.locator('input[type="text"]').last(),
  ];
  for (const locator of candidates) {
    if ((await locator.count()) > 0 && (await locator.first().isVisible().catch(() => false))) {
      return locator.first();
    }
  }
  return null;
}

export async function findSendButton(scope) {
  const candidates = [
    scope.getByRole("button", { name: /发送/ }).first(),
    scope.getByText(/^发送$/).first(),
    scope.locator('[data-role="send"]').first(),
  ];
  for (const locator of candidates) {
    if ((await locator.count()) > 0 && (await locator.first().isVisible().catch(() => false))) {
      return locator.first();
    }
  }
  return null;
}

export async function clickIfVisible(locator, { force = false } = {}) {
  if ((await locator.count()) === 0) return false;
  const target = locator.first();
  if (!(await target.isVisible().catch(() => false))) return false;
  await target.click({ force }).catch(async () => {
    await target.evaluate((el) => el.click());
  });
  return true;
}

/**
 * 在聊天框中输入消息
 */
export async function typeIntoChat(frame, message) {
  const editor = frame.locator('[contenteditable="true"]').last();
  if ((await editor.count()) > 0 && (await editor.isVisible().catch(() => false))) {
    await editor.evaluate((el, value) => {
      el.textContent = value;
      el.dispatchEvent(new InputEvent("input", { bubbles: true }));
    }, message);
    return true;
  }
  const chatInput = await findChatInput(frame);
  if (!chatInput) return false;
  await chatInput.fill(message).catch(async () => {
    await humanType(frame.page(), chatInput, message, { clear: false });
  });
  return true;
}

/**
 * 发送消息（输入 + 点发送按钮 / 按 Enter）
 */
export async function sendChatMessage(frame, page, message) {
  const typed = await typeIntoChat(frame, message);
  if (!typed) return { ok: false, reason: "chat-input-not-found" };

  await antiDetectPause();
  const sendButton = await findSendButton(frame);
  if (sendButton) {
    await sendButton.click({ force: true }).catch(async () => {
      await humanClick(page, sendButton);
    });
  } else {
    await page.keyboard.press("Enter");
  }

  await antiDetectPause();
  return { ok: true };
}

// ══════════════════════════════════════════════════════
//  会话创建 & 选择 (via _imsdk)
// ══════════════════════════════════════════════════════

export function buildConversationNick(result) {
  if (!result.contact?.handle) return "";
  if (result.contact.site) return `${result.contact.site}${result.contact.handle}`;
  return result.contact.handle;
}

/**
 * 通过 _imsdk 创建并选中会话
 */
export async function createAndSelectConversation(frame, result) {
  const conversationNick = buildConversationNick(result);
  if (!conversationNick) return { ok: false, reason: "missing-contact-handle" };

  const created = await frame.evaluate(
    async ({ handle, conversationNick }) => {
      const sdk = window._imsdk;
      const conversationService = sdk?.getConversationService?.();
      if (!conversationService) return { ok: false, reason: "imsdk-conversation-service-missing" };

      const identifier = {
        entityType: "U",
        bizType: "11152",
        target: { targetId: handle, targetType: "8" },
        createContext: {
          createConversationCtx: JSON.stringify({ othersNick: [conversationNick] }),
        },
      };

      const createdConversation = await new Promise((resolve) => {
        conversationService.createSingleConversation({
          identifier,
          dataCallback: (data) => resolve({ ok: true, data }),
          completeCallback: () => {},
          errorCallBack: (error) =>
            resolve({ ok: false, reason: error?.message ?? error?.msg ?? JSON.stringify(error) }),
        });
      });

      if (!createdConversation.ok) return createdConversation;

      const conversationCode = createdConversation.data?.conversationCode;
      if (!conversationCode) return { ok: false, reason: "conversation-code-missing" };

      await new Promise((resolve) => {
        conversationService.enterConversationByCCode({
          ccode: conversationCode,
          dataCallback: () => resolve(null),
          completeCallback: () => resolve(null),
          errorCallBack: () => resolve(null),
        });
      });

      return {
        ok: true,
        conversationCode,
        conversationName:
          createdConversation.data?.conversationIdentifier?.conversationName ??
          createdConversation.data?.conversationName ??
          handle,
      };
    },
    { handle: result.contact.handle, conversationNick },
  );

  if (!created.ok) return created;

  await dismissChatGuide(frame);
  await antiDetectPause();

  const candidateTexts = [created.conversationName, result.contact.handle, result.merchantName].filter(Boolean);
  for (const text of candidateTexts) {
    const item = frame.getByText(text, { exact: false }).first();
    if (await clickIfVisible(item, { force: true })) {
      await antiDetectPause();
      return { ok: true, conversationCode: created.conversationCode, selectedText: text };
    }
  }
  return { ok: false, reason: "conversation-list-item-not-found", conversationCode: created.conversationCode };
}

// ══════════════════════════════════════════════════════
//  完整的"打开旺旺 → 发送消息"流程
// ══════════════════════════════════════════════════════

/**
 * 联系商家：打开旺旺链接 → 进入聊天 → 发送消息
 *
 * @param {import("playwright").BrowserContext} context
 * @param {object} result  搜索结果 (含 contact.url / contact.handle 等)
 * @param {string} askMessage  要发送的消息
 * @returns {Promise<{status: string, reason?: string, finalUrl?: string, conversationCode?: string}>}
 */
export async function contactMerchant(context, result, askMessage) {
  if (!result.contact?.url) {
    return { status: "skipped", reason: "missing-contact-url" };
  }

  const page = await context.newPage();
  try {
    await page.goto(result.contact.url, { waitUntil: "domcontentloaded" });
    await antiDetectPause();

    if (isLoginUrl(page.url())) {
      await promptForManualLogin(page, result.contact.url);
    }

    await chooseWebChatIfNeeded(page);
    await skipContactInfoIfNeeded(page);
    const frame = await getChatFrame(page);

    if (!frame) {
      return { status: "skipped", reason: "chat-frame-not-found", finalUrl: page.url() };
    }

    await dismissChatGuide(frame);
    const frameTextBefore = await frame.locator("body").innerText().catch(() => "");
    let conversationStatus = { ok: true };

    if (frameTextBefore.includes("您尚未选择联系人")) {
      conversationStatus = await createAndSelectConversation(frame, result);
      if (!conversationStatus.ok) {
        return {
          status: "skipped",
          reason: conversationStatus.reason ?? "contact-selection-required",
          finalUrl: page.url(),
        };
      }
    }

    const sendResult = await sendChatMessage(frame, page, askMessage);
    if (!sendResult.ok) {
      return { status: "skipped", reason: sendResult.reason, finalUrl: page.url() };
    }

    const frameTextAfter = await frame.locator("body").innerText().catch(() => "");
    const confirmed = frameTextAfter.includes(askMessage.slice(0, 10));

    return {
      status: confirmed ? "sent" : "unknown",
      reason: confirmed ? "" : "message-not-confirmed-in-frame",
      finalUrl: page.url(),
      conversationCode: conversationStatus.conversationCode ?? "",
    };
  } catch (error) {
    return {
      status: "error",
      reason: error instanceof Error ? error.message : String(error),
      finalUrl: page.url(),
    };
  } finally {
    await page.close().catch(() => {});
  }
}

// ══════════════════════════════════════════════════════
//  消息抓取（新增能力）
// ══════════════════════════════════════════════════════

/**
 * 计算消息文本的 MD5 哈希
 */
export function getMessageHash(text) {
  return crypto.createHash("md5").update(text || "").digest("hex");
}

/**
 * 判断是否有新回复
 */
export function hasNewReply(currentHash, lastStoredHash) {
  if (!lastStoredHash) return !!currentHash;
  return currentHash !== lastStoredHash;
}

/**
 * 从旺旺 iframe 抓取最新消息列表
 *
 * 注意：真实 DOM 选择器需根据实际页面调试确认，下面提供多种备选策略。
 * 在 headless=false 时先手动验证选择器是否命中。
 *
 * @param {import("playwright").Frame} frame  旺旺聊天 iframe
 * @returns {Promise<{messages: Array<{text: string, sender: "self"|"merchant"|"unknown"}>, latestHash: string}>}
 */
export async function scrapeLatestMessages(frame) {
  const extracted = await frame.evaluate(() => {
    // 策略 1：按照常见的 IM 气泡 DOM 结构抓取
    // 旺旺 IM 的消息通常在某个滚动容器内，每条消息有"self"和"other"区分
    const messages = [];

    // 尝试多种可能的选择器
    const selectors = [
      // 常见的 1688 旺旺 IM 消息选择器
      ".msg-content",
      ".message-item",
      "[class*='message']",
      "[class*='chat-msg']",
      "[class*='bubble']",
    ];

    let msgElements = [];
    for (const selector of selectors) {
      const els = document.querySelectorAll(selector);
      if (els.length > 0) {
        msgElements = Array.from(els);
        break;
      }
    }

    // 如果找到了消息元素
    for (const el of msgElements.slice(-10)) {
      const text = (el.textContent || "").replace(/\s+/g, " ").trim();
      if (!text) continue;

      // 判断发送者：检查父级/自身是否有 self/mine 相关 class
      const classes = (el.className || "") + " " + (el.parentElement?.className || "");
      let sender = "unknown";
      if (/self|mine|right|send/i.test(classes)) {
        sender = "self";
      } else if (/other|peer|left|receive/i.test(classes)) {
        sender = "merchant";
      }

      messages.push({ text, sender });
    }

    // 降级策略：如果上面没找到任何消息，尝试从 body 文本粗粒度提取
    if (messages.length === 0) {
      const bodyText = (document.body?.innerText || "").trim();
      if (bodyText) {
        messages.push({ text: bodyText, sender: "unknown" });
      }
    }

    return messages;
  });

  const lastMerchantMsg = [...extracted].reverse().find((m) => m.sender === "merchant");
  const lastAnyMsg = extracted[extracted.length - 1];
  const hashTarget = lastMerchantMsg?.text || lastAnyMsg?.text || "";

  return {
    messages: extracted,
    latestMerchantMessage: lastMerchantMsg || null,
    latestHash: getMessageHash(hashTarget),
  };
}

/**
 * 在已有会话中发送消息（用于多轮对话的第 2-4 轮）
 *
 * 与 contactMerchant 的区别：不重新创建会话，而是打开旺旺后直接在当前会话发消息。
 * 前提是旺旺 iframe 中已经选中了目标会话（通过 conversationCode 或联系人列表）。
 *
 * @param {import("playwright").BrowserContext} context
 * @param {object} chatTask  customer_service_chats 行
 * @param {string} messageText  要发送的文本
 * @returns {Promise<{status: string, reason?: string, finalUrl?: string}>}
 */
export async function sendMessageInExistingChat(context, chatTask, messageText) {
  if (!chatTask.contact_url) {
    return { status: "skipped", reason: "missing-contact-url" };
  }

  const contactIdentity = resolveChatTaskContactIdentity(chatTask);

  const page = await context.newPage();
  try {
    await page.goto(chatTask.contact_url, { waitUntil: "domcontentloaded" });
    await antiDetectPause();

    if (isLoginUrl(page.url())) {
      await promptForManualLogin(page, chatTask.contact_url);
    }

    await chooseWebChatIfNeeded(page);
    await skipContactInfoIfNeeded(page);
    const frame = await getChatFrame(page);

    if (!frame) {
      return { status: "skipped", reason: "chat-frame-not-found", finalUrl: page.url() };
    }

    await dismissChatGuide(frame);

    // 尝试通过联系人列表选中目标会话
    const candidateTexts = [contactIdentity.handle, chatTask.merchant_name].filter(Boolean);
    let selected = false;
    for (const text of candidateTexts) {
      const item = frame.getByText(text, { exact: false }).first();
      if (await clickIfVisible(item, { force: true })) {
        await antiDetectPause();
        selected = true;
        break;
      }
    }

    // 如果联系人列表中没找到，尝试创建会话
    if (!selected) {
      const fakeResult = {
        contact: {
          handle: contactIdentity.handle,
          site: contactIdentity.site,
          url: chatTask.contact_url,
        },
        merchantName: chatTask.merchant_name,
      };
      const convStatus = await createAndSelectConversation(frame, fakeResult);
      if (!convStatus.ok) {
        return { status: "skipped", reason: convStatus.reason ?? "conversation-not-found", finalUrl: page.url() };
      }
    }

    const sendResult = await sendChatMessage(frame, page, messageText);
    if (!sendResult.ok) {
      return { status: "skipped", reason: sendResult.reason, finalUrl: page.url() };
    }

    return { status: "sent", finalUrl: page.url() };
  } catch (error) {
    return {
      status: "error",
      reason: error instanceof Error ? error.message : String(error),
      finalUrl: page.url(),
    };
  } finally {
    await page.close().catch(() => {});
  }
}

/**
 * 打开旺旺并抓取最新消息（不发送，只读）
 *
 * @param {import("playwright").BrowserContext} context
 * @param {object} chatTask  customer_service_chats 行
 * @returns {Promise<{ok: boolean, messages?: object[], latestHash?: string, latestMerchantMessage?: object, reason?: string}>}
 */
export async function openAndScrapeMessages(context, chatTask) {
  if (!chatTask.contact_url) {
    return { ok: false, reason: "missing-contact-url" };
  }

  const contactIdentity = resolveChatTaskContactIdentity(chatTask);

  const page = await context.newPage();
  try {
    await page.goto(chatTask.contact_url, { waitUntil: "domcontentloaded" });
    await antiDetectPause();

    if (isLoginUrl(page.url())) {
      await promptForManualLogin(page, chatTask.contact_url);
    }

    await chooseWebChatIfNeeded(page);
    await skipContactInfoIfNeeded(page);
    const frame = await getChatFrame(page);

    if (!frame) {
      return { ok: false, reason: "chat-frame-not-found" };
    }

    await dismissChatGuide(frame);

    // 选中目标会话
    const candidateTexts = [contactIdentity.handle, chatTask.merchant_name].filter(Boolean);
    for (const text of candidateTexts) {
      const item = frame.getByText(text, { exact: false }).first();
      if (await clickIfVisible(item, { force: true })) {
        await antiDetectPause();
        break;
      }
    }

    const result = await scrapeLatestMessages(frame);
    return { ok: true, ...result };
  } catch (error) {
    return { ok: false, reason: error instanceof Error ? error.message : String(error) };
  } finally {
    await page.close().catch(() => {});
  }
}
