import { describe, it, expect, beforeEach } from 'vitest'
import { acquire, clearLocks } from '../../server/utils/asyncLock.js'

describe('asyncLock', () => {
  beforeEach(() => {
    clearLocks()
  })

  it('executes callback immediately when no lock exists', async () => {
    const executed = { value: false }
    await acquire('test', async () => {
      executed.value = true
      return 42
    })
    expect(executed.value).toBe(true)
  })

  it('returns callback result', async () => {
    const result = await acquire('test', async () => {
      return { value: 123 }
    })
    expect(result).toEqual({ value: 123 })
  })

  it('serializes concurrent operations on same key', async () => {
    const results = []
    const promises = Array.from({ length: 5 }, (_, i) =>
      acquire('same-key', async () => {
        results.push(`start-${i}`)
        await new Promise(resolve => setTimeout(resolve, 10))
        results.push(`end-${i}`)
        return i
      }),
    )

    await Promise.all(promises)

    // Operations should be serialized: start-end pairs should not interleave
    expect(results.length).toBe(10)
    Array.from({ length: 5 }, (_, i) => {
      expect(results[i * 2]).toBe(`start-${i}`)
      expect(results[i * 2 + 1]).toBe(`end-${i}`)
    })
  })

  it('allows parallel operations on different keys', async () => {
    const results = []
    const promises = Array.from({ length: 5 }, (_, i) =>
      acquire(`key-${i}`, async () => {
        results.push(`start-${i}`)
        await new Promise(resolve => setTimeout(resolve, 10))
        results.push(`end-${i}`)
        return i
      }),
    )

    await Promise.all(promises)

    // Different keys can run in parallel
    expect(results.length).toBe(10)
    // All starts should come before all ends (parallel execution)
    const starts = results.filter(r => r.startsWith('start'))
    const ends = results.filter(r => r.startsWith('end'))
    expect(starts.length).toBe(5)
    expect(ends.length).toBe(5)
  })

  it('handles errors and releases lock', async () => {
    const callCount = { value: 0 }
    try {
      await acquire('error-key', async () => {
        callCount.value++
        throw new Error('Test error')
      })
    }
    catch (error) {
      expect(error.message).toBe('Test error')
    }

    // Lock should be released, next operation should execute
    await acquire('error-key', async () => {
      callCount.value++
      return 'success'
    })

    expect(callCount.value).toBe(2)
  })

  it('maintains lock ordering', async () => {
    const order = []
    const promises = Array.from({ length: 3 }, (_, i) =>
      acquire('ordered', async () => {
        order.push(`before-${i}`)
        await new Promise(resolve => setTimeout(resolve, 5))
        order.push(`after-${i}`)
      }),
    )

    await Promise.all(promises)

    // Should execute in order: before-0, after-0, before-1, after-1, before-2, after-2
    expect(order).toEqual(['before-0', 'after-0', 'before-1', 'after-1', 'before-2', 'after-2'])
  })
})
