/**
 * Flock RPA 本机互斥锁 — 防止 embed Worker 与 batch 探针同时开浏览器打同一出口 IP
 */
import fs from "node:fs";
import path from "node:path";

const LOCK_DIR = path.join(process.cwd(), ".rpa");
const LOCK_PATH = path.join(LOCK_DIR, "flock-rpa.lock");

export type FlockLockInfo = {
  owner: string;
  pid: number;
  at: string;
};

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

function isPidAlive(pid: number): boolean {
  if (!Number.isFinite(pid) || pid <= 0) {
    return false;
  }
  try {
    process.kill(pid, 0);
    return true;
  } catch {
    return false;
  }
}

export function readFlockExclusiveLock(): FlockLockInfo | null {
  try {
    if (!fs.existsSync(LOCK_PATH)) {
      return null;
    }
    const raw = JSON.parse(fs.readFileSync(LOCK_PATH, "utf8")) as FlockLockInfo;
    if (!raw?.owner || !raw?.pid) {
      return null;
    }
    return raw;
  } catch {
    return null;
  }
}

/** 锁是否被其他存活进程占用 */
export function isFlockExclusiveBusy(ignoreOwner?: string): boolean {
  const info = readFlockExclusiveLock();
  if (!info) {
    return false;
  }
  if (!isPidAlive(info.pid)) {
    try {
      fs.unlinkSync(LOCK_PATH);
    } catch {
      /* ignore */
    }
    return false;
  }
  if (ignoreOwner && info.owner === ignoreOwner && info.pid === process.pid) {
    return false;
  }
  return info.pid !== process.pid;
}

export function getFlockExclusiveBusyMessage(): string | null {
  if (!isFlockExclusiveBusy()) {
    return null;
  }
  const info = readFlockExclusiveLock();
  const who = info?.owner === "batch" ? "本地批量探针" : "其他 Flock 查价任务";
  return `Flock 官网访问正被${who}占用（串行防互踩），请待其结束后再试`;
}

/** 仅当本地 batch 探针占锁时用于网站入队拒绝（Worker 占锁时仍可入队排队） */
export function getFlockBatchExclusiveBusyMessage(): string | null {
  const info = readFlockExclusiveLock();
  if (!info || info.owner !== "batch") {
    return null;
  }
  if (!isPidAlive(info.pid)) {
    try {
      fs.unlinkSync(LOCK_PATH);
    } catch {
      /* ignore */
    }
    return null;
  }
  return "Flock 官网访问正被本地批量探针占用（串行防互踩），请待探针结束后再在网站查询";
}

function tryAcquireOnce(owner: string): boolean {
  fs.mkdirSync(LOCK_DIR, { recursive: true });
  const stale = readFlockExclusiveLock();
  if (stale && !isPidAlive(stale.pid)) {
    try {
      fs.unlinkSync(LOCK_PATH);
    } catch {
      /* ignore */
    }
  }
  try {
    const fd = fs.openSync(LOCK_PATH, "wx");
    const payload: FlockLockInfo = {
      owner,
      pid: process.pid,
      at: new Date().toISOString(),
    };
    fs.writeFileSync(fd, JSON.stringify(payload), "utf8");
    fs.closeSync(fd);
    return true;
  } catch (error) {
    const code =
      error && typeof error === "object" && "code" in error
        ? String((error as { code: unknown }).code)
        : "";
    if (code === "EEXIST") {
      const again = readFlockExclusiveLock();
      if (again && again.pid === process.pid) {
        return true;
      }
      if (again && !isPidAlive(again.pid)) {
        try {
          fs.unlinkSync(LOCK_PATH);
        } catch {
          /* ignore */
        }
        return tryAcquireOnce(owner);
      }
      return false;
    }
    throw error;
  }
}

export function releaseFlockExclusiveLock(owner: string): void {
  const info = readFlockExclusiveLock();
  if (!info) {
    return;
  }
  if (info.pid !== process.pid && info.owner !== owner) {
    return;
  }
  try {
    fs.unlinkSync(LOCK_PATH);
  } catch {
    /* ignore */
  }
}

/** 等待获取本机唯一 Flock 浏览器锁 */
export async function withFlockExclusiveLock<T>(
  owner: string,
  fn: () => Promise<T>,
  options?: { waitMs?: number; pollMs?: number },
): Promise<T> {
  const waitMs = options?.waitMs ?? 180_000;
  const pollMs = options?.pollMs ?? 1_000;
  const deadline = Date.now() + waitMs;

  while (!tryAcquireOnce(owner)) {
    if (Date.now() >= deadline) {
      throw new Error(
        getFlockExclusiveBusyMessage() ??
          "Flock 官网访问繁忙，请稍后重试",
      );
    }
    const info = readFlockExclusiveLock();
    console.warn(
      `[flock-lock] 等待互斥锁 owner=${info?.owner ?? "?"} pid=${info?.pid ?? "?"}…`,
    );
    await sleep(pollMs);
  }

  console.log(`[flock-lock] 已获取互斥锁 owner=${owner} pid=${process.pid}`);
  try {
    return await fn();
  } finally {
    releaseFlockExclusiveLock(owner);
    console.log(`[flock-lock] 已释放互斥锁 owner=${owner}`);
  }
}

/** 失败是否不应 BullMQ 重试（上限/拒号重试只会加重 IP 风控） */
export function isFlockNonRetryableFailure(message?: string): boolean {
  if (!message) {
    return false;
  }
  return (
    /flock_quote_limit|thank you for your interest|报价.*上限|quote\s*limit/i.test(
      message,
    ) ||
    /flock_account_rejected|issue creating your account/i.test(message) ||
    /flock_account_exists|already (have|registered)/i.test(message) ||
    /flock_login_failed|session_expired|未配置 FLOCK_LOGIN_/i.test(message) ||
    /互斥锁|正被.*占用|防互踩/i.test(message) ||
    /FLOCK_RATES_PENDING|still looking for the best rates|约需\s*15\s*分钟/i.test(
      message,
    )
  );
}
