/**
 * DesktopMutex 单元测试
 * 覆盖：acquire/release 基本流程、FIFO 排队、幂等 release、isBusy/current、cancelWaiters
 */

import { DesktopMutex } from '../../../../src/modules/desktop_op/runtime/desktop_mutex.js';

describe('DesktopMutex', () => {
  let mutex: DesktopMutex;

  beforeEach(() => {
    mutex = new DesktopMutex();
  });

  // 测试 1：空闲时 acquire 立即返回
  it('空闲时 acquire 立即返回 release 函数', async () => {
    const release = await mutex.acquire('t1', 'appA');
    expect(typeof release).toBe('function');
    release();
  });

  // 测试 2：第二个 acquire 阻塞，直到第一个 release
  it('第二个 acquire 阻塞直到第一个 release 被调用', async () => {
    const log: string[] = [];

    const r1 = await mutex.acquire('t1', 'appA');

    const second = mutex.acquire('t2', 'appA').then(release => {
      log.push('t2-got');
      release();
    });

    log.push('t1-running');

    // 给 second 一个微任务调度机会，它应该仍然阻塞
    await new Promise(r => setImmediate(r));
    expect(log).toEqual(['t1-running']);

    r1();
    await second;
    expect(log).toEqual(['t1-running', 't2-got']);
  });

  // 测试 3：多个等待者按 FIFO 顺序被唤醒
  it('多个等待者按 FIFO 顺序获得锁', async () => {
    const log: string[] = [];
    const r1 = await mutex.acquire('t1', 'appA');

    const p2 = mutex.acquire('t2', 'appA').then(release => { log.push('t2'); release(); });
    const p3 = mutex.acquire('t3', 'appA').then(release => { log.push('t3'); release(); });
    const p4 = mutex.acquire('t4', 'appA').then(release => { log.push('t4'); release(); });

    // 等待三个 waiter 全部入队
    await new Promise(r => setImmediate(r));
    expect(log).toEqual([]);

    r1();
    await Promise.all([p2, p3, p4]);

    expect(log).toEqual(['t2', 't3', 't4']);
  });

  // 测试 4：release 幂等——调用两次不会重复弹出 waiter
  it('release 幂等：第二次调用不弹出等待者', async () => {
    const log: string[] = [];
    const r1 = await mutex.acquire('t1', 'appA');

    const p2 = mutex.acquire('t2', 'appA').then(release => {
      log.push('t2-got');
      release();
    });

    await new Promise(r => setImmediate(r));

    r1(); // 第一次 release，唤醒 t2
    r1(); // 第二次 release，应为无操作

    await p2;
    expect(log).toEqual(['t2-got']);
    // 第二次 release 之后 mutex 应为空闲
    expect(mutex.isBusy()).toBe(false);
  });

  // 测试 5：isBusy() 正确反映持锁状态
  it('isBusy() 持锁时返回 true，释放后返回 false', async () => {
    expect(mutex.isBusy()).toBe(false);

    const release = await mutex.acquire('t1', 'appA');
    expect(mutex.isBusy()).toBe(true);

    release();
    expect(mutex.isBusy()).toBe(false);
  });

  // 测试 6：current() 返回当前持锁信息
  it('current() 返回持锁 taskId 和 appId，空闲时返回 null', async () => {
    expect(mutex.current()).toBeNull();

    const release = await mutex.acquire('task-abc', 'weixin');
    expect(mutex.current()).toEqual({ taskId: 'task-abc', appId: 'weixin' });

    release();
    expect(mutex.current()).toBeNull();
  });

  // 测试 7：cancelWaiters 拒绝所有等待者，不影响当前持锁方
  it('cancelWaiters 拒绝所有等待者，当前持锁不受影响', async () => {
    const r1 = await mutex.acquire('t1', 'appA');

    const p2 = mutex.acquire('t2', 'appA');
    const p3 = mutex.acquire('t3', 'appA');

    await new Promise(r => setImmediate(r));

    // 取消所有等待者
    mutex.cancelWaiters('test-abort');

    await expect(p2).rejects.toThrow('mutex-canceled: test-abort');
    await expect(p3).rejects.toThrow('mutex-canceled: test-abort');

    // 当前持锁方仍然有效
    expect(mutex.isBusy()).toBe(true);
    expect(mutex.current()).toEqual({ taskId: 't1', appId: 'appA' });

    r1();
  });

  // 测试 8：cancelWaiters 后，持锁方 release 不崩溃，队列正常
  it('cancelWaiters 后持锁方 release 正常执行，队列为空无崩溃', async () => {
    const r1 = await mutex.acquire('t1', 'appA');

    const p2 = mutex.acquire('t2', 'appA');
    await new Promise(r => setImmediate(r));

    mutex.cancelWaiters('cleanup');
    await expect(p2).rejects.toThrow('mutex-canceled: cleanup');

    // 此时队列为空，release 不应抛出
    expect(() => r1()).not.toThrow();
    expect(mutex.isBusy()).toBe(false);
    expect(mutex.current()).toBeNull();
  });

  // 测试 9：两个并发 acquire 的串行化验证（共享计数器绝不重叠）
  it('串行化执行：两个并发 acquire 不重叠', async () => {
    const log: string[] = [];
    const m = new DesktopMutex();

    const r1 = await m.acquire('t1', 'weixin');

    const second = m.acquire('t2', 'weixin').then(release => {
      log.push('t2-got');
      release();
    });

    log.push('t1-running');

    // 给 second 调度机会，应仍阻塞
    await new Promise(r => setImmediate(r));
    expect(log).toEqual(['t1-running']);

    r1();
    await second;
    expect(log).toEqual(['t1-running', 't2-got']);
  });
});
