import { Provide, Scope, ScopeEnum } from '@midwayjs/core';

export type HumanizeMode = 'full' | 'fast' | 'off';

/**
 * ⚠️ 反风控关键约束：禁止手动 `ctx.newCDPSession(page)` + `cdpSession.send(...)`
 *
 * 原因：patchright 反检测的核心是隐藏 CDP runtime 暴露面（避免 Runtime.enable
 * 被反爬探测到）。一旦我们手动创建 CDP session 并 send 命令，绕过了 patchright
 * 的封装层，反爬可以通过特定时序探测到 debugger 已 attach，识别为自动化工具。
 *
 * 正确做法：所有交互走 patchright 内置 API（page.mouse.* / page.keyboard.* /
 * page.locator(...).click() 等），这些都已经过 patchright 反检测处理。
 *
 * 例外：仅在 open 时一次性调用 Page domain 的非敏感命令（如 setFontFamilies）
 * 是可接受的——它不触发 Runtime.enable，且只在新 page 创建时执行一次。
 */
@Provide()
@Scope(ScopeEnum.Singleton)
export class HumanizerService {
  protected rand(min: number, max: number): number {
    return min + Math.floor(Math.random() * (max - min));
  }
  protected chance(p: number): boolean {
    return Math.random() < p;
  }

  async click(_ctx: any, page: any, point: { x: number; y: number }, mode: HumanizeMode): Promise<void> {
    if (mode === 'full') {
      // 拟人化轨迹（多步 page.mouse.move，每步独立 CDP 事件，DOM 派发 mousemove）
      await this.humanMove(page, point, 20);
      await this.sleep(this.rand(100, 300));
      // 用 patchright 内置 page.mouse.down/up，已经过反检测处理，不暴露 debugger
      await page.mouse.down();
      await this.sleep(this.rand(50, 150));
      await page.mouse.up();
      if (this.chance(0.2)) {
        await page.mouse.wheel(0, this.rand(2, 10));
      }
    } else if (mode === 'fast') {
      await page.mouse.move(point.x, point.y, { steps: 5 });
      await this.sleep(this.rand(50, 200));
      await page.mouse.down();
      await this.sleep(this.rand(20, 50));
      await page.mouse.up();
    } else {
      await page.mouse.move(point.x, point.y);
      await page.mouse.down();
      await page.mouse.up();
    }
  }

  /**
   * 拟人化鼠标移动：自己写循环单步调用 page.mouse.move，确保每步派发 mousemove DOM 事件。
   * 用二次 bezier 曲线 + 随机抖动 + 随机步间延迟模拟人类轨迹。
   *
   * 注意：page.mouse.move(x,y,{steps:N}) 在 patchright 内部可能合并成单次 CDP 调用，
   * 只派发起止两个事件，所以这里手写循环每次单点 move。
   */
  protected async humanMove(page: any, target: { x: number; y: number }, steps: number): Promise<void> {
    const vp = page.viewportSize?.() ?? { width: 1280, height: 720 };
    // 起点：假设鼠标在视口中央附近（open 时已激活 hover 状态到中心）
    const startX = vp.width / 2 + this.rand(-100, 100);
    const startY = vp.height / 2 + this.rand(-100, 100);
    // bezier 控制点：在起点终点之间偏移一些，让轨迹有曲度
    const ctrlX = (startX + target.x) / 2 + this.rand(-80, 80);
    const ctrlY = (startY + target.y) / 2 + this.rand(-80, 80);
    for (let i = 1; i <= steps; i++) {
      const t = i / steps;
      const inv = 1 - t;
      const x = inv * inv * startX + 2 * inv * t * ctrlX + t * t * target.x;
      const y = inv * inv * startY + 2 * inv * t * ctrlY + t * t * target.y;
      await page.mouse.move(x, y);
      await this.sleep(this.rand(8, 25));
    }
  }

  async type(page: any, text: string, mode: HumanizeMode): Promise<void> {
    if (mode === 'off') {
      await page.keyboard.type(text);
      return;
    }
    const minDelay = mode === 'full' ? 80 : 30;
    const maxDelay = mode === 'full' ? 250 : 80;
    for (let i = 0; i < text.length; i++) {
      const ch = text[i];
      if (mode === 'full' && this.chance(0.05)) {
        const wrong = String.fromCharCode(ch.charCodeAt(0) + 1);
        await page.keyboard.type(wrong);
        await this.sleep(this.rand(minDelay, maxDelay));
        await page.keyboard.press('Backspace');
        await this.sleep(this.rand(minDelay, maxDelay));
      }
      await page.keyboard.type(ch);
      await this.sleep(this.rand(minDelay, maxDelay));
    }
  }

  async hover(_ctx: any, page: any, point: { x: number; y: number }, mode: HumanizeMode): Promise<void> {
    if (mode === 'full') {
      await this.humanMove(page, point, 20);
      await this.sleep(this.rand(200, 800));
    } else {
      await page.mouse.move(point.x, point.y, { steps: mode === 'fast' ? 5 : 1 });
      if (mode === 'fast') await this.sleep(this.rand(50, 200));
    }
  }

  async scroll(page: any, deltaY: number, mode: HumanizeMode): Promise<void> {
    if (mode === 'off') {
      await page.mouse.wheel(0, deltaY);
      return;
    }
    const steps = mode === 'full' ? Math.ceil(Math.abs(deltaY) / 50) : Math.ceil(Math.abs(deltaY) / 200);
    const stepSize = deltaY / steps;
    const minD = mode === 'full' ? 50 : 10;
    const maxD = mode === 'full' ? 150 : 30;
    for (let i = 0; i < steps; i++) {
      await page.mouse.wheel(0, stepSize);
      await this.sleep(this.rand(minD, maxD));
    }
  }

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