import { describe, it, expect } from 'vitest';
import { resolveWritePath, isSecretPath, WriteGuardError } from '../write_guard.js';

const PROJECT_ROOT = '/opt/projects/my-app';

describe('resolveWritePath', () => {
  it('resolves a relative path correctly', () => {
    const result = resolveWritePath(PROJECT_ROOT, 'src/index.ts');
    expect(result).toBe('/opt/projects/my-app/src/index.ts');
  });

  it('resolves nested relative path', () => {
    const result = resolveWritePath(PROJECT_ROOT, 'src/lib/utils.ts');
    expect(result).toBe('/opt/projects/my-app/src/lib/utils.ts');
  });

  it('throws on ../ escape', () => {
    expect(() => resolveWritePath(PROJECT_ROOT, '../../../etc/passwd')).toThrow(WriteGuardError);
    expect(() => resolveWritePath(PROJECT_ROOT, '../../../etc/passwd')).toThrow('path escapes project root');
  });

  it('throws on absolute path outside project root', () => {
    expect(() => resolveWritePath(PROJECT_ROOT, '/etc/shadow')).toThrow(WriteGuardError);
    expect(() => resolveWritePath(PROJECT_ROOT, '/tmp/exploit')).toThrow('path escapes project root');
  });

  it('allows absolute path inside project root', () => {
    const result = resolveWritePath(PROJECT_ROOT, '/opt/projects/my-app/src/new.ts');
    expect(result).toBe('/opt/projects/my-app/src/new.ts');
  });

  it('denies .env files', () => {
    expect(() => resolveWritePath(PROJECT_ROOT, '.env')).toThrow(WriteGuardError);
    expect(() => resolveWritePath(PROJECT_ROOT, '.env')).toThrow('cannot write to secret file');
  });

  it('denies .env.local', () => {
    expect(() => resolveWritePath(PROJECT_ROOT, '.env.local')).toThrow(WriteGuardError);
  });

  it('denies .env.production', () => {
    expect(() => resolveWritePath(PROJECT_ROOT, '.env.production')).toThrow(WriteGuardError);
  });

  it('denies *.pem files', () => {
    expect(() => resolveWritePath(PROJECT_ROOT, 'certs/server.pem')).toThrow(WriteGuardError);
    expect(() => resolveWritePath(PROJECT_ROOT, 'certs/server.pem')).toThrow('cannot write to secret file');
  });

  it('denies *.key files', () => {
    expect(() => resolveWritePath(PROJECT_ROOT, 'ssl/private.key')).toThrow(WriteGuardError);
  });

  it('denies id_rsa', () => {
    expect(() => resolveWritePath(PROJECT_ROOT, '.ssh/id_rsa')).toThrow(WriteGuardError);
  });

  it('denies id_ed25519', () => {
    expect(() => resolveWritePath(PROJECT_ROOT, '.ssh/id_ed25519')).toThrow(WriteGuardError);
  });

  it('denies credentials.json', () => {
    expect(() => resolveWritePath(PROJECT_ROOT, 'credentials.json')).toThrow(WriteGuardError);
  });

  it('passes a normal file inside project', () => {
    const result = resolveWritePath(PROJECT_ROOT, 'src/components/Button.tsx');
    expect(result).toBe('/opt/projects/my-app/src/components/Button.tsx');
  });

  it('passes a non-existent nested file (no realpath)', () => {
    // This is the key difference from BooChat's pathGuard: no realpath means
    // files that don't exist yet still pass validation
    const result = resolveWritePath(PROJECT_ROOT, 'src/new-dir/new-file.ts');
    expect(result).toBe('/opt/projects/my-app/src/new-dir/new-file.ts');
  });

  it('throws on null/empty path', () => {
    expect(() => resolveWritePath(PROJECT_ROOT, '')).toThrow(WriteGuardError);
    expect(() => resolveWritePath(PROJECT_ROOT, '')).toThrow('file path is required');
  });

  it('normalizes ../ within project root and still allows', () => {
    const result = resolveWritePath(PROJECT_ROOT, 'src/../lib/utils.ts');
    expect(result).toBe('/opt/projects/my-app/lib/utils.ts');
  });

  it('rejects path that looks inside root but normalizes outside', () => {
    expect(() => resolveWritePath(PROJECT_ROOT, 'src/../../other-project/hack.ts')).toThrow(WriteGuardError);
  });
});

describe('isSecretPath', () => {
  it('detects .env', () => {
    expect(isSecretPath('.env')).toBe(true);
  });

  it('detects nested .env', () => {
    expect(isSecretPath('config/.env')).toBe(true);
  });

  it('detects *.pfx', () => {
    expect(isSecretPath('certs/client.pfx')).toBe(true);
  });

  it('does not flag normal source files', () => {
    expect(isSecretPath('src/index.ts')).toBe(false);
    expect(isSecretPath('README.md')).toBe(false);
    expect(isSecretPath('package.json')).toBe(false);
  });

  it('returns false for empty string', () => {
    expect(isSecretPath('')).toBe(false);
  });
});
