import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { PassThrough } from 'stream';
import { createLocalToolOperations } from '../src/modules/netaclaw/tools/operations/local.js';

describe('LocalToolOperations', () => {
  const cleanupTasks: Array<() => void> = [];

  afterEach(() => {
    jest.resetModules();
    jest.clearAllMocks();
    cleanupTasks.splice(0).forEach(cleanup => cleanup());
  });

  const createTempDir = () => {
    const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'neta-tool-ops-'));
    cleanupTasks.push(() => fs.rmSync(dir, { recursive: true, force: true }));
    return dir;
  };

  it('supports file read/write/append/readDir/realpath operations', async () => {
    const ops = createLocalToolOperations();
    const root = createTempDir();
    const nested = path.join(root, 'nested');
    const filePath = path.join(nested, 'note.txt');

    await ops.file.mkdir(nested);
    await ops.file.writeFile(filePath, 'line 1');
    await ops.file.appendFile(filePath, '\nline 2');
    await ops.file.access(filePath, 'readwrite');

    const buffer = await ops.file.readFile(filePath);
    expect(buffer.toString('utf-8')).toBe('line 1\nline 2');

    expect(await ops.file.isDirectory(root)).toBe(true);
    expect(await ops.file.isDirectory(filePath)).toBe(false);

    const entries = await ops.file.readDir(root);
    expect(entries).toEqual([
      expect.objectContaining({ name: 'nested', isDirectory: true }),
    ]);

    expect(await ops.file.realpath(filePath)).toBe(path.resolve(filePath));
  });

  it('streams process output and returns exitCode', async () => {
    const ops = createLocalToolOperations();
    const root = createTempDir();
    const chunks: Array<{ stream: 'stdout' | 'stderr'; text: string }> = [];

    const result = await ops.process.exec(
      `node -e "process.stdout.write('OUT'); process.stderr.write('ERR')"`,
      root,
      {
        timeout: 5000,
        onData: (chunk, stream) => chunks.push({ stream, text: chunk.toString('utf-8') }),
      },
    );

    expect(result.exitCode).toBe(0);
    expect(chunks).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ stream: 'stdout', text: expect.stringContaining('OUT') }),
        expect.objectContaining({ stream: 'stderr', text: expect.stringContaining('ERR') }),
      ]),
    );
  });

  it('collects search output through SearchOperations wrappers', async () => {
    const spawnMock = jest.fn().mockImplementation((_cmd, _args, _options) => {
      const child: any = new PassThrough();
      child.stdout = new PassThrough();
      child.stderr = new PassThrough();
      child.pid = 123;
      child.once = child.on.bind(child);
      child.off = child.removeListener.bind(child);
      setImmediate(() => {
        child.stdout.write('/tmp/a.txt\n/tmp/b.txt\n');
        child.stdout.end();
        child.stderr.end();
        child.emit('close', 0);
      });
      return child;
    });

    let createOps: typeof createLocalToolOperations;
    jest.isolateModules(() => {
      jest.doMock('../src/comm/child_process.js', () => ({
        spawn: spawnMock,
        resolveShellConfig: () => ({ shell: 'bash', args: ['-lc'] }),
      }));
      ({ createLocalToolOperations: createOps } = require('../src/modules/netaclaw/tools/operations/local.js'));
    });

    const ops = createOps!();
    const result = await ops.search.fd(['--glob', '*.txt'], '/tmp');

    expect(spawnMock).toHaveBeenCalled();
    expect(result.exitCode).toBe(0);
    expect(result.stdout).toContain('/tmp/a.txt');
    expect(result.stdout).toContain('/tmp/b.txt');
  });
});
