import fs from 'node:fs'

const STALE_LOCK_MS =
    process.env.NODE_ENV === 'development' ? 60_000 : 5 * 60 * 1000
const LOCK_WAIT_MS = 180_000

async function readLockPid(lockPath: string): Promise<number | null> {
    try {
        const raw = await fs.promises.readFile(lockPath, 'utf8')
        const pid = Number.parseInt(raw.trim(), 10)
        return Number.isFinite(pid) && pid > 0 ? pid : null
    } catch {
        return null
    }
}

function isProcessAlive(pid: number): boolean {
    try {
        process.kill(pid, 0)
        return true
    } catch (error: unknown) {
        const code =
            error && typeof error === 'object' && 'code' in error
                ? String((error as NodeJS.ErrnoException).code)
                : ''
        return code === 'EPERM'
    }
}

async function shouldBreakLock(lockPath: string): Promise<boolean> {
    const stat = await fs.promises.stat(lockPath).catch(() => null)
    if (!stat) return true

    const pid = await readLockPid(lockPath)
    if (pid != null && !isProcessAlive(pid)) {
        return true
    }

    return Date.now() - stat.mtimeMs > STALE_LOCK_MS
}

/**
 * 跨進程（Next.js + WS）序列化 schema 遷移，避免同時 DROP/CREATE room_streams 導致資料遺失。
 */
export async function withSchemaMigrationLock<T>(
    dbPath: string,
    fn: () => Promise<T>
): Promise<T> {
    const lockPath = `${dbPath}.migrate.lock`
    const deadline = Date.now() + LOCK_WAIT_MS
    let lockHandle: fs.promises.FileHandle | null = null

    while (Date.now() < deadline) {
        try {
            lockHandle = await fs.promises.open(lockPath, 'wx')
            await lockHandle.write(String(process.pid))
            break
        } catch (error: unknown) {
            const code =
                error && typeof error === 'object' && 'code' in error
                    ? String((error as NodeJS.ErrnoException).code)
                    : ''
            if (code !== 'EEXIST') throw error

            if (await shouldBreakLock(lockPath)) {
                await fs.promises.unlink(lockPath).catch(() => {})
                continue
            }
            await new Promise((resolve) => setTimeout(resolve, 40 + Math.random() * 40))
        }
    }

    if (!lockHandle) {
        throw new Error('[gp-live] schema migration lock timeout')
    }

    try {
        return await fn()
    } finally {
        await lockHandle.close()
        await fs.promises.unlink(lockPath).catch(() => {})
    }
}
