"use strict";

/**
 * 发布用 Puppeteer 浏览器（按平台/账号隔离 userDataDir）
 *
 * Windows：Chrome 必须以 detached 子进程启动，否则 Node 被 Tauri 结束后会连带关掉浏览器。
 */
const fs = require("fs");
const path = require("path");
const os = require("os");
const http = require("http");
const { spawn } = require("child_process");

const {
  COMMON_BROWSER_ARGS,
  BROWSER_IGNORE_DEFAULT_ARGS,
  STEALTH_INIT_SCRIPT,
  DEFAULT_USER_AGENT,
} = require("./douyin_browser_constants.js");

const browsers = new Map();

function getRuntimeDataPath(...parts) {
  const base = process.env.AICLIENT_DATA_DIR || path.join(os.homedir(), ".aiclient");
  return path.join(base, ...parts);
}

function getChromiumExecutablePath() {
  const fromEnv =
    process.env.AICLIENT_CHROMIUM_PATH || process.env.PUPPETEER_EXECUTABLE_PATH;
  if (fromEnv && fs.existsSync(fromEnv)) return fromEnv;
  const candidates = [];
  if (process.platform === "win32") {
    const pf = process.env.ProgramFiles || "C:\\Program Files";
    const local = process.env.LOCALAPPDATA || "";
    const administrator_appdata="C:\\Users\\Administrator\\AppData\\Local";
    candidates.push(
      path.join(pf, "Google", "Chrome", "Application", "chrome.exe"),
      path.join(local, "Google", "Chrome", "Application", "chrome.exe"),
      path.join(pf, "Microsoft", "Edge", "Application", "msedge.exe"),
      path.join(administrator_appdata, "Google", "Chrome", "bin","chrome.exe"),
    );
  }
  return candidates.find((p) => p && fs.existsSync(p));
}

function loadPuppeteer() {
  try {
    return require("puppeteer-core");
  } catch {
    return require("puppeteer");
  }
}

function contextKey(platform, accountId) {
  return accountId ? `publish_${platform}_${accountId}` : `publish_${platform}`;
}

function userDataDir(platform, accountId) {
  const sub = accountId ? `${platform}_${accountId}` : platform;
  return getRuntimeDataPath("browser-data", "publish", sub);
}

function debugPortForKey(key) {
  let h = 5381;
  for (let i = 0; i < key.length; i++) {
    h = ((h << 5) + h + key.charCodeAt(i)) >>> 0;
  }
  return 9300 + (h % 700);
}

function delay(ms) {
  return new Promise((r) => setTimeout(r, ms));
}

function fetchDebugVersion(port) {
  return new Promise((resolve, reject) => {
    const req = http.get(`http://127.0.0.1:${port}/json/version`, (res) => {
      let body = "";
      res.on("data", (chunk) => {
        body += chunk;
      });
      res.on("end", () => {
        try {
          resolve(JSON.parse(body));
        } catch (e) {
          reject(e);
        }
      });
    });
    req.on("error", reject);
    req.setTimeout(2500, () => {
      req.destroy();
      reject(new Error("timeout"));
    });
  });
}

async function isDebugPortOpen(port) {
  try {
    await fetchDebugVersion(port);
    return true;
  } catch {
    return false;
  }
}

async function waitForDebugPort(port, timeoutMs = 25000) {
  const start = Date.now();
  while (Date.now() - start < timeoutMs) {
    if (await isDebugPortOpen(port)) return;
    await delay(400);
  }
  throw new Error(`Chrome 调试端口 ${port} 未就绪，请检查是否已安装 Chrome/Edge`);
}

/**
 * 独立进程启动 Chrome（与 Node 生命周期解耦）
 */
async function spawnDetachedChrome(executablePath, dir, port) {
  if (await isDebugPortOpen(port)) return;
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });

  const args = [
    `--remote-debugging-port=${port}`,
    `--user-data-dir=${dir}`,
    "--remote-allow-origins=*",
    ...COMMON_BROWSER_ARGS.filter((a) => !a.startsWith("--user-data-dir")),
    "about:blank",
  ];

  const child = spawn(executablePath, args, {
    detached: true,
    stdio: "ignore",
    windowsHide: false,
  });
  child.unref();
  await waitForDebugPort(port);
}

async function connectPublishBrowser(puppeteer, port) {
  return puppeteer.connect({
    browserURL: `http://127.0.0.1:${port}`,
    defaultViewport: { width: 1280, height: 900 },
  });
}

async function getPublishBrowser(platform, accountId) {
  const key = contextKey(platform, accountId);
  const existing = browsers.get(key);
  if (existing && existing.isConnected()) {
    return existing;
  }

  const puppeteer = loadPuppeteer();
  const dir = userDataDir(platform, accountId);
  const port = debugPortForKey(key);
  const executablePath = getChromiumExecutablePath();
  if (!executablePath) {
    throw new Error(
      "未找到 Chrome/Edge，请安装浏览器或设置 AICLIENT_CHROMIUM_PATH",
    );
  }

  let browser;
  if (await isDebugPortOpen(port)) {
    browser = await connectPublishBrowser(puppeteer, port);
  } else {
    await spawnDetachedChrome(executablePath, dir, port);
    browser = await connectPublishBrowser(puppeteer, port);
  }

  browser.on("disconnected", () => browsers.delete(key));
  browsers.set(key, browser);
  return browser;
}

async function closeBlankPages(browser, keepPage) {
  try {
    const pages = await browser.pages();
    for (const p of pages) {
      if (keepPage && p === keepPage) continue;
      const u = p.url();
      if (!u || u === "about:blank") {
        try {
          await p.close();
        } catch {
          /* ignore */
        }
      }
    }
  } catch {
    /* ignore */
  }
}

async function newPublishPage(platform, accountId, cookies) {
  const browser = await getPublishBrowser(platform, accountId);
  const pages = await browser.pages();
  let page =
    pages.find((p) => {
      try {
        const u = p.url();
        return !u || u === "about:blank";
      } catch {
        return false;
      }
    }) || null;
  if (!page) {
    page = await browser.newPage();
  }
  await page.setUserAgent(DEFAULT_USER_AGENT);
  await page.evaluateOnNewDocument(STEALTH_INIT_SCRIPT);
  page.setDefaultNavigationTimeout(60000);
  page.setDefaultTimeout(60000);
  if (Array.isArray(cookies) && cookies.length) {
    try {
      await page.setCookie(...cookies);
    } catch (e) {
      console.warn("setCookie 失败:", e.message);
    }
  }
  await closeBlankPages(browser, page);
  return page;
}

async function closePublishBrowser(platform, accountId) {
  const key = contextKey(platform, accountId);
  const port = debugPortForKey(key);
  let browser = browsers.get(key);
  browsers.delete(key);
  try {
    const puppeteer = loadPuppeteer();
    if (!browser || !browser.isConnected()) {
      if (!(await isDebugPortOpen(port))) return;
      browser = await connectPublishBrowser(puppeteer, port);
    }
    await browser.close();
  } catch {
    /* ignore */
  }
}

/**
 * 断开 Puppeteer 连接，Chrome 以 detached 进程继续运行
 */
async function detachPublishBrowser(platform, accountId) {
  const key = contextKey(platform, accountId);
  const browser = browsers.get(key);
  browsers.delete(key);
  try {
    if (browser && browser.isConnected()) {
      await browser.disconnect();
    }
  } catch (e) {
    console.warn("detachPublishBrowser:", e.message);
  }
}

const LOGIN_URLS = {
  douyin: "https://creator.douyin.com/",
  kuaishou: "https://cp.kuaishou.com/",
  shipin: "https://channels.weixin.qq.com/",
  xiaohongshu: "https://creator.xiaohongshu.com/",
};

module.exports = {
  delay,
  getPublishBrowser,
  newPublishPage,
  userDataDir,
  LOGIN_URLS,
  getChromiumExecutablePath,
  closePublishBrowser,
  detachPublishBrowser,
  debugPortForKey,
  contextKey,
};
