import { execSync } from 'child_process';
import { existsSync, readFileSync } from 'fs';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { SecurityAuditOptions, SecurityAuditor } from './security-auditor';

// Mock file system and child_process
vi.mock('fs');
vi.mock('child_process');
vi.mock('glob', () => ({
  globSync: vi.fn(),
}));

const mockExistsSync = vi.mocked(existsSync);
const mockReadFileSync = vi.mocked(readFileSync);
const mockExecSync = vi.mocked(execSync);

describe('SecurityAuditor', () => {
  let auditor: SecurityAuditor;
  let options: SecurityAuditOptions;

  beforeEach(() => {
    options = {
      projectPath: '/test/project',
      enableDependencyCheck: true,
      enableCodeAnalysis: true,
      enableConfigurationCheck: true,
    };
    auditor = new SecurityAuditor(options);
    vi.clearAllMocks();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('audit', () => {
    it('should perform a complete security audit', async () => {
      // Mock package.json
      mockExistsSync.mockReturnValue(true);
      mockReadFileSync.mockReturnValue(JSON.stringify({
        dependencies: { lodash: '^4.17.20' },
        devDependencies: { axios: '^0.21.1' }
      }));

      // Mock npm audit
      mockExecSync.mockReturnValue(JSON.stringify({
        vulnerabilities: {
          lodash: {
            severity: 'high',
            via: [{
              source: 'CVE-2021-23337',
              title: 'Prototype Pollution',
              url: 'https://example.com'
            }]
          }
        }
      }));

      // Mock glob for source files
      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue(['src/test.ts']);

      // Mock source file with vulnerability
      mockReadFileSync.mockImplementation((path: any) => {
        if (path.includes('package.json')) {
          return JSON.stringify({
            dependencies: { lodash: '^4.17.20' },
            devDependencies: { axios: '^0.21.1' }
          });
        }
        if (path.includes('test.ts')) {
          return 'element.innerHTML = userInput; // XSS vulnerability';
        }
        return '';
      });

      const result = await auditor.audit();

      // The audit will find multiple vulnerabilities:
      // 1. npm audit vulnerability (lodash)
      // 2. known vulnerable packages (lodash, axios)
      // 3. XSS vulnerability in code
      // 4. configuration issues (no security headers, no HTTPS)
      expect(result.vulnerabilities.length).toBeGreaterThan(0);
      expect(result.summary.total).toBeGreaterThan(0);
      expect(result.owaspCompliance.score).toBeLessThan(100);
      expect(result.timestamp).toBeInstanceOf(Date);

      // Check that we have at least one dependency and one code vulnerability
      const hasDepVuln = result.vulnerabilities.some(v => v.type === 'dependency');
      const hasCodeVuln = result.vulnerabilities.some(v => v.type === 'xss');
      expect(hasDepVuln).toBe(true);
      expect(hasCodeVuln).toBe(true);
    });

    it('should handle npm audit failures gracefully', async () => {
      mockExistsSync.mockImplementation((path: any) => {
        return path.includes('package.json');
      });

      mockReadFileSync.mockImplementation((path: any) => {
        if (path.includes('package.json')) {
          return JSON.stringify({
            dependencies: { lodash: '^4.17.20' },
            scripts: { dev: 'ordojs dev' }
          });
        }
        return '';
      });

      // Mock npm audit failure
      mockExecSync.mockImplementation(() => {
        throw new Error('npm audit failed');
      });

      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue([]);

      const result = await auditor.audit();

      // Should find known vulnerable packages and configuration issues
      expect(result.vulnerabilities.length).toBeGreaterThan(0);
      const hasDepVuln = result.vulnerabilities.some(v => v.type === 'dependency' && v.description.includes('lodash'));
      expect(hasDepVuln).toBe(true);
    });
  });

  describe('XSS vulnerability detection', () => {
    it('should detect innerHTML usage without sanitization', async () => {
      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue(['src/component.ts']);

      mockExistsSync.mockReturnValue(false); // No package.json
      mockReadFileSync.mockReturnValue('element.innerHTML = userInput;');

      const result = await auditor.audit();

      expect(result.vulnerabilities).toHaveLength(1);
      expect(result.vulnerabilities[0].type).toBe('xss');
      expect(result.vulnerabilities[0].severity).toBe('high');
      expect(result.vulnerabilities[0].description).toContain('innerHTML');
    });

    it('should detect eval usage', async () => {
      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue(['src/dangerous.ts']);

      mockExistsSync.mockReturnValue(false);
      mockReadFileSync.mockReturnValue('eval(userCode);');

      const result = await auditor.audit();

      expect(result.vulnerabilities).toHaveLength(1);
      expect(result.vulnerabilities[0].type).toBe('xss');
      expect(result.vulnerabilities[0].severity).toBe('critical');
      expect(result.vulnerabilities[0].description).toContain('eval');
    });

    it('should detect unescaped template literals in HTML', async () => {
      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue(['src/template.ts']);

      mockExistsSync.mockReturnValue(false);
      mockReadFileSync.mockReturnValue('const html = `<div>${userInput}</div>`;');

      const result = await auditor.audit();

      expect(result.vulnerabilities).toHaveLength(1);
      expect(result.vulnerabilities[0].type).toBe('xss');
      expect(result.vulnerabilities[0].severity).toBe('medium');
    });
  });

  describe('SQL injection detection', () => {
    it('should detect string concatenation in SQL queries', async () => {
      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue(['src/database.ts']);

      mockExistsSync.mockReturnValue(false);
      mockReadFileSync.mockReturnValue('const query = "SELECT * FROM users WHERE id = " + userId;');

      const result = await auditor.audit();

      expect(result.vulnerabilities).toHaveLength(1);
      expect(result.vulnerabilities[0].type).toBe('injection');
      expect(result.vulnerabilities[0].severity).toBe('high');
      expect(result.vulnerabilities[0].description).toContain('SQL injection');
    });
  });

  describe('CSRF vulnerability detection', () => {
    it('should detect forms without CSRF protection', async () => {
      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue(['src/form.html']);

      mockExistsSync.mockReturnValue(false);
      mockReadFileSync.mockReturnValue('<form method="post" action="/submit">');

      const result = await auditor.audit();

      expect(result.vulnerabilities).toHaveLength(1);
      expect(result.vulnerabilities[0].type).toBe('csrf');
      expect(result.vulnerabilities[0].severity).toBe('medium');
    });
  });

  describe('Cryptographic vulnerabilities', () => {
    it('should detect weak cryptographic algorithms', async () => {
      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue(['src/crypto.ts']);

      mockExistsSync.mockReturnValue(false);
      mockReadFileSync.mockReturnValue('crypto.createHash("md5").update(data);');

      const result = await auditor.audit();

      expect(result.vulnerabilities).toHaveLength(1);
      expect(result.vulnerabilities[0].type).toBe('other');
      expect(result.vulnerabilities[0].severity).toBe('medium');
      expect(result.vulnerabilities[0].description).toContain('cryptographic');
    });
  });

  describe('Hardcoded secrets detection', () => {
    it('should detect hardcoded passwords', async () => {
      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue(['src/config.ts']);

      mockExistsSync.mockReturnValue(false);
      mockReadFileSync.mockReturnValue('const password = "secret123";');

      const result = await auditor.audit();

      expect(result.vulnerabilities).toHaveLength(1);
      expect(result.vulnerabilities[0].type).toBe('other');
      expect(result.vulnerabilities[0].severity).toBe('high');
      expect(result.vulnerabilities[0].description).toContain('Hardcoded secret');
    });

    it('should detect hardcoded API keys', async () => {
      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue(['src/api.ts']);

      mockExistsSync.mockReturnValue(false);
      mockReadFileSync.mockReturnValue('const apiKey = "sk-1234567890abcdef";');

      const result = await auditor.audit();

      expect(result.vulnerabilities).toHaveLength(1);
      expect(result.vulnerabilities[0].description).toContain('Hardcoded secret');
    });
  });

  describe('Configuration audit', () => {
    it('should detect missing security headers configuration', async () => {
      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue([]);

      mockExistsSync.mockImplementation((path: any) => {
        return path.includes('ordojs.config.ts');
      });

      mockReadFileSync.mockImplementation((path: any) => {
        if (path.includes('ordojs.config.ts')) {
          return 'export default { build: { outDir: "dist" } };';
        }
        return '';
      });

      const result = await auditor.audit();

      expect(result.vulnerabilities).toHaveLength(1);
      expect(result.vulnerabilities[0].type).toBe('configuration');
      expect(result.vulnerabilities[0].description).toContain('security headers');
    });

    it('should detect missing HTTPS configuration', async () => {
      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue([]);

      mockExistsSync.mockImplementation((path: any) => {
        return path.includes('package.json');
      });

      mockReadFileSync.mockReturnValue(JSON.stringify({
        scripts: {
          dev: 'ordojs dev',
          build: 'ordojs build'
        }
      }));

      const result = await auditor.audit();

      expect(result.vulnerabilities).toHaveLength(1);
      expect(result.vulnerabilities[0].type).toBe('configuration');
      expect(result.vulnerabilities[0].description).toContain('HTTPS');
    });
  });

  describe('OWASP compliance', () => {
    it('should calculate OWASP compliance score', async () => {
      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue([]);
      mockExistsSync.mockReturnValue(false);

      const result = await auditor.audit();

      expect(result.owaspCompliance).toBeDefined();
      expect(result.owaspCompliance.score).toBeGreaterThanOrEqual(0);
      expect(result.owaspCompliance.score).toBeLessThanOrEqual(100);
      expect(result.owaspCompliance.categories).toBeDefined();
    });

    it('should mark OWASP categories as non-compliant when vulnerabilities exist', async () => {
      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue(['src/xss.ts']);

      mockExistsSync.mockReturnValue(false);
      mockReadFileSync.mockReturnValue('element.innerHTML = userInput;');

      const result = await auditor.audit();

      expect(result.owaspCompliance.categories['A03:2021 – Injection']).toBe(false);
    });
  });

  describe('options handling', () => {
    it('should respect enableDependencyCheck option', async () => {
      const auditorWithoutDeps = new SecurityAuditor({
        ...options,
        enableDependencyCheck: false,
      });

      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue([]);
      mockExistsSync.mockReturnValue(false);

      const result = await auditorWithoutDeps.audit();

      expect(mockExecSync).not.toHaveBeenCalled();
      expect(result.vulnerabilities).toHaveLength(0);
    });

    it('should respect enableCodeAnalysis option', async () => {
      const auditorWithoutCode = new SecurityAuditor({
        ...options,
        enableCodeAnalysis: false,
      });

      mockExistsSync.mockReturnValue(false);

      const result = await auditorWithoutCode.audit();

      const { globSync } = await import('glob');
      expect(vi.mocked(globSync)).not.toHaveBeenCalled();
    });

    it('should respect custom include/exclude patterns', async () => {
      const customAuditor = new SecurityAuditor({
        ...options,
        includePatterns: ['**/*.custom'],
        excludePatterns: ['**/ignore/**'],
      });

      const { globSync } = await import('glob');
      vi.mocked(globSync).mockReturnValue([]);
      mockExistsSync.mockReturnValue(false);

      await customAuditor.audit();

      expect(vi.mocked(globSync)).toHaveBeenCalledWith(
        ['**/*.custom'],
        expect.objectContaining({
          ignore: ['**/ignore/**'],
        })
      );
    });
  });
});
