/**
 * Security Audit Tool
 *
 * Custom OpenCode tool to run security audits on dependencies and code.
 * Combines npm audit, secret scanning, and OWASP checks.
 *
 * NOTE: This tool SCANS for security anti-patterns - it does not introduce them.
 * The regex patterns below are used to DETECT potential issues in user code.
 */

import { tool } from "@opencode-ai/plugin/tool"
import * as path from "path"
import * as fs from "fs"

export default tool({
  description:
    "Run a comprehensive security audit including dependency vulnerabilities, secret scanning, and common security issues.",
  args: {
    type: tool.schema
      .enum(["all", "dependencies", "secrets", "code"])
      .optional()
      .describe("Type of audit to run (default: all)"),
    fix: tool.schema
      .boolean()
      .optional()
      .describe("Attempt to auto-fix dependency vulnerabilities (default: false)"),
    severity: tool.schema
      .enum(["low", "moderate", "high", "critical"])
      .optional()
      .describe("Minimum severity level to report (default: moderate)"),
  },
  async execute(args, context) {
    const auditType = args.type ?? "all"
    const fix = args.fix ?? false
    const severity = args.severity ?? "moderate"
    const cwd = context.worktree || context.directory

    const results: AuditResults = {
      timestamp: new Date().toISOString(),
      directory: cwd,
      checks: [],
      summary: {
        passed: 0,
        failed: 0,
        warnings: 0,
      },
    }

    // Check for dependencies audit
    if (auditType === "all" || auditType === "dependencies") {
      results.checks.push({
        name: "Dependency Vulnerabilities",
        description: "Check for known vulnerabilities in dependencies",
        command: fix ? "npm audit fix" : "npm audit",
        severityFilter: severity,
        status: "pending",
      })
    }

    // Check for secrets
    if (auditType === "all" || auditType === "secrets") {
      const secretPatterns = await scanForSecrets(cwd)
      if (secretPatterns.length > 0) {
        results.checks.push({
          name: "Secret Detection",
          description: "Scan for hardcoded secrets and API keys",
          status: "failed",
          findings: secretPatterns,
        })
        results.summary.failed++
      } else {
        results.checks.push({
          name: "Secret Detection",
          description: "Scan for hardcoded secrets and API keys",
          status: "passed",
        })
        results.summary.passed++
      }
    }

    // Check for common code security issues
    if (auditType === "all" || auditType === "code") {
      const codeIssues = await scanCodeSecurity(cwd)
      if (codeIssues.length > 0) {
        results.checks.push({
          name: "Code Security",
          description: "Check for common security anti-patterns",
          status: "warning",
          findings: codeIssues,
        })
        results.summary.warnings++
      } else {
        results.checks.push({
          name: "Code Security",
          description: "Check for common security anti-patterns",
          status: "passed",
        })
        results.summary.passed++
      }
    }

    // Generate recommendations
    results.recommendations = generateRecommendations(results)

    return JSON.stringify(results)
  },
})

interface AuditCheck {
  name: string
  description: string
  command?: string
  severityFilter?: string
  status: "pending" | "passed" | "failed" | "warning"
  findings?: Array<{ file: string; issue: string; line?: number }>
}

interface AuditResults {
  timestamp: string
  directory: string
  checks: AuditCheck[]
  summary: {
    passed: number
    failed: number
    warnings: number
  }
  recommendations?: string[]
}

async function scanForSecrets(
  cwd: string
): Promise<Array<{ file: string; issue: string; line?: number }>> {
  const findings: Array<{ file: string; issue: string; line?: number }> = []

  // Patterns to DETECT potential secrets (security scanning)
  const secretPatterns = [
    { pattern: /api[_-]?key\s*[:=]\s*['"][^'"]{20,}['"]/gi, name: "API Key" },
    { pattern: /password\s*[:=]\s*['"][^'"]+['"]/gi, name: "Password" },
    { pattern: /secret\s*[:=]\s*['"][^'"]{10,}['"]/gi, name: "Secret" },
    { pattern: /Bearer\s+[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/g, name: "JWT Token" },
    { pattern: /sk-[a-zA-Z0-9]{32,}/g, name: "OpenAI API Key" },
    { pattern: /ghp_[a-zA-Z0-9]{36}/g, name: "GitHub Token" },
    { pattern: /aws[_-]?secret[_-]?access[_-]?key/gi, name: "AWS Secret" },
  ]

  const ignorePatterns = [
    "node_modules",
    ".git",
    "dist",
    "build",
    ".env.example",
    ".env.template",
  ]

  const srcDir = path.join(cwd, "src")
  if (fs.existsSync(srcDir)) {
    await scanDirectory(srcDir, secretPatterns, ignorePatterns, findings)
  }

  // Also check root config files
  const configFiles = ["config.js", "config.ts", "settings.js", "settings.ts"]
  for (const configFile of configFiles) {
    const filePath = path.join(cwd, configFile)
    if (fs.existsSync(filePath)) {
      await scanFile(filePath, secretPatterns, findings)
    }
  }

  return findings
}

async function scanDirectory(
  dir: string,
  patterns: Array<{ pattern: RegExp; name: string }>,
  ignorePatterns: string[],
  findings: Array<{ file: string; issue: string; line?: number }>
): Promise<void> {
  if (!fs.existsSync(dir)) return

  const entries = fs.readdirSync(dir, { withFileTypes: true })

  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name)

    if (ignorePatterns.some((p) => fullPath.includes(p))) continue

    if (entry.isDirectory()) {
      await scanDirectory(fullPath, patterns, ignorePatterns, findings)
    } else if (entry.isFile() && entry.name.match(/\.(ts|tsx|js|jsx|json)$/)) {
      await scanFile(fullPath, patterns, findings)
    }
  }
}

async function scanFile(
  filePath: string,
  patterns: Array<{ pattern: RegExp; name: string }>,
  findings: Array<{ file: string; issue: string; line?: number }>
): Promise<void> {
  try {
    const content = fs.readFileSync(filePath, "utf-8")
    const lines = content.split("\n")

    for (let i = 0; i < lines.length; i++) {
      const line = lines[i]
      for (const { pattern, name } of patterns) {
        // Reset regex state
        pattern.lastIndex = 0
        if (pattern.test(line)) {
          findings.push({
            file: filePath,
            issue: `Potential ${name} found`,
            line: i + 1,
          })
        }
      }
    }
  } catch {
    // Ignore read errors
  }
}

async function scanCodeSecurity(
  cwd: string
): Promise<Array<{ file: string; issue: string; line?: number }>> {
  const findings: Array<{ file: string; issue: string; line?: number }> = []

  // Patterns to DETECT security anti-patterns (this tool scans for issues)
  // These are detection patterns, not code that uses these anti-patterns
  const securityPatterns = [
    { pattern: /\beval\s*\(/g, name: "eval() usage - potential code injection" },
    { pattern: /innerHTML\s*=/g, name: "innerHTML assignment - potential XSS" },
    { pattern: /dangerouslySetInnerHTML/g, name: "dangerouslySetInnerHTML - potential XSS" },
    { pattern: /document\.write/g, name: "document.write - potential XSS" },
    { pattern: /\$\{.*\}.*sql/gi, name: "Potential SQL injection" },
  ]

  const srcDir = path.join(cwd, "src")
  if (fs.existsSync(srcDir)) {
    await scanDirectory(srcDir, securityPatterns, ["node_modules", ".git", "dist"], findings)
  }

  return findings
}

function generateRecommendations(results: AuditResults): string[] {
  const recommendations: string[] = []

  for (const check of results.checks) {
    if (check.status === "failed" && check.name === "Secret Detection") {
      recommendations.push(
        "CRITICAL: Remove hardcoded secrets and use environment variables instead"
      )
      recommendations.push("Add a .env file (gitignored) for local development")
      recommendations.push("Use a secrets manager for production deployments")
    }

    if (check.status === "warning" && check.name === "Code Security") {
      recommendations.push(
        "Review flagged code patterns for potential security vulnerabilities"
      )
      recommendations.push("Consider using DOMPurify for HTML sanitization")
      recommendations.push("Use parameterized queries for database operations")
    }

    if (check.status === "pending" && check.name === "Dependency Vulnerabilities") {
      recommendations.push("Run 'npm audit' to check for dependency vulnerabilities")
      recommendations.push("Consider using 'npm audit fix' to auto-fix issues")
    }
  }

  if (recommendations.length === 0) {
    recommendations.push("No critical security issues found. Continue following security best practices.")
  }

  return recommendations
}
