import { setTimeout as sleep } from "node:timers/promises";

const STEP_DELAY_MIN_MS = 500;
const STEP_DELAY_MAX_MS = 2000;
const VERIFICATION_SCAN_INTERVAL_MS = 1200;
const VERIFICATION_RETRY_COOLDOWN_MS = 4000;
const VERIFICATION_TEXT_PATTERN = /验证码拦截|拖动下方滑块完成验证|请按住滑块|通过验证以确保正常访问|验证失败|二次验证|拖动到最右边|滑动验证|x5step=1|\/punish\b/i;
const SLIDER_HANDLE_SELECTORS = [
  "#scratch-captcha-btn",
  "#scratch-captcha-btn-icon",
  "#nc_1_n1z",
  "[id^='nc_'][id$='_n1z']",
  ".nc_wrapper .btn_slide",
  ".nc_scale .btn_slide",
  ".btn_slide"
];
const SLIDER_TRACK_SELECTORS = [
  ".scratch-captcha-slider",
  ".scratch-captcha-container",
  "#nc_1__scale",
  "[id^='nc_'][id*='__scale']",
  ".nc_wrapper .nc_scale",
  ".nc_scale"
];
const verificationStates = new WeakMap();

export function randomBetween(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function normalizeDelayRange(min = STEP_DELAY_MIN_MS, max = STEP_DELAY_MAX_MS) {
  const parsedMin = Number.isFinite(min) ? Math.round(min) : STEP_DELAY_MIN_MS;
  const parsedMax = Number.isFinite(max) ? Math.round(max) : STEP_DELAY_MAX_MS;
  const lower = Math.min(parsedMin, parsedMax);
  const upper = Math.max(parsedMin, parsedMax);

  if (upper <= STEP_DELAY_MIN_MS) {
    return { min: STEP_DELAY_MIN_MS, max: STEP_DELAY_MIN_MS };
  }

  if (lower >= STEP_DELAY_MAX_MS) {
    return { min: STEP_DELAY_MAX_MS, max: STEP_DELAY_MAX_MS };
  }

  return {
    min: Math.max(STEP_DELAY_MIN_MS, lower),
    max: Math.max(STEP_DELAY_MIN_MS, Math.min(STEP_DELAY_MAX_MS, upper))
  };
}

function normalizeText(value) {
  return String(value ?? "").replace(/\s+/g, " ").trim();
}

async function readBodyText(frame) {
  try {
    return normalizeText(await frame.locator("body").innerText({ timeout: 1200 }));
  } catch {
    try {
      return normalizeText(await frame.evaluate(() => document.body?.innerText ?? ""));
    } catch {
      return "";
    }
  }
}

async function findVisibleLocator(frame, selectors) {
  for (const selector of selectors) {
    const locator = frame.locator(selector).first();
    const count = await locator.count().catch(() => 0);

    if (count === 0) {
      continue;
    }

    if (await locator.isVisible().catch(() => false)) {
      return locator;
    }
  }

  return null;
}

async function detectVerificationTarget(page) {
  if (!page || page.isClosed()) {
    return null;
  }

  for (const frame of page.frames()) {
    const frameUrl = String(frame.url?.() ?? "");
    const bodyText = await readBodyText(frame);
    const pageLooksBlocked = VERIFICATION_TEXT_PATTERN.test(frameUrl) || VERIFICATION_TEXT_PATTERN.test(bodyText);
    const handle = await findVisibleLocator(frame, SLIDER_HANDLE_SELECTORS);
    const track = await findVisibleLocator(frame, SLIDER_TRACK_SELECTORS);

    if (!handle || !track) {
      if (pageLooksBlocked) {
        return {
          frame,
          handle: null,
          track: null,
          handleBox: null,
          trackBox: null,
          blockedOnly: true
        };
      }
      continue;
    }

    const handleBox = await handle.boundingBox().catch(() => null);
    const trackBox = await track.boundingBox().catch(() => null);
    if (!handleBox || !trackBox || trackBox.width <= handleBox.width || trackBox.width < 120) {
      continue;
    }

    return {
      frame,
      handle,
      track,
      handleBox,
      trackBox,
      blockedOnly: false
    };
  }

  return null;
}

async function dragSliderLikeHuman(page, target) {
  const { handleBox, trackBox } = target;
  const startX = handleBox.x + handleBox.width / 2;
  const startY = handleBox.y + handleBox.height / 2 + randomBetween(-2, 2);
  const trackRight = trackBox.x + trackBox.width;
  const rightPadding = Math.max(10, Math.round(handleBox.width * 0.4));
  const targetX = Math.max(startX + 80, Math.min(trackRight - rightPadding, trackRight - 6));
  const checkpoints = [0.18, 0.42, 0.68, 0.9, 1];

  await page.mouse.move(Math.max(0, startX - randomBetween(12, 24)), startY + randomBetween(-4, 4), {
    steps: randomBetween(8, 16)
  });
  await randomPause(60, 140);
  await page.mouse.move(startX, startY, { steps: randomBetween(4, 8) });
  await randomPause(80, 180);
  await page.mouse.down();

  for (const checkpoint of checkpoints) {
    const nextX = startX + (targetX - startX) * checkpoint + (checkpoint < 1 ? randomBetween(-6, 6) : 0);
    const nextY = startY + randomBetween(-3, 3);
    await page.mouse.move(nextX, nextY, { steps: randomBetween(10, 22) });
    if (checkpoint < 1) {
      await randomPause(40, 120);
    }
  }

  const overshootX = Math.min(trackRight - 4, targetX + randomBetween(4, 10));
  if (overshootX > targetX + 1) {
    await page.mouse.move(overshootX, startY + randomBetween(-2, 2), { steps: randomBetween(4, 8) });
    await randomPause(40, 120);
    await page.mouse.move(targetX, startY + randomBetween(-2, 2), { steps: randomBetween(3, 6) });
  }

  await randomPause(80, 180);
  await page.mouse.up();
}

function getVerificationState(page) {
  const current = verificationStates.get(page);
  if (current) {
    return current;
  }

  const next = {
    running: false,
    lastAttemptAt: 0
  };
  verificationStates.set(page, next);
  return next;
}

export async function ensureSlideVerification(page, options = {}) {
  if (!page || page.isClosed()) {
    return false;
  }

  const state = getVerificationState(page);
  if (state.running) {
    return false;
  }

  const currentTarget = await detectVerificationTarget(page);
  if (!currentTarget) {
    return false;
  }

  const now = Date.now();
  if (now - state.lastAttemptAt < VERIFICATION_RETRY_COOLDOWN_MS) {
    return false;
  }

  const logPrefix = options.logPrefix ?? "[验证码]";
  const maxAttempts = Number.isInteger(options.maxAttempts) && options.maxAttempts > 0 ? options.maxAttempts : 2;

  state.running = true;
  state.lastAttemptAt = now;

  try {
    if (!currentTarget.handle || !currentTarget.track || !currentTarget.handleBox || !currentTarget.trackBox) {
      console.warn(`${logPrefix} 检测到验证页，但暂未定位到滑块控件，稍后重试。`);
      return false;
    }

    console.log(`${logPrefix} 检测到滑块验证，开始自动拖动。`);

    for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
      const target = attempt === 1 ? currentTarget : await detectVerificationTarget(page);
      if (!target) {
        return true;
      }

      if (!target.handle || !target.track || !target.handleBox || !target.trackBox) {
        console.warn(`${logPrefix} 第 ${attempt} 次尝试前未找到可拖动滑块，等待下次重试。`);
        return false;
      }

      await dragSliderLikeHuman(page, target);
      await Promise.race([
        page.waitForLoadState("domcontentloaded", { timeout: 4000 }).catch(() => {}),
        sleep(1200)
      ]);
      await randomPause(500, 900);

      if (!(await detectVerificationTarget(page))) {
        console.log(`${logPrefix} 第 ${attempt} 次拖动后验证已解除。`);
        return true;
      }
    }

    console.warn(`${logPrefix} 自动拖动后验证仍存在，后续继续监控重试。`);
    return false;
  } catch (error) {
    console.warn(`${logPrefix} 自动拖动验证失败: ${error instanceof Error ? error.message : String(error)}`);
    return false;
  } finally {
    state.running = false;
  }
}

export function attachVerificationMonitor(context, options = {}) {
  let disposed = false;
  let scanning = false;

  const scanAllPages = async (reason) => {
    if (disposed || scanning) {
      return;
    }

    scanning = true;
    try {
      for (const page of context.pages()) {
        await ensureSlideVerification(page, { ...options, reason });
      }
    } finally {
      scanning = false;
    }
  };

  const watchPage = (page) => {
    if (!page || page.isClosed()) {
      return;
    }

    const rescan = () => {
      void ensureSlideVerification(page, options);
    };

    page.on("domcontentloaded", rescan);
    page.on("load", rescan);
    void ensureSlideVerification(page, options);
  };

  for (const page of context.pages()) {
    watchPage(page);
  }

  const onPage = (page) => {
    watchPage(page);
  };
  context.on("page", onPage);

  const interval = setInterval(() => {
    void scanAllPages("interval");
  }, options.intervalMs ?? VERIFICATION_SCAN_INTERVAL_MS);
  interval.unref?.();

  return {
    dispose() {
      if (disposed) {
        return;
      }

      disposed = true;
      clearInterval(interval);
      context.off?.("page", onPage);
    }
  };
}

export async function randomPause(min = 120, max = 360) {
  await sleep(randomBetween(min, max));
}

export async function humanMoveTo(page, locator) {
  await locator.scrollIntoViewIfNeeded().catch(() => {});
  const box = await locator.boundingBox();

  if (!box) {
    return;
  }

  const x = box.x + box.width * (0.25 + Math.random() * 0.5);
  const y = box.y + box.height * (0.25 + Math.random() * 0.5);
  await page.mouse.move(x, y, { steps: randomBetween(12, 28) });
  await randomPause(80, 180);
}

export async function humanClick(page, locator) {
  await humanMoveTo(page, locator);
  await locator.click({ delay: randomBetween(60, 180) });
  await randomPause(140, 320);
}

export async function humanType(page, locator, text, { clear = true } = {}) {
  await humanClick(page, locator);

  if (clear) {
    await page.keyboard.press("Meta+A").catch(() => {});
    await randomPause(60, 140);
    await page.keyboard.press("Backspace").catch(() => {});
    await randomPause(80, 180);
  }

  for (const char of text) {
    await page.keyboard.type(char, { delay: randomBetween(90, 220) });
    await randomPause(30, 120);
  }
}

export async function humanScrollPage(page, steps = 4) {
  for (let index = 0; index < steps; index += 1) {
    await page.mouse.wheel(0, randomBetween(280, 640));
    await randomPause(350, 900);
  }
}

export async function antiDetectPause(min = STEP_DELAY_MIN_MS, max = STEP_DELAY_MAX_MS) {
  const { min: resolvedMin, max: resolvedMax } = normalizeDelayRange(min, max);
  const ms = randomBetween(resolvedMin, resolvedMax);
  console.log(`  🛡️ [反检测] 暂停 ${(ms / 1000).toFixed(1)}s...`);
  await sleep(ms);
}

/**
 * 对话间长随机延迟 (10-45 秒)，模拟真人在不同商家间切换的间隔。
 */
export async function chatPause(min = 10_000, max = 45_000) {
  const ms = randomBetween(min, max);
  console.log(`  💬 [对话间隔] 暂停 ${(ms / 1000).toFixed(1)}s...`);
  await sleep(ms);
}

/**
 * 在基础延迟上加 ±20% 随机抖动
 * @param {number} baseMs 基础毫秒数
 * @returns {Promise<void>}
 */
export async function jitterDelay(baseMs) {
  const jitter = baseMs * (0.8 + Math.random() * 0.4); // ±20%
  await sleep(Math.round(jitter));
}
