/**
 * Path Validator Tests - HIGH-2 Remediation Validation
 *
 * Tests verify:
 * - Path traversal prevention
 * - Prefix validation
 * - Symlink handling
 * - Blocked file detection
 */

import { describe, it, expect, beforeEach } from 'vitest';
import * as path from 'path';
import {
  PathValidator,
  PathValidatorError,
  createProjectPathValidator,
  createFullProjectPathValidator,
} from '../src/path-validator.js';

describe('PathValidator', () => {
  let validator: PathValidator;
  const projectRoot = '/workspaces/project';

  beforeEach(() => {
    validator = new PathValidator({
      allowedPrefixes: [projectRoot],
      allowHidden: false,
    });
  });

  describe('Configuration', () => {
    it('should require at least one prefix', () => {
      expect(() => new PathValidator({
        allowedPrefixes: [],
      })).toThrow(PathValidatorError);
    });

    it('should resolve prefixes to absolute paths', () => {
      const relativeValidator = new PathValidator({
        allowedPrefixes: ['./src'],
      });

      const prefixes = relativeValidator.getAllowedPrefixes();
      expect(prefixes[0]).toMatch(/^\//);
    });
  });

  describe('Path Traversal Prevention', () => {
    it('should block ../ traversal', async () => {
      const result = await validator.validate('/workspaces/project/../etc/passwd');
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Path traversal pattern detected');
    });

    it('should block ..\\ traversal (Windows)', async () => {
      const result = await validator.validate('/workspaces/project\\..\\..\\etc\\passwd');
      expect(result.isValid).toBe(false);
    });

    it('should block URL-encoded traversal (%2e%2e)', async () => {
      const result = await validator.validate('/workspaces/project/%2e%2e/etc/passwd');
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Path traversal pattern detected');
    });

    it('should block double URL-encoded traversal', async () => {
      const result = await validator.validate('/workspaces/project/%252e%252e/etc/passwd');
      expect(result.isValid).toBe(false);
    });

    it('should block mixed encoding traversal', async () => {
      const result = await validator.validate('/workspaces/project/.%2e/etc/passwd');
      expect(result.isValid).toBe(false);
    });

    it('should block null byte injection', async () => {
      const result = await validator.validate('/workspaces/project/file.txt\x00.jpg');
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Path traversal pattern detected');
    });

    it('should block URL-encoded null byte', async () => {
      const result = await validator.validate('/workspaces/project/file.txt%00.jpg');
      expect(result.isValid).toBe(false);
    });
  });

  describe('Prefix Validation', () => {
    it('should allow paths within prefix', async () => {
      const result = await validator.validate('/workspaces/project/src/file.ts');
      expect(result.isValid).toBe(true);
      expect(result.matchedPrefix).toBe(projectRoot);
    });

    it('should block paths outside prefix', async () => {
      const result = await validator.validate('/etc/passwd');
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Path is outside allowed directories');
    });

    it('should block paths that start with prefix but escape', async () => {
      const result = await validator.validate('/workspaces/project-other/file.ts');
      expect(result.isValid).toBe(false);
    });

    it('should handle exact prefix match', async () => {
      const result = await validator.validate(projectRoot);
      expect(result.isValid).toBe(true);
    });

    it('should calculate relative path correctly', async () => {
      const result = await validator.validate('/workspaces/project/src/deep/file.ts');
      expect(result.relativePath).toBe('src/deep/file.ts');
    });
  });

  describe('Hidden File Handling', () => {
    it('should block hidden files by default', async () => {
      const result = await validator.validate('/workspaces/project/.env');
      expect(result.isValid).toBe(false);
      expect(result.errors.some(e => e.includes('Hidden'))).toBe(true);
    });

    it('should block hidden directories by default', async () => {
      const result = await validator.validate('/workspaces/project/.git/config');
      expect(result.isValid).toBe(false);
    });

    it('should allow hidden files when configured', async () => {
      const hiddenValidator = new PathValidator({
        allowedPrefixes: [projectRoot],
        allowHidden: true,
        blockedNames: [], // Remove .git from blocked names for this test
        blockedExtensions: [],
      });

      const result = await hiddenValidator.validate('/workspaces/project/.gitignore');
      expect(result.isValid).toBe(true);
    });
  });

  describe('Blocked Files', () => {
    it('should block .env files', async () => {
      const hiddenValidator = new PathValidator({
        allowedPrefixes: [projectRoot],
        allowHidden: true,
      });

      const result = await hiddenValidator.validate('/workspaces/project/config/.env');
      expect(result.isValid).toBe(false);
      expect(result.errors.some(e => e.includes('.env'))).toBe(true);
    });

    it('should block .pem files', async () => {
      const result = await validator.validate('/workspaces/project/certs/key.pem');
      expect(result.isValid).toBe(false);
    });

    it('should block private key files', async () => {
      const hiddenValidator = new PathValidator({
        allowedPrefixes: [projectRoot],
        allowHidden: true,
      });

      const result = await hiddenValidator.validate('/workspaces/project/.ssh/id_rsa');
      expect(result.isValid).toBe(false);
    });

    it('should block .htpasswd files', async () => {
      const hiddenValidator = new PathValidator({
        allowedPrefixes: [projectRoot],
        allowHidden: true,
      });

      const result = await hiddenValidator.validate('/workspaces/project/.htpasswd');
      expect(result.isValid).toBe(false);
    });
  });

  describe('Path Length', () => {
    it('should block paths exceeding max length', async () => {
      const longPath = '/workspaces/project/' + 'a'.repeat(5000);
      const result = await validator.validate(longPath);
      expect(result.isValid).toBe(false);
      expect(result.errors.some(e => e.includes('maximum length'))).toBe(true);
    });

    it('should allow paths within max length', async () => {
      const result = await validator.validate('/workspaces/project/src/file.ts');
      expect(result.isValid).toBe(true);
    });
  });

  describe('Empty Path', () => {
    it('should reject empty path', async () => {
      const result = await validator.validate('');
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Path is empty');
    });

    it('should reject whitespace-only path', async () => {
      const result = await validator.validate('   ');
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Path is empty');
    });
  });

  describe('Synchronous Validation', () => {
    it('should validate synchronously', () => {
      const result = validator.validateSync('/workspaces/project/src/file.ts');
      expect(result.isValid).toBe(true);
    });

    it('should detect traversal synchronously', () => {
      const result = validator.validateSync('/workspaces/project/../etc/passwd');
      expect(result.isValid).toBe(false);
    });
  });

  describe('validateOrThrow', () => {
    it('should return path when valid', async () => {
      const resolved = await validator.validateOrThrow('/workspaces/project/src/file.ts');
      expect(resolved).toBe('/workspaces/project/src/file.ts');
    });

    it('should throw when invalid', async () => {
      await expect(
        validator.validateOrThrow('/etc/passwd')
      ).rejects.toThrow(PathValidatorError);
    });
  });

  describe('securePath', () => {
    it('should join paths securely', async () => {
      const resolved = await validator.securePath(projectRoot, 'src', 'file.ts');
      expect(resolved).toBe('/workspaces/project/src/file.ts');
    });

    it('should block traversal in segments', async () => {
      await expect(
        validator.securePath(projectRoot, '..', 'etc', 'passwd')
      ).rejects.toThrow(PathValidatorError);
    });
  });

  describe('isWithinAllowed', () => {
    it('should return true for allowed paths', () => {
      expect(validator.isWithinAllowed('/workspaces/project/src')).toBe(true);
    });

    it('should return false for disallowed paths', () => {
      expect(validator.isWithinAllowed('/etc/passwd')).toBe(false);
    });
  });

  describe('Factory Functions', () => {
    it('should create project path validator', () => {
      const projectValidator = createProjectPathValidator('/workspaces/project');
      const prefixes = projectValidator.getAllowedPrefixes();

      expect(prefixes).toContain('/workspaces/project/src');
      expect(prefixes).toContain('/workspaces/project/tests');
      expect(prefixes).toContain('/workspaces/project/docs');
    });

    it('should create full project path validator', () => {
      const fullValidator = createFullProjectPathValidator('/workspaces/project');
      const prefixes = fullValidator.getAllowedPrefixes();

      expect(prefixes).toContain('/workspaces/project');
    });
  });

  describe('HIGH-2 Security Verification', () => {
    it('should prevent access to system files via traversal', async () => {
      const attacks = [
        '../../../etc/passwd',
        '..\\..\\..\\windows\\system32\\config\\sam',
        '....//....//....//etc/passwd',
        '..%252f..%252f..%252fetc/passwd',
        '..%c0%af..%c0%af..%c0%afetc/passwd',
      ];

      for (const attack of attacks) {
        const result = await validator.validate(`/workspaces/project/${attack}`);
        expect(result.isValid).toBe(false);
      }
    });

    it('should resolve symlink-like path attempts', async () => {
      // Even if the path looks like it's within bounds, resolution should catch escapes
      const result = await validator.validate('/workspaces/project/symlink/../../../etc/passwd');
      expect(result.isValid).toBe(false);
    });

    it('should not allow prefix manipulation', async () => {
      // Path starts with project root but escapes via traversal
      const result = await validator.validate('/workspaces/project/../../etc/passwd');
      expect(result.isValid).toBe(false);
    });
  });
});
