/**
 * Flock RPA 卡顿侦测 — 单步超过 FLOCK_STALL_MS（默认 20s）自动截图留证
 */
import fs from "node:fs";
import path from "node:path";
import type { Page } from "playwright";
import {
  isFlockQuoteLimitReached,
  describeFlockPagePhase,
} from "@/workers/rpa/flock/page-state";
import { isFlockPageAlive } from "@/workers/rpa/flock/page-alive";

export type FlockStallCapture = {
  step: string;
  screenshotPath: string;
  bodyPath: string;
  url: string;
  phase: string;
  quoteLimit: boolean;
  elapsedMs: number;
  at: string;
  skipped?: boolean;
};

export function readFlockStallMs(): number {
  const raw = process.env.FLOCK_STALL_MS?.trim();
  const n = raw ? Number(raw) : 20_000;
  return Number.isFinite(n) && n >= 5_000 ? n : 20_000;
}

function stallOutputDir(): string {
  const dir = path.join(process.cwd(), ".rpa", "flock-stalls");
  fs.mkdirSync(dir, { recursive: true });
  return dir;
}

function safeStepName(step: string): string {
  return step.replace(/[^\w\u4e00-\u9fa5-]+/g, "_").slice(0, 48);
}

/** 卡顿快照：截图 + 页面正文；页面已关则跳过（防 locator.count Target closed） */
export async function captureFlockStall(
  page: Page,
  step: string,
  elapsedMs: number,
): Promise<FlockStallCapture> {
  if (!isFlockPageAlive(page)) {
    return {
      step,
      screenshotPath: "",
      bodyPath: "",
      url: "",
      phase: "closed",
      quoteLimit: false,
      elapsedMs,
      at: new Date().toISOString(),
      skipped: true,
    };
  }

  const dir = stallOutputDir();
  const stamp = `${Date.now()}-${safeStepName(step)}`;
  const screenshotPath = path.join(dir, `${stamp}.png`);
  const bodyPath = path.join(dir, `${stamp}.txt`);

  const body = ((await page.locator("body").innerText().catch(() => "")) || "")
    .slice(0, 10_000);
  if (!isFlockPageAlive(page)) {
    return {
      step,
      screenshotPath: "",
      bodyPath: "",
      url: "",
      phase: "closed",
      quoteLimit: false,
      elapsedMs,
      at: new Date().toISOString(),
      skipped: true,
    };
  }

  const quoteLimit = await isFlockQuoteLimitReached(page).catch(() => false);
  const phase = describeFlockPagePhase(body);
  const url = (() => {
    try {
      return page.url();
    } catch {
      return "";
    }
  })();

  await page
    .screenshot({ path: screenshotPath, fullPage: true })
    .catch(() => undefined);
  fs.writeFileSync(
    bodyPath,
    [`url: ${url}`, `phase: ${phase}`, `quoteLimit: ${quoteLimit}`, "", body].join(
      "\n",
    ),
    "utf8",
  );

  const capture: FlockStallCapture = {
    step,
    screenshotPath,
    bodyPath,
    url,
    phase,
    quoteLimit,
    elapsedMs,
    at: new Date().toISOString(),
  };

  console.warn(
    `[flock-stall] 步骤「${step}」已等待 ${elapsedMs}ms | 页面阶段=${phase} | 报价上限=${quoteLimit ? "是" : "否"}`,
  );
  console.warn(`[flock-stall] 截图: ${screenshotPath}`);
  console.warn(`[flock-stall] 正文: ${bodyPath}`);

  return capture;
}

/**
 * 轮询等待条件，超过 stallMs 无进展则截图一次（每轮 wait 最多一张）
 */
export async function waitWithFlockStallWatch(
  page: Page,
  step: string,
  predicate: () => Promise<boolean>,
  options?: { timeoutMs?: number; pollMs?: number },
): Promise<{ ok: boolean; stall?: FlockStallCapture }> {
  const timeoutMs = options?.timeoutMs ?? 90_000;
  const pollMs = options?.pollMs ?? 500;
  const stallMs = readFlockStallMs();
  const started = Date.now();
  let stallCaptured = false;
  let lastStallMark = started;

  while (Date.now() - started < timeoutMs) {
    if (!isFlockPageAlive(page)) {
      return { ok: false };
    }
    if (await predicate()) {
      return { ok: true };
    }

    const now = Date.now();
    if (!stallCaptured && now - lastStallMark >= stallMs) {
      const stall = await captureFlockStall(page, step, now - started);
      stallCaptured = true;
      lastStallMark = now;
      if (stall.quoteLimit) {
        return { ok: false, stall };
      }
    }

    if (!isFlockPageAlive(page)) {
      return { ok: false };
    }
    await page.waitForTimeout(pollMs).catch(() => undefined);
  }

  return { ok: false };
}

/** 包装耗时步骤：整体超过 stallMs 则截图（步骤结束后禁止再碰 page） */
export async function withFlockStallWatch<T>(
  page: Page,
  step: string,
  fn: () => Promise<T>,
): Promise<T> {
  const stallMs = readFlockStallMs();
  const started = Date.now();
  let captured = false;
  let active = true;

  const timer = setInterval(() => {
    if (!active || captured) return;
    if (Date.now() - started >= stallMs) {
      captured = true;
      void captureFlockStall(page, step, Date.now() - started).catch(
        () => undefined,
      );
    }
  }, 2_000);

  try {
    return await fn();
  } finally {
    active = false;
    clearInterval(timer);
  }
}
