import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { spawn } from 'node:child_process';

const BASE_URL = process.env.WENFAN_HELP_BASE_URL || 'http://localhost:3000';
const API_BASE = process.env.WENFAN_HELP_API_BASE || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api/v1';
const VIEWPORT = { width: 1440, height: 1000 };
const OUTPUT_DIR = resolve('apps/web/public/help/wenfan-xiaobao');

const AUTH_USER = {
  id: 'm-8',
  name: '超级管理员',
  username: 'admin',
  roleId: 'role-admin',
  departmentId: '',
  email: '',
};

const versionDetailRoute = (context) => (context.versionId ? `/versions/${context.versionId}` : '/versions');

const SCREENSHOTS = [
  { file: 'product-create.png', route: '/products', waitForText: '产品' },
  { file: 'project-create.png', route: '/projects', waitForText: '项目' },
  { file: 'version-create.png', route: '/versions', waitForText: '新建版本' },
  { file: 'requirement-pool.png', route: '/requirements', waitForText: '需求池' },
  { file: 'version-requirement-include.png', route: versionDetailRoute, tabLabel: '关联需求', waitForText: '添加需求' },
  { file: 'research-plan.png', route: versionDetailRoute, tabLabel: '调研', waitForText: '调研' },
  { file: 'product-plan.png', route: versionDetailRoute, tabLabel: '产品方案', waitForText: '产品方案' },
  { file: 'ai-decompose.png', route: versionDetailRoute, tabLabel: '产品方案', waitForText: 'AI 拆解', action: openProductPlanAiEntry, scrollText: 'AI 拆解' },
  { file: 'ui-design-plan.png', route: versionDetailRoute, tabLabel: 'UI设计', waitForText: 'UI设计' },
  { file: 'dev-task-workflow.png', route: versionDetailRoute, tabLabel: '开发任务', waitForText: '开发任务' },
  { file: 'test-case-workflow.png', route: versionDetailRoute, tabLabel: '测试用例', waitForText: '测试用例' },
  { file: 'bug-workflow.png', route: versionDetailRoute, tabLabel: '测试用例', waitForText: '提 BUG', action: openFailedTestCaseBugEntry },
  { file: 'activity-logs.png', route: versionDetailRoute, tabLabel: '产品方案', waitForText: '日志', scrollText: '日志' },
  { file: 'xiaobao-warning.png', route: '/xiaobao-warning', waitForText: '小宝预警' },
];

function findBrowser() {
  const candidates = [
    process.env.CHROME_PATH,
    'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
    'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
    'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
    'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
    '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
    '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
    '/usr/bin/google-chrome',
    '/usr/bin/chromium',
    '/usr/bin/chromium-browser',
  ].filter(Boolean);

  return candidates.find((candidate) => existsSync(candidate));
}

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

async function loadDataKey(key) {
  try {
    const response = await fetch(`${API_BASE}/data/${key}`);
    if (!response.ok) return null;
    const payload = await response.json();
    return payload.value ?? null;
  } catch {
    return null;
  }
}

function toArray(value) {
  return Array.isArray(value) ? value : [];
}

function findPreferredVersionId(overview, testCases, versionPlans) {
  const versions = Array.isArray(overview)
    ? overview.flatMap((product) => (Array.isArray(product.versions) ? product.versions : []))
    : [];
  const failedVersionIds = new Set(
    toArray(testCases)
      .filter((testCase) => testCase.status === 'failed' && testCase.versionId)
      .map((testCase) => testCase.versionId),
  );
  const aiReadyVersionIds = new Set(
    toArray(versionPlans)
      .filter((plan) => plan.type === 'product' && plan.status === 'completed' && plan.resultUrl && plan.versionId)
      .map((plan) => plan.versionId),
  );

  const scoreVersion = (version) => {
    let score = 0;
    if (!['released', 'closed'].includes(version.status)) score += 10;
    if (version.status === 'developing') score += 4;
    if (failedVersionIds.has(version.id)) score += 3;
    if (aiReadyVersionIds.has(version.id)) score += 2;
    return score;
  };

  return [...versions].sort((a, b) => scoreVersion(b) - scoreVersion(a))[0]?.id ?? null;
}

function findFailedTestCase(testCases, versionId) {
  return toArray(testCases)
    .filter((testCase) => testCase.versionId === versionId && testCase.status === 'failed')
    .sort((a, b) => {
      const roundDiff = (a.roundNo ?? 1) - (b.roundNo ?? 1);
      if (roundDiff !== 0) return roundDiff;
      return String(a.caseNo ?? a.title ?? '').localeCompare(String(b.caseNo ?? b.title ?? ''));
    })[0] ?? null;
}

function findAiReadyProductPlan(versionPlans, versionId) {
  return toArray(versionPlans)
    .filter((plan) => (
      plan.versionId === versionId
      && plan.type === 'product'
      && plan.status === 'completed'
      && plan.resultUrl
    ))
    .sort((a, b) => {
      const httpDiff = Number(String(b.resultUrl).startsWith('http')) - Number(String(a.resultUrl).startsWith('http'));
      if (httpDiff !== 0) return httpDiff;
      return String(b.completedAt ?? b.createdAt ?? '').localeCompare(String(a.completedAt ?? a.createdAt ?? ''));
    })[0] ?? null;
}

async function resolveScreenshotContext() {
  const [overview, testCases, versionPlans] = await Promise.all([
    loadDataKey('products-overview'),
    loadDataKey('test-cases'),
    loadDataKey('version-plans'),
  ]);
  const versionId = findPreferredVersionId(overview, testCases, versionPlans);
  if (!versionId) {
    console.warn('未找到可进入的版本详情，版本内截图将退回版本列表。');
  }

  const failedTestCase = findFailedTestCase(testCases, versionId);
  if (!failedTestCase) {
    console.warn('未找到失败/不通过测试用例，Bug 配图将退回测试用例列表。');
  }

  const productPlan = findAiReadyProductPlan(versionPlans, versionId);
  if (!productPlan) {
    console.warn('未找到已完成且有成果链接的产品方案，AI 拆解配图可能无法展示按钮。');
  }

  return { versionId, failedTestCase, productPlan };
}

async function assertServerAvailable() {
  try {
    const response = await fetch(BASE_URL);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
  } catch (error) {
    throw new Error(`本地页面不可访问：${BASE_URL}。请先启动前端 dev server。`);
  }
}

function launchBrowser(browserPath, userDataDir) {
  const child = spawn(
    browserPath,
    [
      '--headless=new',
      '--disable-gpu',
      '--no-first-run',
      '--no-default-browser-check',
      '--remote-debugging-port=0',
      `--user-data-dir=${userDataDir}`,
      `--window-size=${VIEWPORT.width},${VIEWPORT.height}`,
      'about:blank',
    ],
    { stdio: ['ignore', 'pipe', 'pipe'] },
  );

  return child;
}

function waitForDevtools(child) {
  return new Promise((resolveDevtools, rejectDevtools) => {
    const timer = setTimeout(() => rejectDevtools(new Error('Chrome DevTools 启动超时。')), 15000);

    function onData(data) {
      const text = data.toString();
      const match = text.match(/DevTools listening on (ws:\/\/[^\s]+)/);
      if (!match) return;
      clearTimeout(timer);
      child.stdout.off('data', onData);
      child.stderr.off('data', onData);
      resolveDevtools(match[1]);
    }

    child.stdout.on('data', onData);
    child.stderr.on('data', onData);
    child.once('exit', (code) => {
      clearTimeout(timer);
      rejectDevtools(new Error(`浏览器提前退出，退出码：${code}`));
    });
  });
}

async function getPageWebSocketUrl(wsEndpoint) {
  const url = new URL(wsEndpoint);
  const listUrl = `http://${url.hostname}:${url.port}/json/list`;

  for (let i = 0; i < 30; i += 1) {
    const response = await fetch(listUrl);
    const targets = await response.json();
    const page = targets.find((target) => target.type === 'page' && target.webSocketDebuggerUrl);
    if (page) return page.webSocketDebuggerUrl;
    await delay(200);
  }

  throw new Error('没有找到可截图的 Chrome 页面 target。');
}

class CdpClient {
  constructor(webSocketUrl) {
    this.webSocketUrl = webSocketUrl;
    this.nextId = 1;
    this.pending = new Map();
    this.eventWaiters = new Map();
  }

  async connect() {
    this.ws = new WebSocket(this.webSocketUrl);
    this.ws.addEventListener('message', (event) => this.handleMessage(event.data));
    await new Promise((resolveOpen, rejectOpen) => {
      this.ws.addEventListener('open', resolveOpen, { once: true });
      this.ws.addEventListener('error', rejectOpen, { once: true });
    });
  }

  handleMessage(raw) {
    const message = JSON.parse(raw);
    if (message.id && this.pending.has(message.id)) {
      const { resolve: resolvePending, reject } = this.pending.get(message.id);
      this.pending.delete(message.id);
      if (message.error) reject(new Error(message.error.message));
      else resolvePending(message.result);
      return;
    }

    if (message.method && this.eventWaiters.has(message.method)) {
      const waiters = this.eventWaiters.get(message.method);
      this.eventWaiters.delete(message.method);
      for (const waiter of waiters) waiter(message.params);
    }
  }

  send(method, params = {}) {
    const id = this.nextId;
    this.nextId += 1;
    const payload = JSON.stringify({ id, method, params });

    return new Promise((resolveSend, rejectSend) => {
      this.pending.set(id, { resolve: resolveSend, reject: rejectSend });
      this.ws.send(payload);
    });
  }

  waitForEvent(method, timeoutMs = 15000) {
    return new Promise((resolveEvent, rejectEvent) => {
      const timer = setTimeout(() => rejectEvent(new Error(`等待 ${method} 超时。`)), timeoutMs);
      const waiters = this.eventWaiters.get(method) || [];
      waiters.push((params) => {
        clearTimeout(timer);
        resolveEvent(params);
      });
      this.eventWaiters.set(method, waiters);
    });
  }

  close() {
    this.ws?.close();
  }
}

async function navigateAndWait(client, url) {
  const loadPromise = client.waitForEvent('Page.loadEventFired').catch(() => undefined);
  await client.send('Page.navigate', { url });
  await loadPromise;
  await delay(1200);
}

async function evaluateValue(client, expression) {
  const result = await client.send('Runtime.evaluate', {
    expression,
    awaitPromise: true,
    returnByValue: true,
  });
  if (result.exceptionDetails) {
    throw new Error(result.exceptionDetails.text || '页面脚本执行失败。');
  }
  return result.result?.value;
}

async function waitForCondition(client, expression, timeoutMs = 10000) {
  const start = Date.now();
  while (Date.now() - start < timeoutMs) {
    const matched = await evaluateValue(client, expression).catch(() => false);
    if (matched) return true;
    await delay(250);
  }
  return false;
}

async function waitForPageText(client, text, timeoutMs = 10000) {
  return waitForCondition(
    client,
    `document.body && document.body.innerText.includes(${JSON.stringify(text)})`,
    timeoutMs,
  );
}

async function clickButtonByText(client, text) {
  return evaluateValue(
    client,
    `(() => {
      const target = ${JSON.stringify(text)};
      const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
      const buttons = Array.from(document.querySelectorAll('button,[role="button"]'));
      const button = buttons.find((node) => {
        const label = normalize(node.textContent);
        return label === target || label.includes(target);
      });
      if (!button) return false;
      button.scrollIntoView({ block: 'center', inline: 'center' });
      button.click();
      return true;
    })()`,
  );
}

async function clickNearestClickableByText(client, text) {
  return evaluateValue(
    client,
    `(() => {
      const target = ${JSON.stringify(text)};
      const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
      const clickables = Array.from(document.querySelectorAll('button,a,[role="button"],[class*="cursor-pointer"]'));
      let node = clickables.find((element) => normalize(element.textContent).includes(target));
      if (!node) {
        const textNodes = Array.from(document.querySelectorAll('h1,h2,h3,p,span,div'))
          .filter((element) => normalize(element.textContent).length < 240);
        node = textNodes
          .map((element) => element.closest('button,a,[role="button"],[class*="cursor-pointer"]'))
          .find((element) => element && normalize(element.textContent).includes(target));
      }
      if (!node) return false;
      node.scrollIntoView({ block: 'center', inline: 'center' });
      node.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
      return true;
    })()`,
  );
}

async function scrollTextIntoView(client, text) {
  return evaluateValue(
    client,
    `(() => {
      const target = ${JSON.stringify(text)};
      const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
      const nodes = Array.from(document.querySelectorAll('button,a,h1,h2,h3,p,span,div'));
      const node = nodes.find((element) => normalize(element.textContent).includes(target));
      if (!node) return false;
      node.scrollIntoView({ block: 'center', inline: 'center' });
      return true;
    })()`,
  );
}

async function filterTestCaseList(client, keyword) {
  if (!keyword) return false;
  return evaluateValue(
    client,
    `(() => {
      const keyword = ${JSON.stringify(keyword)};
      const inputs = Array.from(document.querySelectorAll('input'));
      const input = inputs.find((node) => {
        const placeholder = node.getAttribute('placeholder') || '';
        return placeholder.includes('搜索') || placeholder.includes('标题') || placeholder.includes('编号');
      }) || inputs[0];
      if (!input) return false;
      const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
      input.focus();
      if (setter) setter.call(input, keyword);
      else input.value = keyword;
      input.dispatchEvent(new Event('input', { bubbles: true }));
      input.dispatchEvent(new Event('change', { bubbles: true }));
      return true;
    })()`,
  );
}

async function scrollBugButtonIntoView(client) {
  return evaluateValue(
    client,
    `(() => {
      const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
      const button = Array.from(document.querySelectorAll('button')).find((node) => {
        const text = normalize(node.textContent);
        return text.includes('提') && text.toUpperCase().includes('BUG');
      });
      if (!button) return false;
      button.scrollIntoView({ block: 'center', inline: 'center' });
      return true;
    })()`,
  );
}

async function openProductPlanAiEntry(client, context) {
  if (!context.productPlan) return;

  const opened = await clickNearestClickableByText(client, context.productPlan.title);
  if (!opened) {
    console.warn(`未打开产品方案：${context.productPlan.title}`);
    return;
  }

  await delay(800);
  const found = await waitForPageText(client, 'AI 拆解', 5000);
  if (!found) {
    console.warn(`已打开产品方案 ${context.productPlan.title}，但未找到 AI 拆解入口。`);
  }
  await scrollTextIntoView(client, 'AI 拆解');
}

async function openFailedTestCaseBugEntry(client, context) {
  const failedTestCase = context.failedTestCase;
  if (failedTestCase) {
    await filterTestCaseList(client, failedTestCase.caseNo || failedTestCase.title);
    await delay(700);
  }

  const opened = await evaluateValue(
    client,
    `(() => {
      const needles = ${JSON.stringify(
        [context.failedTestCase?.caseNo, context.failedTestCase?.title].filter(Boolean),
      )};
      const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
      const rows = Array.from(document.querySelectorAll('[class*="cursor-pointer"],[role="button"],button'));
      const row = rows.find((node) => {
        const text = normalize(node.textContent);
        if (needles.length > 0) return needles.some((needle) => text.includes(needle));
        return text.includes('TC-') && text.includes('不通过');
      });
      if (!row) return false;
      row.scrollIntoView({ block: 'center', inline: 'center' });
      row.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
      return true;
    })()`,
  );

  if (!opened) {
    console.warn('未找到失败/不通过的测试用例行，Bug 配图将停留在测试用例列表。');
    await scrollTextIntoView(client, '不通过');
    return;
  }

  await waitForCondition(
    client,
    `document.body && Array.from(document.querySelectorAll('button')).some((node) => {
      const text = (node.textContent || '').replace(/\\s+/g, ' ').trim();
      return text.includes('提') && text.toUpperCase().includes('BUG');
    })`,
    5000,
  );
  await scrollBugButtonIntoView(client);
}

async function authenticate(client) {
  await navigateAndWait(client, `${BASE_URL}/login`);
  const serialized = JSON.stringify(AUTH_USER).replace(/\\/g, '\\\\').replace(/'/g, "\\'");
  await client.send('Runtime.evaluate', {
    expression: `
      sessionStorage.setItem('ftb_auth_session', '${serialized}');
      localStorage.setItem('ftb_auth_persist', '${serialized}');
    `,
  });
}

async function prepareScreenshot(client, item, context) {
  if (item.tabLabel) {
    const clicked = await clickButtonByText(client, item.tabLabel);
    if (!clicked) {
      console.warn(`未找到 Tab：${item.tabLabel}`);
    }
    await delay(800);
  }

  if (item.action) {
    await item.action(client, context);
    await delay(600);
  }

  if (item.scrollText) {
    await scrollTextIntoView(client, item.scrollText);
    await delay(400);
  }

  if (item.waitForText) {
    const found = await waitForPageText(client, item.waitForText, 8000);
    if (!found) {
      console.warn(`截图前未在页面找到文本：${item.waitForText}`);
    }
  }
}

async function capture(client, item, context, outputPath) {
  const route = typeof item.route === 'function' ? item.route(context) : item.route;
  await navigateAndWait(client, `${BASE_URL}${route}`);
  await client.send('Emulation.setDeviceMetricsOverride', {
    width: VIEWPORT.width,
    height: VIEWPORT.height,
    deviceScaleFactor: 1,
    mobile: false,
  });
  await prepareScreenshot(client, item, context);
  const screenshot = await client.send('Page.captureScreenshot', {
    format: 'png',
    fromSurface: true,
    captureBeyondViewport: false,
  });
  writeFileSync(outputPath, Buffer.from(screenshot.data, 'base64'));
}

async function terminateBrowser(browser) {
  if (browser.exitCode !== null) return;

  const exited = new Promise((resolveExit) => {
    browser.once('exit', resolveExit);
  });
  browser.kill();
  await Promise.race([exited, delay(2000)]);
}

async function main() {
  const browserPath = findBrowser();
  if (!browserPath) {
    throw new Error('未找到 Chrome 或 Edge，请安装浏览器，或通过 CHROME_PATH 指定浏览器路径。');
  }

  await assertServerAvailable();
  const context = await resolveScreenshotContext();
  mkdirSync(OUTPUT_DIR, { recursive: true });

  const userDataDir = join(tmpdir(), `wenfan-help-screenshots-${Date.now()}`);
  const browser = launchBrowser(browserPath, userDataDir);
  let client;

  try {
    const browserWsEndpoint = await waitForDevtools(browser);
    const pageWsEndpoint = await getPageWebSocketUrl(browserWsEndpoint);
    client = new CdpClient(pageWsEndpoint);
    await client.connect();
    await client.send('Page.enable');
    await client.send('Runtime.enable');
    await authenticate(client);

    for (const item of SCREENSHOTS) {
      const outputPath = join(OUTPUT_DIR, item.file);
      await capture(client, item, context, outputPath);
      console.log(`saved ${outputPath}`);
    }
  } finally {
    client?.close();
    await terminateBrowser(browser);
    try {
      rmSync(userDataDir, { recursive: true, force: true });
    } catch {
      console.warn(`临时浏览器目录未能立即清理，可稍后手动删除：${userDataDir}`);
    }
  }
}

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