#!/usr/bin/env npx tsx
/**
 * M7: Real-Repo Validation Script
 *
 * Runs security scans on real-world AI/LLM codebases to validate
 * scanner effectiveness before beta launch.
 *
 * Target repos:
 * - langchainjs (LangChain.js)
 * - ai (Vercel AI SDK)
 * - openai-cookbook
 * - anthropic-cookbook
 *
 * Usage:
 *   npx tsx packages/scanner/src/__tests__/validation/run-validation.ts
 *   npx tsx packages/scanner/src/__tests__/validation/run-validation.ts --repo langchainjs
 *   npx tsx packages/scanner/src/__tests__/validation/run-validation.ts --depth local
 */

import * as fs from 'fs'
import * as path from 'path'
import { glob } from 'glob'
import { runScan, type ScanFile, type ScanResult, type ScanDepth } from '../../index'

// Configuration
const VALIDATION_DIR = path.join(__dirname, '../../../validation-repos')
const RESULTS_DIR = path.join(__dirname, '../../../validation-results')

const TARGET_REPOS = ['langchainjs', 'ai', 'openai-cookbook', 'anthropic-cookbook']
const SCAN_DEPTHS: ScanDepth[] = ['local', 'verified']

// File patterns to scan
const INCLUDE_PATTERNS = [
  '**/*.ts',
  '**/*.tsx',
  '**/*.js',
  '**/*.jsx',
  '**/*.py',
  '**/*.json',
  '**/*.yaml',
  '**/*.yml',
]

// Patterns to exclude
const EXCLUDE_PATTERNS = [
  '**/node_modules/**',
  '**/dist/**',
  '**/build/**',
  '**/.git/**',
  '**/coverage/**',
  '**/__pycache__/**',
  '**/venv/**',
  '**/.venv/**',
  '**/vendor/**',
  '**/*.min.js',
  '**/*.bundle.js',
  '**/package-lock.json',
  '**/yarn.lock',
  '**/pnpm-lock.yaml',
]

// Max file size (50KB as per scanner spec)
const MAX_FILE_SIZE = 50 * 1024

interface ValidationConfig {
  repos: string[]
  depths: ScanDepth[]
  maxFilesPerRepo?: number
  verbose: boolean
}

/**
 * Collect scannable files from a repository
 */
async function collectFiles(repoPath: string, maxFiles?: number): Promise<ScanFile[]> {
  const files: ScanFile[] = []

  for (const pattern of INCLUDE_PATTERNS) {
    const matches = await glob(pattern, {
      cwd: repoPath,
      ignore: EXCLUDE_PATTERNS,
      nodir: true,
      absolute: false,
    })

    for (const match of matches) {
      if (maxFiles && files.length >= maxFiles) break

      const filePath = path.join(repoPath, match)

      try {
        const stats = fs.statSync(filePath)
        if (stats.size > MAX_FILE_SIZE) continue

        const content = fs.readFileSync(filePath, 'utf-8')
        const ext = path.extname(match).slice(1)

        files.push({
          path: match,
          content,
          language: getLanguage(ext),
          size: stats.size,
        })
      } catch (err) {
        // Skip files that can't be read
        continue
      }
    }

    if (maxFiles && files.length >= maxFiles) break
  }

  return files
}

/**
 * Map file extension to language
 */
function getLanguage(ext: string): string {
  const langMap: Record<string, string> = {
    ts: 'typescript',
    tsx: 'typescript',
    js: 'javascript',
    jsx: 'javascript',
    py: 'python',
    json: 'json',
    yaml: 'yaml',
    yml: 'yaml',
  }
  return langMap[ext] || ext
}

/**
 * Run a scan on a repository
 */
async function scanRepo(
  repoName: string,
  depth: ScanDepth,
  verbose: boolean,
  maxFiles?: number
): Promise<ScanResult> {
  const repoPath = path.join(VALIDATION_DIR, repoName)

  if (!fs.existsSync(repoPath)) {
    throw new Error(`Repository not found: ${repoPath}. Run: git clone --depth 1 https://github.com/<org>/${repoName}.git ${repoPath}`)
  }

  console.log(`\n${'='.repeat(60)}`)
  console.log(`Scanning: ${repoName} (depth: ${depth})`)
  console.log('='.repeat(60))

  // Collect files
  const startCollect = Date.now()
  const files = await collectFiles(repoPath, maxFiles)
  const collectTime = Date.now() - startCollect

  console.log(`Collected ${files.length} files in ${collectTime}ms`)

  if (files.length === 0) {
    throw new Error(`No scannable files found in ${repoPath}`)
  }

  // Run scan
  const result = await runScan(
    files,
    {
      name: repoName,
      url: `https://github.com/${getRepoOrg(repoName)}/${repoName}`,
      branch: 'main',
    },
    {
      enableAI: depth !== 'local', // Only enable AI for verified/deep
      scanDepth: depth,
    },
    verbose ? (progress) => {
      console.log(`  [${progress.status}] ${progress.message}`)
    } : undefined
  )

  // Print summary
  console.log(`\nScan complete in ${result.scanDuration}ms`)
  console.log(`  Files scanned: ${result.filesScanned}`)
  console.log(`  Total findings: ${result.vulnerabilities.length}`)
  console.log(`  Severity breakdown:`)
  console.log(`    Critical: ${result.severityCounts.critical}`)
  console.log(`    High:     ${result.severityCounts.high}`)
  console.log(`    Medium:   ${result.severityCounts.medium}`)
  console.log(`    Low:      ${result.severityCounts.low}`)
  console.log(`    Info:     ${result.severityCounts.info}`)

  return result
}

/**
 * Get GitHub organization for a repo
 */
function getRepoOrg(repoName: string): string {
  const orgs: Record<string, string> = {
    langchainjs: 'langchain-ai',
    ai: 'vercel',
    'openai-cookbook': 'openai',
    'anthropic-cookbook': 'anthropics',
  }
  return orgs[repoName] || 'unknown'
}

/**
 * Save scan results to file
 */
function saveResults(repoName: string, depth: ScanDepth, result: ScanResult): string {
  const outputPath = path.join(RESULTS_DIR, `${repoName}-${depth}.json`)

  // Ensure results directory exists
  if (!fs.existsSync(RESULTS_DIR)) {
    fs.mkdirSync(RESULTS_DIR, { recursive: true })
  }

  fs.writeFileSync(outputPath, JSON.stringify(result, null, 2))
  console.log(`Results saved to: ${outputPath}`)

  return outputPath
}

/**
 * Parse command line arguments
 */
function parseArgs(): ValidationConfig {
  const args = process.argv.slice(2)
  const config: ValidationConfig = {
    repos: TARGET_REPOS,
    depths: SCAN_DEPTHS,
    verbose: false,
  }

  for (let i = 0; i < args.length; i++) {
    const arg = args[i]

    if (arg === '--repo' && args[i + 1]) {
      const repo = args[i + 1]
      if (!TARGET_REPOS.includes(repo)) {
        console.error(`Unknown repo: ${repo}. Valid repos: ${TARGET_REPOS.join(', ')}`)
        process.exit(1)
      }
      config.repos = [repo]
      i++
    } else if (arg === '--depth' && args[i + 1]) {
      const depth = args[i + 1] as ScanDepth
      if (!['local', 'verified', 'deep'].includes(depth)) {
        console.error(`Invalid depth: ${depth}. Valid depths: local, verified, deep`)
        process.exit(1)
      }
      config.depths = [depth]
      i++
    } else if (arg === '--max-files' && args[i + 1]) {
      config.maxFilesPerRepo = parseInt(args[i + 1], 10)
      i++
    } else if (arg === '--verbose' || arg === '-v') {
      config.verbose = true
    } else if (arg === '--help' || arg === '-h') {
      console.log(`
M7: Real-Repo Validation Script

Usage:
  npx tsx run-validation.ts [options]

Options:
  --repo <name>       Scan only this repo (langchainjs, ai, openai-cookbook, anthropic-cookbook)
  --depth <depth>     Use only this scan depth (local, verified, deep)
  --max-files <n>     Limit files per repo (for faster testing)
  --verbose, -v       Show detailed progress
  --help, -h          Show this help

Examples:
  npx tsx run-validation.ts                           # Scan all repos at all depths
  npx tsx run-validation.ts --repo langchainjs        # Scan only LangChain.js
  npx tsx run-validation.ts --depth local             # Only local scans
  npx tsx run-validation.ts --repo ai --max-files 50  # Quick test on Vercel AI SDK
`)
      process.exit(0)
    }
  }

  return config
}

/**
 * Main entry point
 */
async function main() {
  const config = parseArgs()
  const results: Map<string, ScanResult> = new Map()

  console.log('\n' + '='.repeat(60))
  console.log('M7: REAL-REPO VALIDATION')
  console.log('='.repeat(60))
  console.log(`Repos: ${config.repos.join(', ')}`)
  console.log(`Depths: ${config.depths.join(', ')}`)
  if (config.maxFilesPerRepo) {
    console.log(`Max files per repo: ${config.maxFilesPerRepo}`)
  }

  // Check repos exist
  for (const repo of config.repos) {
    const repoPath = path.join(VALIDATION_DIR, repo)
    if (!fs.existsSync(repoPath)) {
      console.error(`\nError: Repository not found: ${repoPath}`)
      console.error(`Please clone it first:`)
      console.error(`  cd ${VALIDATION_DIR}`)
      console.error(`  git clone --depth 1 https://github.com/${getRepoOrg(repo)}/${repo}.git`)
      process.exit(1)
    }
  }

  // Run scans
  const totalScans = config.repos.length * config.depths.length
  let scanCount = 0

  for (const repo of config.repos) {
    for (const depth of config.depths) {
      scanCount++
      console.log(`\n[${scanCount}/${totalScans}] Starting scan...`)

      try {
        const result = await scanRepo(repo, depth, config.verbose, config.maxFilesPerRepo)
        const key = `${repo}-${depth}`
        results.set(key, result)
        saveResults(repo, depth, result)
      } catch (err) {
        console.error(`Error scanning ${repo} at ${depth}:`, err)
      }
    }
  }

  // Print summary
  console.log('\n' + '='.repeat(60))
  console.log('VALIDATION SUMMARY')
  console.log('='.repeat(60))

  for (const [key, result] of results) {
    const [repo, depth] = key.split('-')
    const mediumPlus = result.severityCounts.critical +
                       result.severityCounts.high +
                       result.severityCounts.medium

    console.log(`\n${repo} (${depth}):`)
    console.log(`  Files: ${result.filesScanned}`)
    console.log(`  Total findings: ${result.vulnerabilities.length}`)
    console.log(`  Medium+ findings: ${mediumPlus} (to triage)`)
    console.log(`  Duration: ${result.scanDuration}ms`)
  }

  console.log('\n' + '='.repeat(60))
  console.log('Next steps:')
  console.log('1. Run analyze-results.ts to generate detailed metrics')
  console.log('2. Review medium+ findings for FP triage')
  console.log('3. Update docs/RESULTSCOMPARISON.md with findings')
  console.log('='.repeat(60))
}

// Run
main().catch(err => {
  console.error('Validation failed:', err)
  process.exit(1)
})
