/**
 * Input Validator Tests
 * Comprehensive tests for input validation and sanitization
 */

import { beforeEach, describe, expect, it } from 'vitest';
import { InputValidator } from './input-validator';
import { ValidationSchema } from './types';

describe('InputValidator', () => {
  let validator: InputValidator;

  beforeEach(() => {
    validator = new InputValidator();
  });

  describe('Basic Validation Rules', () => {
    it('should validate required fields', () => {
      const schema: ValidationSchema = {
        name: [validator.getRule('required')!]
      };

      const validData = { name: 'John Doe' };
      const invalidData = { name: '' };

      const validResult = validator.validate(validData, schema);
      const invalidResult = validator.validate(invalidData, schema);

      expect(validResult.isValid).toBe(true);
      expect(invalidResult.isValid).toBe(false);
      expect(invalidResult.errors[0].rule).toBe('required');
    });

    it('should validate email addresses', () => {
      const schema: ValidationSchema = {
        email: [validator.getRule('email')!]
      };

      const validData = { email: 'test@example.com' };
      const invalidData = { email: 'invalid-email' };

      const validResult = validator.validate(validData, schema);
      const invalidResult = validator.validate(invalidData, schema);

      expect(validResult.isValid).toBe(true);
      expect(invalidResult.isValid).toBe(false);
      expect(invalidResult.errors[0].rule).toBe('email');
    });

    it('should validate URLs', () => {
      const schema: ValidationSchema = {
        website: [validator.getRule('url')!]
      };

      const validData = { website: 'https://example.com' };
      const invalidData = { website: 'not-a-url' };

      const validResult = validator.validate(validData, schema);
      const invalidResult = validator.validate(invalidData, schema);

      expect(validResult.isValid).toBe(true);
      expect(invalidResult.isValid).toBe(false);
    });

    it('should validate numeric values', () => {
      const schema: ValidationSchema = {
        age: [validator.getRule('numeric')!]
      };

      const validData = { age: '25' };
      const invalidData = { age: 'not-a-number' };

      const validResult = validator.validate(validData, schema);
      const invalidResult = validator.validate(invalidData, schema);

      expect(validResult.isValid).toBe(true);
      expect(validResult.sanitizedData.age).toBe(25);
      expect(invalidResult.isValid).toBe(false);
    });
  });

  describe('SQL Injection Prevention', () => {
    it('should detect SQL injection attempts', () => {
      const maliciousInputs = [
        "'; DROP TABLE users; --",
        "1' OR '1'='1",
        "UNION SELECT * FROM passwords",
        "'; EXEC xp_cmdshell('dir'); --"
      ];

      for (const input of maliciousInputs) {
        const result = validator.validateSqlInput(input);
        expect(result.isValid).toBe(false);
        expect(result.threats.length).toBeGreaterThan(0);
      }
    });

    it('should allow safe SQL inputs', () => {
      const safeInputs = [
        'John Doe',
        'user@example.com',
        '12345',
        'A normal string with spaces'
      ];

      for (const input of safeInputs) {
        const result = validator.validateSqlInput(input);
        expect(result.isValid).toBe(true);
        expect(result.threats.length).toBe(0);
      }
    });

    it('should sanitize SQL injection attempts', () => {
      const input = "'; DROP TABLE users; --";
      const result = validator.validateSqlInput(input);

      expect(typeof result.sanitized).toBe('string');
      expect(result.sanitized).not.toContain('DROP TABLE');
      expect(result.sanitized).not.toContain('--');
    });
  });

  describe('Path Traversal Prevention', () => {
    it('should detect path traversal attempts', () => {
      const maliciousPaths = [
        '../../../etc/passwd',
        '..\\..\\windows\\system32',
        '/etc/shadow',
        'C:\\Windows\\System32\\config\\SAM'
      ];

      for (const path of maliciousPaths) {
        const result = validator.validatePath(path);
        expect(result.isValid).toBe(false);
        expect(result.errors.length).toBeGreaterThan(0);
      }
    });

    it('should allow safe paths', () => {
      const safePaths = [
        'documents/file.txt',
        'images/photo.jpg',
        'data/export.csv'
      ];

      for (const path of safePaths) {
        const result = validator.validatePath(path);
        expect(result.isValid).toBe(true);
        expect(result.errors.length).toBe(0);
      }
    });

    it('should sanitize dangerous paths', () => {
      const dangerousPath = '../../../etc/passwd';
      const result = validator.validatePath(dangerousPath);

      // The path should be invalid, so sanitizedPath might be undefined
      if (result.sanitizedPath) {
        expect(result.sanitizedPath).not.toContain('..');
      } else {
        expect(result.isValid).toBe(false);
      }
    });
  });

  describe('XSS Prevention', () => {
    it('should detect XSS attempts', () => {
      const xssRule = validator.getRule('noXss');
      expect(xssRule).toBeDefined(); // Ensure the rule exists

      const schema: ValidationSchema = {
        content: [xssRule!]
      };

      const xssInputs = [
        '<script>alert("xss")</script>',
        '<img src="x" onerror="alert(1)">',
        'javascript:alert("xss")',
        '<iframe src="javascript:alert(1)"></iframe>'
      ];

      for (const input of xssInputs) {
        const result = validator.validate({ content: input }, schema);
        expect(result.isValid).toBe(false);
      }
    });

    it('should sanitize XSS attempts', () => {
      const xssInput = '<script>alert("xss")</script>';
      const sanitized = validator.sanitize(xssInput, { escapeHtml: true, stripHtml: false });

      expect(sanitized).not.toContain('<script>');
      expect(sanitized).toContain('&lt;script&gt;');
    });
  });

  describe('Custom Rules', () => {
    it('should allow creating custom validation rules', () => {
      const customRule = validator.createRule(
        'strongPassword',
        (value: string) => {
          if (!value) return false;
          return value.length >= 8 &&
                 /[A-Z]/.test(value) &&
                 /[a-z]/.test(value) &&
                 /[0-9]/.test(value);
        },
        'Password must be at least 8 characters with uppercase, lowercase, and numbers'
      );

      const schema: ValidationSchema = {
        password: [customRule]
      };

      const weakPassword = { password: 'weak' };
      const strongPassword = { password: 'StrongPass123' };

      const weakResult = validator.validate(weakPassword, schema);
      const strongResult = validator.validate(strongPassword, schema);

      expect(weakResult.isValid).toBe(false);
      expect(strongResult.isValid).toBe(true);
    });
  });

  describe('Sanitization', () => {
    it('should trim whitespace', () => {
      const input = '  hello world  ';
      const sanitized = validator.sanitize(input, { trimWhitespace: true });
      expect(sanitized).toBe('hello world');
    });

    it('should enforce max length', () => {
      const input = 'this is a very long string that should be truncated';
      const sanitized = validator.sanitize(input, { maxLength: 10 });
      expect(sanitized.length).toBe(10);
    });

    it('should strip HTML tags', () => {
      const input = '<p>Hello <strong>world</strong></p>';
      const sanitized = validator.sanitize(input, { stripHtml: true });
      expect(sanitized).toBe('Hello world');
    });

    it('should escape HTML characters', () => {
      const input = '<script>alert("test")</script>';
      const sanitized = validator.sanitize(input, { escapeHtml: true, stripHtml: false });
      expect(sanitized).toContain('&lt;script&gt;');
    });
  });

  describe('Complex Validation Scenarios', () => {
    it('should handle multiple validation rules', () => {
      const schema: ValidationSchema = {
        email: [
          validator.getRule('required')!,
          validator.getRule('email')!,
          validator.getRule('noXss')!
        ]
      };

      const validData = { email: 'test@example.com' };
      const invalidData = { email: '<script>alert("xss")</script>' };

      const validResult = validator.validate(validData, schema);
      const invalidResult = validator.validate(invalidData, schema);

      expect(validResult.isValid).toBe(true);
      expect(invalidResult.isValid).toBe(false);
      expect(invalidResult.errors.length).toBeGreaterThan(0);
    });

    it('should validate complex nested data', () => {
      const maliciousQuery = "'; DROP TABLE users; --";
      const sqlResult = validator.validateSqlInput(maliciousQuery);
      expect(sqlResult.isValid).toBe(false); // Should detect SQL injection

      const safeQuery = "user profile information";
      const safeResult = validator.validateSqlInput(safeQuery);
      expect(safeResult.isValid).toBe(true); // Should allow safe content
    });
  });

  describe('Configuration Options', () => {
    it('should respect disabled security features', () => {
      const permissiveValidator = new InputValidator({
        enableSqlInjectionPrevention: false,
        enableXssProtection: false,
        enablePathTraversalPrevention: false
      });

      const maliciousInput = "'; DROP TABLE users; --";
      const result = permissiveValidator.validateSqlInput(maliciousInput);

      expect(result.isValid).toBe(true); // Should pass when SQL injection prevention is disabled
    });

    it('should use custom sanitization defaults', () => {
      const customValidator = new InputValidator({
        sanitizationDefaults: {
          maxLength: 5,
          trimWhitespace: true
        }
      });

      const longInput = '  this is a very long string  ';
      const sanitized = customValidator.sanitize(longInput);

      expect(sanitized.length).toBeLessThanOrEqual(5);
    });
  });
});
