import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
import { existsSync } from 'node:fs'
import { join } from 'node:path'
import { cleanupTempDir, createTempDir } from '../../../tests/mocks/file-system'

// Mock the lockfile module so tests don't need real file locks
mock.module('../lockfile.js', () => ({
  lock: async (_file: string, _options?: unknown) => {
    return async () => {}
  },
}))

let tempDir = ''

beforeEach(async () => {
  tempDir = await createTempDir('autonomy-persistence-')
})

afterEach(async () => {
  if (tempDir) {
    await cleanupTempDir(tempDir)
  }
})

describe('withAutonomyPersistenceLock', () => {
  test('runs fn and returns its result', async () => {
    const { withAutonomyPersistenceLock } = await import(
      '../autonomyPersistence'
    )
    const result = await withAutonomyPersistenceLock(tempDir, async () => {
      return 42
    })
    expect(result).toBe(42)
  })

  test('creates the autonomy directory and lock file', async () => {
    const { withAutonomyPersistenceLock } = await import(
      '../autonomyPersistence'
    )
    await withAutonomyPersistenceLock(tempDir, async () => 'ok')

    const autonomyDir = join(tempDir, '.claude', 'autonomy')
    expect(existsSync(autonomyDir)).toBe(true)
  })

  test('propagates errors from the inner function', async () => {
    const { withAutonomyPersistenceLock } = await import(
      '../autonomyPersistence'
    )
    await expect(
      withAutonomyPersistenceLock(tempDir, async () => {
        throw new Error('inner failure')
      }),
    ).rejects.toThrow('inner failure')
  })

  test('releases same-root lock bookkeeping after success and failure', async () => {
    const {
      getAutonomyPersistenceLockCountForTests,
      withAutonomyPersistenceLock,
    } = await import('../autonomyPersistence')

    expect(getAutonomyPersistenceLockCountForTests()).toBe(0)

    await withAutonomyPersistenceLock(tempDir, async () => 'ok')
    expect(getAutonomyPersistenceLockCountForTests()).toBe(0)

    await expect(
      withAutonomyPersistenceLock(tempDir, async () => {
        throw new Error('inner failure')
      }),
    ).rejects.toThrow('inner failure')
    expect(getAutonomyPersistenceLockCountForTests()).toBe(0)
  })

  test('serializes concurrent calls on the same rootDir', async () => {
    const { withAutonomyPersistenceLock } = await import(
      '../autonomyPersistence'
    )
    const order: number[] = []

    const first = withAutonomyPersistenceLock(tempDir, async () => {
      order.push(1)
      // Simulate async work
      await new Promise(resolve => setTimeout(resolve, 20))
      order.push(2)
      return 'first'
    })

    const second = withAutonomyPersistenceLock(tempDir, async () => {
      order.push(3)
      return 'second'
    })

    const [r1, r2] = await Promise.all([first, second])

    expect(r1).toBe('first')
    expect(r2).toBe('second')
    // The second call must wait for the first to finish
    expect(order).toEqual([1, 2, 3])
  })

  test('allows parallel calls on different rootDirs', async () => {
    const { withAutonomyPersistenceLock } = await import(
      '../autonomyPersistence'
    )
    const tempDir2 = await createTempDir('autonomy-persistence-2-')

    try {
      const order: string[] = []

      const first = withAutonomyPersistenceLock(tempDir, async () => {
        order.push('a-start')
        await new Promise(resolve => setTimeout(resolve, 10))
        order.push('a-end')
        return 'a'
      })

      const second = withAutonomyPersistenceLock(tempDir2, async () => {
        order.push('b-start')
        await new Promise(resolve => setTimeout(resolve, 10))
        order.push('b-end')
        return 'b'
      })

      const [r1, r2] = await Promise.all([first, second])
      expect(r1).toBe('a')
      expect(r2).toBe('b')
      // Both should start before either ends (parallel)
      expect(order.indexOf('a-start')).toBeLessThan(order.indexOf('a-end'))
      expect(order.indexOf('b-start')).toBeLessThan(order.indexOf('b-end'))
    } finally {
      await cleanupTempDir(tempDir2)
    }
  })
})
