#!/usr/bin/env tsx

import { readdirSync, readFileSync, statSync } from 'fs'
import { join, extname } from 'path'

interface SecurityIssue {
  severity: 'critical' | 'high' | 'medium' | 'low'
  file: string
  line: number
  issue: string
  pattern: string
}

const SECURITY_PATTERNS = [
  {
    name: 'eval usage',
    pattern: /eval\s*\(/g,
    severity: 'critical' as const,
    description: 'eval() can execute arbitrary code'
  },
  {
    name: 'innerHTML assignment',
    pattern: /\.innerHTML\s*=/g,
    severity: 'high' as const,
    description: 'innerHTML is vulnerable to XSS. Use textContent or sanitize input'
  },
  {
    name: 'dangerouslySetInnerHTML',
    pattern: /dangerouslySetInnerHTML/g,
    severity: 'high' as const,
    description: 'Ensure the content is properly sanitized'
  },
  {
    name: 'hardcoded credentials',
    pattern: /(password|secret|token|apiKey|api_key)\s*[:=]\s*['"][\w-]+['"]/gi,
    severity: 'critical' as const,
    description: 'Potential hardcoded credentials detected'
  },
  {
    name: 'SQL injection risk',
    pattern: /\$\{.*\}|`.*\+.*`/g,
    severity: 'high' as const,
    description: 'Potential SQL injection via string interpolation'
  },
  {
    name: 'unvalidated fetch',
    pattern: /fetch\s*\(\s*[^'"]/g,
    severity: 'medium' as const,
    description: 'Fetch with non-string URL could be unsafe'
  },
  {
    name: 'missing input validation',
    pattern: /\.value|\.text(?!Content)/g,
    severity: 'medium' as const,
    description: 'User input should be validated'
  },
  {
    name: 'missing CORS headers',
    pattern: /(res\.|response\.).*header|setHeader/g,
    severity: 'medium' as const,
    description: 'Consider CORS security headers'
  }
]

function scanFiles(): SecurityIssue[] {
  const issues: SecurityIssue[] = []
  const srcDir = 'src'
  
  function walkDir(dir: string) {
    try {
      const files = readdirSync(dir)
      
      for (const file of files) {
        const fullPath = join(dir, file)
        const stat = statSync(fullPath)
        
        if (stat.isDirectory() && !['node_modules', '.next', 'dist', 'build'].includes(file)) {
          walkDir(fullPath)
        } else if (['.ts', '.tsx', '.js', '.jsx'].includes(extname(file))) {
          scanFile(fullPath, issues)
        }
      }
    } catch (e) {
      // Skip inaccessible directories
    }
  }
  
  walkDir(srcDir)
  return issues
}

function scanFile(filePath: string, issues: SecurityIssue[]) {
  try {
    const content = readFileSync(filePath, 'utf8')
    const lines = content.split('\n')
    
    for (const patternConfig of SECURITY_PATTERNS) {
      let match
      const pattern = new RegExp(patternConfig.pattern.source, 'g')
      
      while ((match = pattern.exec(content)) !== null) {
        const lineNumber = content.substring(0, match.index).split('\n').length
        
        // Skip if in comment
        const line = lines[lineNumber - 1]
        const commentStart = line.indexOf('//')
        const matchPosition = match.index - content.substring(0, match.index).lastIndexOf('\n') - 1
        
        if (commentStart >= 0 && matchPosition > commentStart) {
          continue
        }
        
        issues.push({
          severity: patternConfig.severity,
          file: filePath,
          line: lineNumber,
          issue: patternConfig.description,
          pattern: patternConfig.name
        })
      }
    }
  } catch (e) {
    // Skip files that can't be read
  }
}

// Main execution
const issues = scanFiles()

const summary = {
  totalIssues: issues.length,
  critical: issues.filter(i => i.severity === 'critical').length,
  high: issues.filter(i => i.severity === 'high').length,
  medium: issues.filter(i => i.severity === 'medium').length,
  low: issues.filter(i => i.severity === 'low').length,
  issues: issues.sort((a, b) => {
    const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 }
    return severityOrder[a.severity] - severityOrder[b.severity]
  }),
  timestamp: new Date().toISOString()
}

console.log(JSON.stringify(summary, null, 2))

// Exit with error if critical issues found
if (summary.critical > 0) {
  process.exit(1)
}
