import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import { acquireProcessInstanceFileLock } from '@electron/main/process-instance-lock';

const tempDirs: string[] = [];

function createTempDir(): string {
  const dir = mkdtempSync(join(tmpdir(), 'clawx-instance-lock-'));
  tempDirs.push(dir);
  return dir;
}

afterEach(() => {
  while (tempDirs.length > 0) {
    const dir = tempDirs.pop();
    if (!dir) continue;
    rmSync(dir, { recursive: true, force: true });
  }
});

describe('process instance file lock', () => {
  it('acquires lock and writes owner pid', () => {
    const userDataDir = createTempDir();
    const lock = acquireProcessInstanceFileLock({
      userDataDir,
      lockName: 'clawx',
      pid: 12345,
    });

    const lockPath = join(userDataDir, 'clawx.instance.lock');
    expect(lock.acquired).toBe(true);
    expect(existsSync(lockPath)).toBe(true);
    expect(readFileSync(lockPath, 'utf8')).toBe('12345');

    lock.release();
    expect(existsSync(lockPath)).toBe(false);
  });

  it('rejects a second lock when owner pid is alive', () => {
    const userDataDir = createTempDir();
    const first = acquireProcessInstanceFileLock({
      userDataDir,
      lockName: 'clawx',
      pid: 2222,
      isPidAlive: () => true,
    });

    const second = acquireProcessInstanceFileLock({
      userDataDir,
      lockName: 'clawx',
      pid: 3333,
      isPidAlive: () => true,
    });

    expect(first.acquired).toBe(true);
    expect(second.acquired).toBe(false);
    expect(second.ownerPid).toBe(2222);
    expect(second.ownerFormat).toBe('legacy');

    first.release();
  });

  it('replaces stale lock file when owner pid is not alive', () => {
    const userDataDir = createTempDir();
    const lockPath = join(userDataDir, 'clawx.instance.lock');
    writeFileSync(lockPath, '4444', 'utf8');

    const lock = acquireProcessInstanceFileLock({
      userDataDir,
      lockName: 'clawx',
      pid: 5555,
      isPidAlive: () => false,
    });

    expect(lock.acquired).toBe(true);
    expect(readFileSync(lockPath, 'utf8')).toBe('5555');
    lock.release();
  });

  it('replaces stale structured lock file when owner pid is not alive', () => {
    const userDataDir = createTempDir();
    const lockPath = join(userDataDir, 'clawx.instance.lock');
    writeFileSync(lockPath, JSON.stringify({
      schema: 'clawx-instance-lock',
      version: 1,
      pid: 7777,
    }), 'utf8');

    const lock = acquireProcessInstanceFileLock({
      userDataDir,
      lockName: 'clawx',
      pid: 6666,
      isPidAlive: () => false,
    });

    expect(lock.acquired).toBe(true);
    expect(readFileSync(lockPath, 'utf8')).toBe('6666');
    lock.release();
  });

  it('does not treat malformed lock file content as stale', () => {
    const userDataDir = createTempDir();
    const lockPath = join(userDataDir, 'clawx.instance.lock');
    writeFileSync(lockPath, 'not-a-pid', 'utf8');

    const lock = acquireProcessInstanceFileLock({
      userDataDir,
      lockName: 'clawx',
      pid: 6666,
    });

    expect(lock.acquired).toBe(false);
    expect(lock.ownerPid).toBeUndefined();
    expect(lock.ownerFormat).toBe('unknown');
    expect(readFileSync(lockPath, 'utf8')).toBe('not-a-pid');
  });

  it('does not remove lock file if ownership changed before release', () => {
    const userDataDir = createTempDir();
    const lockPath = join(userDataDir, 'clawx.instance.lock');
    const first = acquireProcessInstanceFileLock({
      userDataDir,
      lockName: 'clawx',
      pid: 1234,
    });

    // Simulate a new process acquiring the lock after a handover race.
    writeFileSync(lockPath, '9999', 'utf8');
    first.release();

    expect(readFileSync(lockPath, 'utf8')).toBe('9999');
  });

  it('does not treat unknown structured lock schema as stale', () => {
    const userDataDir = createTempDir();
    const lockPath = join(userDataDir, 'clawx.instance.lock');
    writeFileSync(lockPath, JSON.stringify({
      schema: 'future-lock-schema',
      version: 2,
      pid: 8888,
      owner: 'future-build',
    }), 'utf8');

    const lock = acquireProcessInstanceFileLock({
      userDataDir,
      lockName: 'clawx',
      pid: 9999,
    });

    expect(lock.acquired).toBe(false);
    expect(lock.ownerPid).toBeUndefined();
    expect(lock.ownerFormat).toBe('unknown');
    expect(readFileSync(lockPath, 'utf8')).toContain('future-lock-schema');
  });

  it('force: true acquires lock even when existing owner pid is alive', () => {
    const userDataDir = createTempDir();
    const lockPath = join(userDataDir, 'clawx.instance.lock');
    // Simulate a lock held by a live process (e.g. orphan Python process after update)
    writeFileSync(lockPath, '14736', 'utf8');

    const lock = acquireProcessInstanceFileLock({
      userDataDir,
      lockName: 'clawx',
      pid: 5555,
      isPidAlive: () => true, // owner appears alive (PID recycled on Windows)
      force: true,
    });

    expect(lock.acquired).toBe(true);
    expect(readFileSync(lockPath, 'utf8')).toBe('5555');
    lock.release();
  });

  it('force: true acquires lock when lock file has malformed content', () => {
    const userDataDir = createTempDir();
    const lockPath = join(userDataDir, 'clawx.instance.lock');
    writeFileSync(lockPath, 'garbage-content', 'utf8');

    const lock = acquireProcessInstanceFileLock({
      userDataDir,
      lockName: 'clawx',
      pid: 7777,
      force: true,
    });

    expect(lock.acquired).toBe(true);
    expect(readFileSync(lockPath, 'utf8')).toBe('7777');
    lock.release();
  });
});