import { describe, it, expect } from 'vitest';
import { withUserLock } from './user-lock.js';
import { mkdtempSync, mkdirSync, writeFileSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import lockfile from 'proper-lockfile';

describe('withUserLock', () => {
  it('serializes concurrent callers for the same user', async () => {
    const dir = mkdtempSync(join(tmpdir(), 'lk-'));
    mkdirSync(join(dir, 'u-1'), { recursive: true });
    const order: string[] = [];
    const a = withUserLock(dir, 'u-1', async () => {
      await new Promise(r => setTimeout(r, 50));
      order.push('A');
    });
    const b = withUserLock(dir, 'u-1', async () => {
      order.push('B');
    });
    await Promise.all([a, b]);
    expect(order).toEqual(['A', 'B']);
  });

  it('allows concurrent callers across different users', async () => {
    const dir = mkdtempSync(join(tmpdir(), 'lk-'));
    mkdirSync(join(dir, 'u-1'), { recursive: true });
    mkdirSync(join(dir, 'u-2'), { recursive: true });

    const order: string[] = [];

    // u-1 holds its lock for 80ms; u-2 should be able to start immediately
    const a = withUserLock(dir, 'u-1', async () => {
      await new Promise(r => setTimeout(r, 80));
      order.push('u-1-done');
    });
    // Give u-1 a small head-start so it acquires the lock first
    await new Promise(r => setTimeout(r, 10));
    const b = withUserLock(dir, 'u-2', async () => {
      // u-2 completes well before u-1 finishes
      order.push('u-2-done');
    });
    await Promise.all([a, b]);

    // u-2-done must appear before u-1-done, proving interleaving happened
    expect(order[0]).toBe('u-2-done');
    expect(order[1]).toBe('u-1-done');
  });

  it('times out and throws when lock is held too long', async () => {
    const dir = mkdtempSync(join(tmpdir(), 'lk-'));
    mkdirSync(join(dir, 'u-timeout'), { recursive: true });

    // Create the sentinel file and acquire the lock externally
    const sentinel = join(dir, 'u-timeout', '.reflection.lock');
    writeFileSync(sentinel, '');
    const release = await lockfile.lock(sentinel);

    try {
      await expect(
        withUserLock(dir, 'u-timeout', async () => 'should not run', {
          timeoutMs: 200,
          retries: 3,
        })
      ).rejects.toThrow();
    } finally {
      await release();
    }
  });
});
