import { describe, expect, it } from 'vitest';
import { normalizeWorkspacePath, prefixWorkspacePath, tryNormalizeWorkspacePath, WorkspacePathError } from './path-normalize.js';

describe('normalizeWorkspacePath', () => {
  it('preserves a simple workspace-relative path', () => {
    expect(normalizeWorkspacePath('foo.ts')).toBe('foo.ts');
    expect(normalizeWorkspacePath('output/foo.ts')).toBe('output/foo.ts');
    expect(normalizeWorkspacePath('subtasks/1/output/foo.ts')).toBe('subtasks/1/output/foo.ts');
  });

  it('strips redundant "./" segments and trailing slashes', () => {
    expect(normalizeWorkspacePath('./foo.ts')).toBe('foo.ts');
    expect(normalizeWorkspacePath('output/./foo.ts')).toBe('output/foo.ts');
    expect(normalizeWorkspacePath('output//foo.ts')).toBe('output/foo.ts');
  });

  it('canonicalizes paths with "./" but rejects ".." (traversal)', () => {
    // The Codex rule: workspace ".." is reject, not warn.
    expect(() => normalizeWorkspacePath('subtasks/1/output/../output/foo.ts')).toThrow(WorkspacePathError);
    expect(() => normalizeWorkspacePath('../foo.ts')).toThrow(WorkspacePathError);
    expect(() => normalizeWorkspacePath('output/../../escape.ts')).toThrow(WorkspacePathError);
  });

  it('rejects absolute paths', () => {
    expect(() => normalizeWorkspacePath('/etc/passwd')).toThrow(/absolute path not allowed/);
    expect(() => normalizeWorkspacePath('/foo.ts')).toThrow(WorkspacePathError);
  });

  it('rejects backslashes', () => {
    expect(() => normalizeWorkspacePath('output\\foo.ts')).toThrow(/backslashes not allowed/);
  });

  it('rejects Windows drive paths', () => {
    expect(() => normalizeWorkspacePath('C:/foo.ts')).toThrow(/Windows drive prefix/);
    expect(() => normalizeWorkspacePath('c:/foo.ts')).toThrow(WorkspacePathError);
  });

  it('rejects UNC paths', () => {
    expect(() => normalizeWorkspacePath('\\\\server\\share\\foo')).toThrow(WorkspacePathError);
  });

  it('rejects NUL bytes', () => {
    expect(() => normalizeWorkspacePath('foo\0bar.ts')).toThrow(/NUL byte/);
  });

  it('rejects empty / whitespace-only paths', () => {
    expect(() => normalizeWorkspacePath('')).toThrow(/empty path/);
    expect(() => normalizeWorkspacePath('./')).toThrow(/normalized to empty path/);
    expect(() => normalizeWorkspacePath('.')).toThrow(/normalized to empty path/);
  });
});

describe('tryNormalizeWorkspacePath', () => {
  it('returns the normalized path for valid input', () => {
    expect(tryNormalizeWorkspacePath('./foo.ts')).toBe('foo.ts');
  });

  it('returns null for invalid input instead of throwing', () => {
    expect(tryNormalizeWorkspacePath('../escape.ts')).toBeNull();
    expect(tryNormalizeWorkspacePath('/abs.ts')).toBeNull();
    expect(tryNormalizeWorkspacePath('')).toBeNull();
  });
});

describe('prefixWorkspacePath', () => {
  it('joins a child workspace prefix with a child-relative path', () => {
    expect(prefixWorkspacePath('subtasks/1', 'output/foo.ts')).toBe('subtasks/1/output/foo.ts');
  });

  it('normalizes both inputs', () => {
    expect(prefixWorkspacePath('./subtasks/1', 'output/./foo.ts')).toBe('subtasks/1/output/foo.ts');
  });

  it('rejects traversal in either input', () => {
    expect(() => prefixWorkspacePath('subtasks/1', '../escape.ts')).toThrow(WorkspacePathError);
    expect(() => prefixWorkspacePath('../escape', 'foo.ts')).toThrow(WorkspacePathError);
  });

  it('rejects absolute child paths', () => {
    expect(() => prefixWorkspacePath('subtasks/1', '/etc/passwd')).toThrow(WorkspacePathError);
  });
});
