// 一次性截图脚本 —— 用系统 Chrome 的 remote debugging 协议直接通信,
// 避免装 puppeteer/playwright(项目 pnpm store 状态不允许)。
//
// 通过 Fetch.requestPaused 拦截 /api/auth/session 注入 mock next-auth session,
// 让客户端 useSession() 以为已登录,从而能触发 VoteModal / 进入 /me 页。
import { spawn } from "node:child_process";
import { writeFile, mkdir } from "node:fs/promises";
import { setTimeout as wait } from "node:timers/promises";

const CHROME = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe";
const PORT = 9333;
const PROFILE = "C:\\Users\\10419\\AppData\\Local\\Temp\\cs-voting-shot";
const OUT_DIR = "d:\\ClaudeProjects\\虚拟明星\\UI-UX\\docs\\screenshots\\voting-refactor";
const ORIGIN = "http://localhost:3000";

const MOCK_SESSION = {
  user: { id: "mock-user", name: "测试用户" },
  expires: new Date(Date.now() + 86400 * 1000).toISOString(),
};

async function launchChrome() {
  const proc = spawn(
    CHROME,
    [
      `--headless=new`,
      `--disable-gpu`,
      `--remote-debugging-port=${PORT}`,
      `--user-data-dir=${PROFILE}`,
      `--window-size=1500,900`,
      `--hide-scrollbars`,
      `--no-first-run`,
      `--no-default-browser-check`,
      `about:blank`,
    ],
    { stdio: "ignore", detached: true },
  );
  proc.unref();
  for (let i = 0; i < 30; i++) {
    try {
      const r = await fetch(`http://127.0.0.1:${PORT}/json/version`);
      if (r.ok) return proc.pid;
    } catch (_e) {
      void _e;
    }
    await wait(300);
  }
  throw new Error("Chrome remote debugging did not come up");
}

async function killChrome(pid) {
  try {
    process.kill(pid);
  } catch {
    /* */
  }
  spawn("taskkill", ["/F", "/PID", String(pid), "/T"], { stdio: "ignore" });
}

async function openPage() {
  const r = await fetch(`http://127.0.0.1:${PORT}/json/new?about:blank`, {
    method: "PUT",
  });
  return await r.json();
}

class CDP {
  constructor(wsUrl) {
    this.ws = null;
    this.wsUrl = wsUrl;
    this.id = 0;
    this.pending = new Map();
    this.listeners = new Set();
  }
  async connect() {
    this.ws = new WebSocket(this.wsUrl);
    await new Promise((res, rej) => {
      this.ws.addEventListener("open", () => res(), { once: true });
      this.ws.addEventListener("error", (e) => rej(e), { once: true });
    });
    this.ws.addEventListener("message", (ev) => {
      const msg = JSON.parse(ev.data);
      if (msg.id && this.pending.has(msg.id)) {
        const { resolve, reject } = this.pending.get(msg.id);
        this.pending.delete(msg.id);
        if (msg.error) reject(new Error(msg.error.message));
        else resolve(msg.result);
      } else if (msg.method) {
        for (const cb of this.listeners) cb(msg);
      }
    });
  }
  on(cb) {
    this.listeners.add(cb);
    return () => this.listeners.delete(cb);
  }
  send(method, params = {}) {
    const id = ++this.id;
    return new Promise((resolve, reject) => {
      this.pending.set(id, { resolve, reject });
      this.ws.send(JSON.stringify({ id, method, params }));
    });
  }
  close() {
    this.ws.close();
  }
}

function b64(str) {
  return Buffer.from(str).toString("base64");
}

/** 安装 /api/auth/session 拦截器 —— 返回 mock session 让 useSession 觉得已登录。
 *  /me 路由会调用 next-auth/server,这个对客户端透明 —— 但 /me 页是 server component 包了
 *  client MeContent,如果 server 端 redirect 我们抓 not handled。简单做法:直接 navigate
 *  到 /me 仍可能 redirect,但 hero / 卡片 / vote modal 这些纯 client 用 useSession 都 OK。
 */
async function setupSessionMock(cdp) {
  await cdp.send("Fetch.enable", {
    patterns: [{ urlPattern: "*/api/auth/session*" }],
  });
  cdp.on(async (msg) => {
    if (msg.method !== "Fetch.requestPaused") return;
    const { requestId, request } = msg.params;
    if (request.url.includes("/api/auth/session")) {
      const body = b64(JSON.stringify(MOCK_SESSION));
      await cdp.send("Fetch.fulfillRequest", {
        requestId,
        responseCode: 200,
        responseHeaders: [
          { name: "Content-Type", value: "application/json" },
          { name: "Cache-Control", value: "no-store" },
        ],
        body,
      });
    } else {
      await cdp.send("Fetch.continueRequest", { requestId });
    }
  });
}

async function waitForLoad(cdp, ms = 2000) {
  await wait(ms);
}

async function setLocalStorage(cdp, items) {
  for (const [key, value] of Object.entries(items)) {
    await cdp.send("Runtime.evaluate", {
      expression: `localStorage.setItem(${JSON.stringify(key)}, ${JSON.stringify(value)})`,
    });
  }
}

async function clearLocalStorage(cdp) {
  await cdp.send("Runtime.evaluate", { expression: `localStorage.clear()` });
}

async function navigate(cdp, url, settleMs = 2500) {
  await cdp.send("Page.navigate", { url });
  await waitForLoad(cdp, settleMs);
}

async function pauseVideos(cdp) {
  await cdp.send("Runtime.evaluate", {
    expression: `document.querySelectorAll('video').forEach(v => { try { v.pause(); v.currentTime = 0.5; } catch{} });`,
  });
}

async function screenshotFull(cdp, path) {
  const r = await cdp.send("Page.captureScreenshot", { format: "png" });
  await writeFile(path, Buffer.from(r.data, "base64"));
  console.log(`[ok] ${path}`);
}

async function screenshotElement(cdp, selector, path, padding = 8) {
  const r = await cdp.send("Runtime.evaluate", {
    expression: `(() => {
      const el = document.querySelector(${JSON.stringify(selector)});
      if (!el) return null;
      const r = el.getBoundingClientRect();
      return { x: r.left, y: r.top, width: r.width, height: r.height };
    })()`,
    returnByValue: true,
  });
  if (!r.result || !r.result.value) {
    console.log(`[skip] no element ${selector} for ${path}`);
    return false;
  }
  const b = r.result.value;
  const cap = await cdp.send("Page.captureScreenshot", {
    format: "png",
    clip: {
      x: Math.max(0, b.x - padding),
      y: Math.max(0, b.y - padding),
      width: Math.max(1, b.width + padding * 2),
      height: Math.max(1, b.height + padding * 2),
      scale: 1,
    },
  });
  await writeFile(path, Buffer.from(cap.data, "base64"));
  console.log(`[ok] ${path}  (${selector})`);
  return true;
}

function makeVoteState(ids) {
  return JSON.stringify({ state: { votedArtists: ids }, version: 0 });
}

async function main() {
  await mkdir(OUT_DIR, { recursive: true });
  console.log("[boot] launching Chrome...");
  const pid = await launchChrome();
  console.log(`[boot] Chrome pid=${pid} port=${PORT}`);
  try {
    const target = await openPage();
    const cdp = new CDP(target.webSocketDebuggerUrl);
    await cdp.connect();
    await cdp.send("Page.enable");
    await cdp.send("Network.enable");
    await cdp.send("Runtime.enable");
    await cdp.send("Emulation.setDeviceMetricsOverride", {
      width: 1500,
      height: 900,
      deviceScaleFactor: 1,
      mobile: false,
    });
    await setupSessionMock(cdp);

    // ===== 1. Hero 进度三态(裁切胶囊 + 整图各一份)=====
    // 1a. 0/12 (已登录但未投)
    // user-data-dir 复用会带上上次的 localStorage —— 必须先进 ORIGIN 上下文再 clear
    await navigate(cdp, ORIGIN, 500);
    await clearLocalStorage(cdp);
    await navigate(cdp, ORIGIN);
    await pauseVideos(cdp);
    await wait(800);
    await screenshotFull(cdp, `${OUT_DIR}\\01a-hero-0of12.png`);
    await screenshotElement(
      cdp,
      "[data-hero-vote-progress]",
      `${OUT_DIR}\\01a-progress-0of12.png`,
      12,
    );

    // 1b. 5/12
    await setLocalStorage(cdp, {
      "cyber-star-vote": makeVoteState(["001", "002", "003", "004", "005"]),
    });
    await navigate(cdp, ORIGIN);
    await pauseVideos(cdp);
    await wait(800);
    await screenshotFull(cdp, `${OUT_DIR}\\01b-hero-5of12.png`);
    await screenshotElement(
      cdp,
      "[data-hero-vote-progress]",
      `${OUT_DIR}\\01b-progress-5of12.png`,
      12,
    );

    // 1c. 12/12
    await setLocalStorage(cdp, {
      "cyber-star-vote": makeVoteState([
        "001", "002", "003", "004", "005", "006",
        "007", "008", "009", "010", "011", "012",
      ]),
    });
    await navigate(cdp, ORIGIN);
    await pauseVideos(cdp);
    await wait(800);
    await screenshotFull(cdp, `${OUT_DIR}\\01c-hero-12of12.png`);
    await screenshotElement(
      cdp,
      "[data-hero-vote-progress]",
      `${OUT_DIR}\\01c-progress-12of12.png`,
      12,
    );

    // ===== 2. 艺人卡片角标对比 =====
    // 投了 1/3/5 — 卡片网格里能看到混合态(已投紫框✓ vs 未投灰框)
    await setLocalStorage(cdp, {
      "cyber-star-vote": makeVoteState(["001", "003", "005"]),
    });
    await navigate(cdp, ORIGIN);
    await pauseVideos(cdp);
    await cdp.send("Runtime.evaluate", {
      expression: `document.getElementById('artists')?.scrollIntoView({behavior:'instant',block:'start'}); window.scrollBy(0, 100);`,
    });
    await wait(1000);
    await screenshotFull(cdp, `${OUT_DIR}\\02-artist-cards-mixed.png`);

    // ===== 3. /me 页 =====
    // useSession returns mock session → MeContent 应该正常渲染
    // 但 /me 是 server component:它在 server 端调 auth() — 我们 intercept 仅作用 client。
    // 实际 server component 不会用到 fetch /api/auth/session,它用 cookies 直接验。
    // 没有 next-auth cookie → server redirect。我们尝试,如果失败就截 redirect 后状态。
    await navigate(cdp, `${ORIGIN}/me`, 1500);
    // 检查是否被重定向
    const urlInfo = await cdp.send("Runtime.evaluate", {
      expression: "location.pathname",
      returnByValue: true,
    });
    if (urlInfo.result.value !== "/me") {
      console.log(
        `[note] /me redirected to ${urlInfo.result.value} — server auth() not bypassed`,
      );
      // 在 hash 模式下不会 redirect; 强制 navigate 后端 client only render
      // 退而求其次:直接构造一个空白 page 在客户端 render MeContent — 无法,跳过
    }
    await screenshotFull(cdp, `${OUT_DIR}\\03-me-page.png`);

    // ===== 4. VoteModal 正常态(未投 + 未满) =====
    await setLocalStorage(cdp, {
      "cyber-star-vote": makeVoteState([]),
    });
    await navigate(cdp, ORIGIN);
    await pauseVideos(cdp);
    await wait(1200);
    // 滚到卡片区点第一张卡的投票按钮
    await cdp.send("Runtime.evaluate", {
      expression: `document.getElementById('artists')?.scrollIntoView({behavior:'instant',block:'start'}); window.scrollBy(0, 100);`,
    });
    await wait(500);
    const clicked = await cdp.send("Runtime.evaluate", {
      expression: `(() => {
        const btns = Array.from(document.querySelectorAll('button'));
        const target = btns.find(b => (b.textContent || '').trim() === '投票');
        if (target) { target.click(); return true; }
        return false;
      })()`,
      returnByValue: true,
    });
    console.log(`[note] vote modal trigger: ${clicked.result.value}`);
    await wait(1000);
    await screenshotFull(cdp, `${OUT_DIR}\\04-vote-modal-normal.png`);
    await screenshotElement(
      cdp,
      '[role="dialog"]',
      `${OUT_DIR}\\04-vote-modal-cropped.png`,
      24,
    );

    cdp.close();
    console.log("[done]");
  } finally {
    await killChrome(pid);
  }
}

main().catch((e) => {
  console.error(e);
  process.exit(1);
});
