import { describe, expect, it, vi } from 'vitest';
import type { FFmpegAdapter } from '../../src/ffmpeg/ffmpeg.types';
import {
  describeMountedInputAccess,
  mountJobFiles,
} from '../../src/ffmpeg/virtual-fs';

function createFilesystemAdapter(
  options: { keepWorkDirectory?: boolean; rejectWorkerFs?: boolean } = {}
) {
  const directories = new Set(['/input', '/work']);
  const files = new Set<string>();
  const adapter = {
    createDir: vi.fn(async (path: string) => {
      directories.add(path);
    }),
    mount: vi.fn(async () => {
      if (options.rejectWorkerFs) {
        throw new Error('WORKERFS unavailable');
      }
    }),
    unmount: vi.fn(async () => undefined),
    writeFile: vi.fn(async (path: string) => {
      files.add(path);
    }),
    deleteFile: vi.fn(async (path: string) => {
      files.delete(path);
    }),
    deleteDir: vi.fn(async (path: string) => {
      if (options.keepWorkDirectory && path.startsWith('/work/job-')) return;
      directories.delete(path);
    }),
    listDir: vi.fn(async (path: string) =>
      [...directories]
        .filter(
          (entry) =>
            entry !== path && entry.slice(0, entry.lastIndexOf('/')) === path
        )
        .map((entry) => ({
          name: entry.split('/').at(-1) ?? '',
          isDir: true,
          isLink: false,
        }))
    ),
  };
  return adapter as unknown as FFmpegAdapter;
}

describe('per-job FFmpeg filesystem', () => {
  it('uses generated paths and verifies complete cleanup', async () => {
    const adapter = createFilesystemAdapter();
    const mounted = await mountJobFiles(
      adapter,
      '../../job with separators',
      [new File(['source'], '../../private name.MP4')],
      ['../unsafe result.mp4']
    );

    expect(mounted.inputPaths).toEqual([
      '/input/job-job-with-separators/source-0.mp4',
    ]);
    expect(mounted.outputPaths).toEqual([
      '/work/job-job-with-separators/output-0.mp4',
    ]);
    expect(mounted).toMatchObject({
      inputMode: 'workerfs',
      inputBytes: 6,
      copiedInputBytes: 0,
    });
    expect(describeMountedInputAccess(mounted)).toContain(
      '0 bytes copied into engine memory'
    );
    expect(mounted.inputPaths[0]).not.toContain('private');
    expect(await mounted.cleanup()).toBe(true);
  });

  it('accounts for and explains the actual memory-copy fallback', async () => {
    const adapter = createFilesystemAdapter({ rejectWorkerFs: true });
    const mounted = await mountJobFiles(
      adapter,
      'copy-fallback',
      [new File(['1234'], 'one.mp4'), new File(['567'], 'two.mp4')],
      ['result.mp4']
    );

    expect(mounted).toMatchObject({
      inputMode: 'memory-copy',
      inputBytes: 7,
      copiedInputBytes: 7,
    });
    expect(adapter.writeFile).toHaveBeenCalledTimes(2);
    expect(describeMountedInputAccess(mounted)).toBe(
      'Input compatibility fallback: WORKERFS mounting was unavailable, so 7 bytes were copied into engine memory.'
    );
    expect(await mounted.cleanup()).toBe(true);
  });

  it('reports a directory that survives cleanup', async () => {
    const adapter = createFilesystemAdapter({ keepWorkDirectory: true });
    const mounted = await mountJobFiles(
      adapter,
      'job',
      [new File(['source'], 'source.wav')],
      ['result.wav']
    );

    expect(await mounted.cleanup()).toBe(false);
  });
});
