import type { Page } from "playwright";

export type HumanPauseKind =
  | "micro"
  | "short"
  | "medium"
  | "long"
  | "read"
  | "navigate"
  | "betweenPages"
  | "betweenTasks";

const PAUSE_RANGES: Record<HumanPauseKind, [number, number]> = {
  micro: [120, 420],
  short: [700, 1800],
  medium: [1800, 4200],
  long: [3500, 7500],
  read: [2800, 9000],
  navigate: [2200, 5500],
  betweenPages: [5000, 14000],
  betweenTasks: [10000, 24000],
};

const PAUSE_NEIGHBORS: Partial<Record<HumanPauseKind, HumanPauseKind[]>> = {
  micro: ["short"],
  short: ["micro", "medium"],
  medium: ["short", "long", "read"],
  long: ["medium", "read"],
  read: ["medium", "long"],
  navigate: ["medium", "short"],
  betweenPages: ["long", "betweenTasks"],
  betweenTasks: ["betweenPages", "long"],
};

/** 每次執行略有不同，避免整段任務節奏固定。 */
let sessionDrift = 0.82 + Math.random() * 0.38;

function delayMultiplier(): number {
  const raw = process.env.THREADS_HUMAN_DELAY_MULTIPLIER;
  if (!raw) return 1;
  const parsed = Number(raw);
  return Number.isFinite(parsed) && parsed > 0 ? Math.max(0.75, Math.min(parsed, 3)) : 1;
}

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

/** 近似常態分佈，比均勻亂數更像真人節奏。 */
function gaussianBetween(min: number, max: number): number {
  const span = max - min;
  const u = (randomBetween(0, 1000) + randomBetween(0, 1000) + randomBetween(0, 1000)) / 3000;
  return Math.round(min + u * span);
}

function maybeRefreshDrift(): void {
  if (Math.random() < 0.1) {
    sessionDrift = 0.82 + Math.random() * 0.38;
  }
}

/** 在基準毫秒上加減百分比抖動。 */
export function jitterMs(baseMs: number, spread = 0.3): number {
  maybeRefreshDrift();
  const mult = delayMultiplier() * sessionDrift;
  const factor = 1 + (Math.random() * 2 - 1) * spread;
  return Math.max(60, Math.round(baseMs * factor * mult));
}

function pickPauseKind(preferred: HumanPauseKind): HumanPauseKind {
  if (Math.random() < 0.18) {
    const neighbors = PAUSE_NEIGHBORS[preferred];
    if (neighbors?.length) {
      return neighbors[randomBetween(0, neighbors.length - 1)];
    }
  }
  return preferred;
}

function pauseRangeMs(kind: HumanPauseKind): number {
  const [min, max] = PAUSE_RANGES[kind];
  // jitterMs 已經套用全域倍率與 session drift；不要在這裡重複乘一次。
  return jitterMs(gaussianBetween(min, max), 0.12);
}

async function sleep(ms: number): Promise<void> {
  await new Promise((resolve) => setTimeout(resolve, ms));
}

/** 任務間隔：每次呼叫都重新抽樣，不會固定在同一秒數。 */
export function computeStaggerMs(range: [number, number]): number {
  const base = gaussianBetween(range[0], range[1]);
  let ms = jitterMs(base, 0.28);

  if (Math.random() < 0.14) {
    ms += jitterMs(randomBetween(1200, 4800), 0.35);
  }
  if (Math.random() < 0.06) {
    ms = Math.round(ms * (0.55 + Math.random() * 0.25));
  }

  return ms;
}

/** 隨機停留，模擬真人閱讀／思考；節奏每次不同。 */
export async function humanPause(kind: HumanPauseKind = "medium"): Promise<void> {
  maybeRefreshDrift();
  const resolved = pickPauseKind(kind);
  let ms = pauseRangeMs(resolved);

  const distractChance = 0.08 + Math.random() * 0.12;
  if (Math.random() < distractChance) {
    ms += pauseRangeMs(Math.random() < 0.5 ? "short" : "medium");
  }

  if (Math.random() < 0.16) {
    const first = Math.round(ms * (0.35 + Math.random() * 0.3));
    const second = ms - first + jitterMs(randomBetween(80, 320), 0.4);
    await sleep(first);
    await sleep(Math.max(60, second));
    return;
  }

  await sleep(ms);
}

async function humanMouseWander(page: Page, moves = 1): Promise<void> {
  const viewport = page.viewportSize() ?? { width: 1280, height: 900 };
  const count = Math.max(1, moves);

  for (let m = 0; m < count; m++) {
    const xSpread = 0.12 + Math.random() * 0.2;
    const ySpread = 0.12 + Math.random() * 0.22;
    await page.mouse.move(
      randomBetween(
        Math.floor(viewport.width * (0.15 + Math.random() * xSpread)),
        Math.floor(viewport.width * (0.55 + Math.random() * xSpread))
      ),
      randomBetween(
        Math.floor(viewport.height * (0.12 + Math.random() * ySpread)),
        Math.floor(viewport.height * (0.5 + Math.random() * ySpread))
      ),
      { steps: randomBetween(4, 14) }
    );
    if (m < count - 1) await humanPause("micro");
  }
}

/** 進入新頁面後先「站一下」再開始操作；每次動作組合不同。 */
export async function humanLandingPause(page: Page): Promise<void> {
  maybeRefreshDrift();

  if (Math.random() < 0.22) {
    await humanPause("short");
    await humanPause("navigate");
  } else {
    await humanPause("navigate");
  }

  if (Math.random() < 0.18) {
    const nudge = randomBetween(40, 180) * (Math.random() < 0.5 ? 1 : -1);
    await page.mouse.wheel(0, nudge);
    await humanPause("micro");
  }

  const moveChance = 0.28 + Math.random() * 0.42;
  if (Math.random() < moveChance) {
    const moves = Math.random() < 0.25 ? 2 : 1;
    await humanMouseWander(page, moves);
    await humanPause("micro");
  }

  if (Math.random() < 0.1) {
    await humanPause(Math.random() < 0.5 ? "medium" : "short");
  }
}

function pickScrollPauseKind(): HumanPauseKind {
  const pool: HumanPauseKind[] = ["micro", "short", "medium", "read"];
  const weights = [0.15, 0.35, 0.32, 0.18];
  const roll = Math.random();
  let acc = 0;
  for (let i = 0; i < pool.length; i++) {
    acc += weights[i];
    if (roll < acc) return pool[i];
  }
  return "short";
}

async function humanWheelStep(page: Page, deltaY: number): Promise<void> {
  const chunks = randomBetween(1, 4);
  let remaining = deltaY;

  for (let c = 0; c < chunks; c++) {
    const portion =
      c === chunks - 1
        ? remaining
        : Math.round((remaining / (chunks - c)) * (0.55 + Math.random() * 0.55));
    remaining -= portion;

    await page.mouse.wheel(
      Math.random() < 0.12 ? randomBetween(-25, 25) : 0,
      portion
    );

    if (c < chunks - 1 || Math.random() < 0.3) {
      await humanPause("micro");
    }
  }
}

/** 不規則捲動＋停留，每次路徑與節奏都不同。 */
export async function humanScrollPage(
  page: Page,
  options?: { minPasses?: number; maxPasses?: number }
): Promise<void> {
  maybeRefreshDrift();

  const minPasses = options?.minPasses ?? 2;
  const maxPasses = options?.maxPasses ?? 5;
  const passes = randomBetween(minPasses, maxPasses + randomBetween(0, 1));
  const viewport = page.viewportSize() ?? { width: 1280, height: 900 };

  if (Math.random() < 0.2) {
    await humanMouseWander(page, randomBetween(1, 2));
  }

  for (let i = 0; i < passes; i++) {
    if (i > 0 && Math.random() < 0.12) {
      await humanPause(pickScrollPauseKind());
    }

    const scrollAmount = randomBetween(180, 1100);
    const scrollUp = i > 0 && Math.random() < (0.12 + Math.random() * 0.14);
    await humanWheelStep(page, scrollUp ? -scrollAmount : scrollAmount);

    if (Math.random() < 0.28 + Math.random() * 0.22) {
      await humanMouseWander(page, Math.random() < 0.2 ? 2 : 1);
    }

    if (Math.random() < 0.08) {
      await page.mouse.move(
        randomBetween(Math.floor(viewport.width * 0.1), Math.floor(viewport.width * 0.9)),
        randomBetween(Math.floor(viewport.height * 0.15), Math.floor(viewport.height * 0.85)),
        { steps: randomBetween(3, 10) }
      );
    }

    await humanPause(pickScrollPauseKind());

    if (Math.random() < 0.09) {
      await humanWheelStep(page, randomBetween(60, 240) * (Math.random() < 0.4 ? -1 : 1));
      await humanPause("micro");
    }
  }
}

/** 平行海巡上限：預設安全雙工，可用環境變數降回 1；硬上限 2。 */
export function getBrowserConcurrency(): number {
  const parsed = Number.parseInt(process.env.THREADS_BROWSER_CONCURRENCY ?? "2", 10);
  return Number.isFinite(parsed) ? Math.max(1, Math.min(parsed, 2)) : 2;
}

export function getReplyFetchConcurrency(): number {
  return 1;
}

export const DEFAULT_STAGGER_MS: [number, number] = [3500, 9000];
export const DEFAULT_TASK_STAGGER_MS: [number, number] = [3500, 7500];
