import { readFile } from 'node:fs/promises';
import path from 'node:path';

import { describe, expect, it } from 'vitest';

const SERVER_PATH = path.join(import.meta.dirname, 'server.ts');
const SEARCH_PLAN_PATH = path.join(import.meta.dirname, '..', 'playwright', 'search-plan.js');

async function readRunSearchTaskBody() {
  const source = await readFile(SERVER_PATH, 'utf8');
  const start = source.indexOf('async function runSearchTask');
  const end = source.indexOf('function readSearchTaskId', start);

  expect(start).toBeGreaterThanOrEqual(0);
  expect(end).toBeGreaterThan(start);

  return source.slice(start, end);
}

function extractFunctionSource(source: string, functionName: string) {
  const start = source.indexOf(`function ${functionName}`);
  expect(start).toBeGreaterThanOrEqual(0);

  const bodyStart = source.indexOf('{', start);
  expect(bodyStart).toBeGreaterThan(start);

  let depth = 0;
  for (let index = bodyStart; index < source.length; index += 1) {
    const char = source[index];
    if (char === '{') {
      depth += 1;
    } else if (char === '}') {
      depth -= 1;
      if (depth === 0) {
        return source.slice(start, index + 1);
      }
    }
  }

  throw new Error(`未能提取函数 ${functionName}`);
}

describe('search task login handoff', () => {
  it('lets the search child own 1688 login warmup instead of spawning the standalone login script first', async () => {
    const runSearchTaskBody = await readRunSearchTaskBody();

    expect(runSearchTaskBody).not.toContain('LOGIN_1688_SCRIPT');
    expect(runSearchTaskBody).not.toContain('1688 账户登录');
    expect(runSearchTaskBody).toContain('ROW_PIPELINE_SYNC_SCRIPT');
    expect(runSearchTaskBody).toContain('正在检查登录态并准备搜索');
  });

  it('serializes same-account search execution and passes the long-lived account profile to children', async () => {
    const source = await readFile(SERVER_PATH, 'utf8');
    const runSearchTaskBody = await readRunSearchTaskBody();

    expect(source).toContain('from "./search-account-lock.ts"');
    expect(source).toContain('from "../playwright/profile-dir.js"');
    expect(runSearchTaskBody).toContain('withSearchAccountExecutionLock(account.id');
    expect(runSearchTaskBody).not.toContain('prepareSearchTaskRuntimeProfile');
    expect(runSearchTaskBody).not.toContain('syncSearchTaskRuntimeProfileToAccount');
    expect(runSearchTaskBody).toContain('resolve1688AccountProfileDir');
    expect(runSearchTaskBody).toContain('accountProfileDir: accountProfileDir');
    expect(runSearchTaskBody).toContain('outputRoot: runtimePaths.outputRoot');
    expect(runSearchTaskBody).toContain('等待 1688 账户空闲');
  });

  it('does not copy or sync the profile after the search child confirms login warmup', async () => {
    const source = await readFile(SERVER_PATH, 'utf8');
    const searchPlanSource = await readFile(SEARCH_PLAN_PATH, 'utf8');

    expect(searchPlanSource).toContain('login_session_ready');
    expect(source).toContain('type !== "login_session_ready"');

    const applyStart = source.indexOf('async function applySearchChildEvent');
    const applyEnd = source.indexOf('async function runManagedSearchChild', applyStart);
    expect(applyStart).toBeGreaterThanOrEqual(0);
    expect(applyEnd).toBeGreaterThan(applyStart);

    const applyBody = source.slice(applyStart, applyEnd);
    const loginEventIndex = applyBody.indexOf('event.type === "login_session_ready"');

    expect(loginEventIndex).toBeGreaterThanOrEqual(0);
    expect(applyBody.slice(loginEventIndex)).not.toContain('syncSearchTaskRuntimeProfileToAccount');
  });

  it('uses a dedicated visible verification helper after a headless captcha handoff', async () => {
    const source = await readFile(SERVER_PATH, 'utf8');
    const runSearchTaskBody = await readRunSearchTaskBody();

    expect(source).toContain('HEADLESS_MANUAL_VERIFICATION_HANDOFF');
    expect(source).toContain('MANUAL_VERIFICATION_SESSION_SCRIPT');
    expect(runSearchTaskBody).toContain('isHeadlessManualVerificationHandoffError');
    expect(runSearchTaskBody).toContain('runManualVerificationHelper');
    expect(runSearchTaskBody).toContain('childResult = await runItemSearchChild(runState.task.headless)');
    expect(runSearchTaskBody).not.toContain('runItemSearchChild(false)');
  });

  it('does not fail a material after a fixed number of captcha handoffs', async () => {
    const source = await readFile(SERVER_PATH, 'utf8');
    const runSearchTaskBody = await readRunSearchTaskBody();

    expect(source).not.toContain('SEARCH_MANUAL_VERIFICATION_MAX_HANDOFFS_PER_ITEM');
    expect(runSearchTaskBody).not.toContain('验证码接管次数超过上限');
  });

  it('keeps the visible verification helper open until captcha resolution instead of timing out', async () => {
    const manualSessionSource = await readFile(path.join(import.meta.dirname, '..', 'playwright', 'manual-verification-session.js'), 'utf8');

    expect(manualSessionSource).not.toContain('验证码人工处理超时');
    expect(manualSessionSource).not.toContain('Date.now() - startedAt > timeoutMs');
  });

  it('lets progress updates return to processing after manual intervention is explicitly cleared', async () => {
    const source = await readFile(SERVER_PATH, 'utf8');

    expect(source).toContain('readManualInterventionActive(result) === false');
    expect(source).toContain('const manualInterventionActive = readManualInterventionActive(resultWithoutStaleManualIntervention) === true');
    expect(source).toContain('manualInterventionActive');
    expect(source).toContain('event.type === "manual_intervention_resolved"');
    expect(source).toContain('result: clearManualInterventionResult(runState, buildManualInterventionResult(runState, event, false), event)');
  });

  it('clears stale manual intervention state when normal progress resumes', async () => {
    const source = await readFile(SERVER_PATH, 'utf8');

    expect(source).toContain('clearManualInterventionResult');
    expect(source).toContain('const resultWithoutStaleManualIntervention = clearManualInterventionResult');
    expect(source).toContain('readManualInterventionActive(resultWithoutStaleManualIntervention) === true');
  });

  it('explicitly clears manual intervention before resuming headless search after helper verification', async () => {
    const runSearchTaskBody = await readRunSearchTaskBody();

    const helperIndex = runSearchTaskBody.indexOf('const helperResult = await runManualVerificationHelper');
    const clearIndex = runSearchTaskBody.indexOf('result: clearManualInterventionResult', helperIndex);
    const resumeIndex = runSearchTaskBody.indexOf('验证码已处理，恢复无头搜索', helperIndex);

    expect(helperIndex).toBeGreaterThanOrEqual(0);
    expect(clearIndex).toBeGreaterThan(helperIndex);
    expect(resumeIndex).toBeGreaterThan(clearIndex);
  });

  it('makes login warmup manual verification trigger the existing headless handoff path', async () => {
    const searchPlanSource = await readFile(SEARCH_PLAN_PATH, 'utf8');

    expect(searchPlanSource).toContain('loginWarmup.status === "manual_verification_required"');
    expect(searchPlanSource).toContain('manual_intervention_required');
    expect(searchPlanSource).toContain('pageUrl: String(page?.url?.() ?? "")');
    expect(searchPlanSource).toContain('HEADLESS_MANUAL_VERIFICATION_HANDOFF');
  });

  it('summarizes nested login warmup errors instead of showing only exit 1', async () => {
    const source = await readFile(SERVER_PATH, 'utf8');
    const functionSource = extractFunctionSource(source, 'summarizeSearchFailureMessage')
      .replace(/: string/g, '');
    const summarizeSearchFailureMessage = new Function(`${functionSource}; return summarizeSearchFailureMessage;`)() as (message: string) => string;

    expect(summarizeSearchFailureMessage([
      '物料 a7dd0b25 搜索 执行失败: exit 1',
      '[stderr]',
      'Error: 搜索前登录态预热暂停：当前页面需要手机短信验证码',
    ].join('\n'))).toBe('搜索前登录态预热暂停：当前页面需要手机短信验证码');
  });

  it('summarizes nested headed-mode and browser runtime errors instead of showing only exit 1', async () => {
    const source = await readFile(SERVER_PATH, 'utf8');
    const functionSource = extractFunctionSource(source, 'summarizeSearchFailureMessage')
      .replace(/: string/g, '');
    const summarizeSearchFailureMessage = new Function(`${functionSource}; return summarizeSearchFailureMessage;`)() as (message: string) => string;

    expect(summarizeSearchFailureMessage([
      '物料 dca98140 搜索 执行失败: exit 1',
      '[stdout]',
      '首轮 1688 文字搜索 执行失败: exit 1',
      '[stderr]',
      'Error: 有头模式人工验证未完成：浏览器页面已关闭',
    ].join('\n'))).toBe('有头模式人工验证未完成：浏览器页面已关闭');

    expect(summarizeSearchFailureMessage([
      '物料 dca98140 搜索 执行失败: exit 1',
      '[stderr]',
      'page.goto: Timeout 20000ms exceeded.',
    ].join('\n'))).toBe('页面操作超时：page.goto 超过 20000ms');

    expect(summarizeSearchFailureMessage([
      '物料 dca98140 搜索 执行失败: exit 1',
      '[stderr]',
      'browserType.launchPersistentContext: Target page, context or browser has been closed',
    ].join('\n'))).toBe('浏览器页面已关闭，可能是人工验证窗口被关闭');
  });

  it('writes a diagnostic run summary when login warmup fails before the first search round', async () => {
    const searchPlanSource = await readFile(SEARCH_PLAN_PATH, 'utf8');

    expect(searchPlanSource).toContain('writeLoginWarmupFailureSummary');
    expect(searchPlanSource).toContain('login_warmup_failed');
    expect(searchPlanSource).toContain('round_failure_reason: "login_intercept"');
  });

  it('does not let search-stage manual handoff be swallowed as an empty round', async () => {
    const searchPlanSource = await readFile(SEARCH_PLAN_PATH, 'utf8');

    expect(searchPlanSource).toContain('isHeadlessManualVerificationHandoffError');
    expect(searchPlanSource).toContain('throw createHeadlessManualVerificationHandoffError');
    expect(searchPlanSource).toContain('writeSearchStageManualHandoffSummary');
    expect(searchPlanSource).toContain('manual_handoff_required');
    expect(searchPlanSource).toContain('throw error;');
  });

  it('recovers fixed-runtime login state before handling captcha on paginated results', async () => {
    const searchPlanSource = await readFile(SEARCH_PLAN_PATH, 'utf8');
    const extractStart = searchPlanSource.indexOf('async function extractResultsAcrossPages');
    const extractEnd = searchPlanSource.indexOf('async function saveImage', extractStart);
    expect(extractStart).toBeGreaterThanOrEqual(0);
    expect(extractEnd).toBeGreaterThan(extractStart);

    const extractBody = searchPlanSource.slice(extractStart, extractEnd);
    const gotoIndex = extractBody.indexOf('await page.goto(nextUrl');
    const recoveryIndex = extractBody.indexOf('recoverFixedRuntimeLoginStateBeforeRetry', gotoIndex);
    const captchaIndex = extractBody.indexOf('[fixed-runtime][验证码]', gotoIndex);

    expect(searchPlanSource).toContain('async function recoverFixedRuntimeLoginStateBeforeRetry');
    expect(searchPlanSource).toContain('pageLooksLikeLoggedOutSearchState');
    expect(searchPlanSource).toContain('const loginWarmup = await ensure1688LoggedIn');
    expect(searchPlanSource).toContain('emitLoginSessionReady(recoveredPage)');
    expect(searchPlanSource).toContain('await recoveredPage.goto(targetUrl');
    expect(gotoIndex).toBeGreaterThanOrEqual(0);
    expect(recoveryIndex).toBeGreaterThan(gotoIndex);
    expect(captchaIndex).toBeGreaterThan(recoveryIndex);
  });

  it('summarizes runtime run-summary login interception before no-search-candidates', async () => {
    const source = await readFile(SERVER_PATH, 'utf8');

    expect(source).toContain('collectRunSummaryRoundFailureReasons');
    expect(source).toContain('login_intercept');
    expect(source).toContain('搜索完成但未获得有效商家：1688 登录态失效或被登录页拦截');
  });
});
