/**
 * RateLimiter 单元测试
 * 覆盖三个维度：per-target-per-min / per-app-per-min / per-app-per-day
 */

import { RateLimiter } from '../../../../src/modules/desktop_op/runtime/rate_limiter.js';

describe('RateLimiter', () => {
  let limiter: RateLimiter;

  beforeEach(() => {
    jest.useFakeTimers({ advanceTimers: false });
    jest.setSystemTime(new Date('2025-01-01T00:00:00.000Z'));
    limiter = new RateLimiter();
  });

  afterEach(() => {
    jest.useRealTimers();
  });

  // 测试 1: 未超任何限制时返回 allowed:true
  it('未超任何限制时返回 allowed:true', () => {
    const result = limiter.tryAcquire('appA', 'target1', {
      perTargetPerMin: 5,
      perAppPerMin: 10,
      perAppPerDay: 100,
    });
    expect(result).toEqual({ allowed: true });
  });

  // 测试 2: perTargetPerMin 在同目标第 N+1 次时拦截
  it('perTargetPerMin: 同目标第 N+1 次被拦截', () => {
    const opts = { perTargetPerMin: 3 };
    limiter.tryAcquire('appA', 'target1', opts);
    limiter.tryAcquire('appA', 'target1', opts);
    limiter.tryAcquire('appA', 'target1', opts);
    const result = limiter.tryAcquire('appA', 'target1', opts);
    expect(result).toMatchObject({
      allowed: false,
      reason: expect.stringMatching(/per-target-per-min\(3\)/),
    });
  });

  // 测试 3: perAppPerMin 跨不同目标时拦截同一 app
  it('perAppPerMin: 同 app 跨不同目标超限时被拦截', () => {
    const opts = { perAppPerMin: 2 };
    limiter.tryAcquire('appA', 'target1', opts);
    limiter.tryAcquire('appA', 'target2', opts);
    const result = limiter.tryAcquire('appA', 'target3', opts);
    expect(result).toMatchObject({
      allowed: false,
      reason: expect.stringMatching(/per-app-per-min\(2\)/),
    });
  });

  // 测试 4: perAppPerDay 超限时拦截
  it('perAppPerDay: 超日限时被拦截', () => {
    const opts = { perAppPerDay: 2 };
    limiter.tryAcquire('appA', 'target1', opts);
    limiter.tryAcquire('appA', 'target2', opts);
    const result = limiter.tryAcquire('appA', 'target3', opts);
    expect(result).toMatchObject({
      allowed: false,
      reason: expect.stringMatching(/per-app-per-day\(2\)/),
    });
  });

  // 测试 5: 模拟 61 秒后每分钟计数器重置，但每日计数仍保留
  it('61秒后每分钟计数器重置，每日计数仍累计', () => {
    const opts = { perTargetPerMin: 2, perAppPerDay: 100 };
    // T=0 用掉 2 次配额
    limiter.tryAcquire('appA', 'target1', opts);
    limiter.tryAcquire('appA', 'target1', opts);

    // T=0 第3次被拦截
    const blocked = limiter.tryAcquire('appA', 'target1', opts);
    expect(blocked.allowed).toBe(false);

    // 前进 61 秒
    jest.setSystemTime(new Date('2025-01-01T00:01:01.000Z'));

    // 每分钟窗口已过，应放行
    const allowedAgain = limiter.tryAcquire('appA', 'target1', opts);
    expect(allowedAgain).toEqual({ allowed: true });

    // 确认日计数确实在跨分钟窗口后仍累积
    const limiter2 = new RateLimiter();
    const optsDay = { perTargetPerMin: 10, perAppPerDay: 3 };
    jest.setSystemTime(new Date('2025-01-01T00:00:00.000Z'));
    limiter2.tryAcquire('appB', 't1', optsDay);
    limiter2.tryAcquire('appB', 't1', optsDay);
    // 前进 61 秒（每分钟过期，但每日不过期）
    jest.setSystemTime(new Date('2025-01-01T00:01:01.000Z'));
    limiter2.tryAcquire('appB', 't1', optsDay); // 第3次，正好到日限
    const dayBlocked = limiter2.tryAcquire('appB', 't1', optsDay); // 第4次，超日限
    expect(dayBlocked).toMatchObject({
      allowed: false,
      reason: expect.stringMatching(/per-app-per-day\(3\)/),
    });
  });

  // 测试 6: app A 的操作不影响 app B
  it('app A 超限不影响 app B', () => {
    const opts = { perAppPerMin: 2 };
    limiter.tryAcquire('appA', 'target1', opts);
    limiter.tryAcquire('appA', 'target2', opts);
    // appA 已满
    const blockedA = limiter.tryAcquire('appA', 'target3', opts);
    expect(blockedA.allowed).toBe(false);

    // appB 未受影响
    const allowedB = limiter.tryAcquire('appB', 'target1', opts);
    expect(allowedB).toEqual({ allowed: true });
  });

  // 测试 7: reset() 清空所有计数
  it('reset() 清空所有计数后恢复放行', () => {
    const opts = { perTargetPerMin: 1 };
    limiter.tryAcquire('appA', 'target1', opts);
    const blocked = limiter.tryAcquire('appA', 'target1', opts);
    expect(blocked.allowed).toBe(false);

    limiter.reset();

    const allowed = limiter.tryAcquire('appA', 'target1', opts);
    expect(allowed).toEqual({ allowed: true });
  });

  // 测试 8: 触达上限后连续调用仍保持被拦截（窗口内不自动恢复）
  it('上限触达后窗口内持续拦截', () => {
    const opts = { perTargetPerMin: 2 };
    limiter.tryAcquire('appA', 'target1', opts);
    limiter.tryAcquire('appA', 'target1', opts);
    // 后续三次都应被拦截
    for (let i = 0; i < 3; i++) {
      const result = limiter.tryAcquire('appA', 'target1', opts);
      expect(result.allowed).toBe(false);
    }
  });

  // 测试 9: 多个维度同时超限时，返回第一个检查维度的 reason（perTargetPerMin 优先）
  it('多维度超限时返回第一优先维度 per-target-per-min', () => {
    const opts = { perTargetPerMin: 1, perAppPerMin: 1, perAppPerDay: 1 };
    limiter.tryAcquire('appA', 'target1', opts);
    // 此时三个维度都已满
    const result = limiter.tryAcquire('appA', 'target1', opts);
    expect(result).toMatchObject({
      allowed: false,
      reason: expect.stringMatching(/per-target-per-min/),
    });
  });

  // 测试 10: opts 为空 {} 时始终放行
  it('opts 为空 {} 时始终放行', () => {
    for (let i = 0; i < 20; i++) {
      const result = limiter.tryAcquire('appA', 'target1', {});
      expect(result).toEqual({ allowed: true });
    }
  });
});
