import z from "zod"
import { Tool } from "./tool"
import { spawn } from "child_process"
import path from "path"
import { Instance } from "../project/instance"
import { Log } from "../util/log"

const log = Log.create({ service: "validate-wordpress-plugin" })

const DEFAULT_TIMEOUT = 120000 // 2 minutes

export interface ValidationIssue {
  severity: "error" | "warning"
  category: string
  file?: string
  line?: number
  message: string
}

export interface ValidationResult {
  passed: boolean
  errorCount: number
  warningCount: number
  issues: ValidationIssue[]
  summary: string
}

export const ValidateWordPressPluginTool = Tool.define("validate_wordpress_plugin", async () => {
  return {
    description: `Validates a WordPress plugin for security vulnerabilities, coding standards violations, and common runtime errors. Runs comprehensive static analysis including:
- Forbidden/dangerous function detection
- SQL injection pattern detection  
- XSS and input sanitization checks
- Nonce and capability verification
- PHP syntax validation
- Duplicate class/function detection
- Class loading validation
- File path security checks
- WordPress deprecated function detection

CRITICAL: This tool MUST be called before completing ANY WordPress plugin work. Do NOT mark work complete until validation passes.

Returns structured results with severity categorization. Use verbose=true only if you need full output.`,
    parameters: z.object({
      plugin_path: z.string().describe("Absolute or relative path to the WordPress plugin directory to validate"),
      verbose: z.boolean().describe("Include full script output in addition to structured results (default: false)").optional(),
    }),
    async execute(params, ctx) {
      const timeout = DEFAULT_TIMEOUT
      const pluginPath = path.isAbsolute(params.plugin_path)
        ? params.plugin_path
        : path.resolve(Instance.directory, params.plugin_path)

      // Check if directory exists
      const dirExists = await Bun.file(pluginPath).stat().then(s => s.isDirectory()).catch(() => false)
      if (!dirExists) {
        return {
          title: "Validation Failed",
          output: `<validation_result>
<summary>FAIL: Plugin directory not found: ${pluginPath}</summary>
<errors count="1">
  [path] Plugin directory does not exist: ${pluginPath}
</errors>
</validation_result>`,
          metadata: {
            passed: false,
            errorCount: 1,
            warningCount: 0,
            pluginPath,
          },
        }
      }

      // Get the validation script paths
      const scriptsDir = path.join(Instance.worktree || process.cwd(), "scripts")
      const bashScript = path.join(scriptsDir, "validate-wordpress-plugin.sh")

      // Verify script exists
      const bashExists = await Bun.file(bashScript).exists()
      
      if (!bashExists) {
        return {
          title: "Validation Failed",
          output: `<validation_result>
<summary>FAIL: Validation script not found</summary>
<errors count="1">
  [setup] Validation script not found: ${bashScript}
</errors>
</validation_result>`,
          metadata: {
            passed: false,
            errorCount: 1,
            warningCount: 0,
            pluginPath,
          },
        }
      }

      // Run the main validation script
      const result: ValidationResult = {
        passed: true,
        errorCount: 0,
        warningCount: 0,
        issues: [],
        summary: "",
      }

      const output = await runValidationScript(bashScript, pluginPath, timeout, ctx.abort)
      
      // Parse the validation output
      parseValidationOutput(output, result)

      // Build concise summary
      const summaryParts: string[] = []
      
      if (result.errorCount > 0) {
        summaryParts.push(`${result.errorCount} errors`)
        result.passed = false
      }
      
      if (result.warningCount > 0) {
        summaryParts.push(`${result.warningCount} warnings`)
      }
      
      if (result.errorCount === 0 && result.warningCount === 0) {
        summaryParts.push("SUCCESS")
        result.passed = true
      }
      
      result.summary = result.passed 
        ? "✓ All validation checks passed"
        : `✗ Validation failed: ${summaryParts.join(", ")}`

      // Build output string (minimized for token efficiency)
      let outputText = `<validation_result>\n`
      outputText += `<summary>${result.summary}</summary>\n\n`
      
      // Group issues by severity
      const errors = result.issues.filter(i => i.severity === "error")
      const warnings = result.issues.filter(i => i.severity === "warning")
      
      if (errors.length > 0) {
        outputText += `<errors count="${errors.length}">\n`
        // Limit to first 10 errors to save tokens
        errors.slice(0, 10).forEach(issue => {
          outputText += `  [${issue.category}] ${issue.file ? `${issue.file}:${issue.line} - ` : ""}${issue.message}\n`
        })
        if (errors.length > 10) {
          outputText += `  ... and ${errors.length - 10} more errors\n`
        }
        outputText += `</errors>\n\n`
      }
      
      if (warnings.length > 0) {
        outputText += `<warnings count="${warnings.length}">\n`
        // Limit to first 5 warnings
        warnings.slice(0, 5).forEach(issue => {
          outputText += `  [${issue.category}] ${issue.file ? `${issue.file}:${issue.line} - ` : ""}${issue.message}\n`
        })
        if (warnings.length > 5) {
          outputText += `  ... and ${warnings.length - 5} more warnings\n`
        }
        outputText += `</warnings>\n`
      }
      
      outputText += `</validation_result>`

      if (params.verbose) {
        outputText += `\n\n<raw_output>\n${output.slice(-3000)}\n</raw_output>`
      }

      return {
        title: `WordPress Plugin Validation: ${path.basename(pluginPath)}`,
        output: outputText,
        metadata: {
          passed: result.passed,
          errorCount: result.errorCount,
          warningCount: result.warningCount,
          pluginPath,
          truncated: output.length > 5000,
        },
      }
    },
  }
})

async function runValidationScript(
  scriptPath: string,
  pluginPath: string,
  timeout: number,
  abortSignal: AbortSignal
): Promise<string> {
  return new Promise((resolve, reject) => {
    const proc = spawn("bash", [scriptPath, pluginPath], {
      stdio: ["ignore", "pipe", "pipe"],
      detached: process.platform !== "win32",
    })

    let output = ""
    proc.stdout?.on("data", (chunk: Buffer) => { output += chunk.toString() })
    proc.stderr?.on("data", (chunk: Buffer) => { output += chunk.toString() })

    let timedOut = false
    const timeoutTimer = setTimeout(() => {
      timedOut = true
      proc.kill("SIGTERM")
      setTimeout(() => proc.kill("SIGKILL"), 5000)
    }, timeout)

    const abortHandler = () => {
      proc.kill("SIGTERM")
      setTimeout(() => proc.kill("SIGKILL"), 5000)
    }
    abortSignal.addEventListener("abort", abortHandler, { once: true })

    proc.on("exit", () => {
      clearTimeout(timeoutTimer)
      abortSignal.removeEventListener("abort", abortHandler)
      if (timedOut) {
        output += "\n\n[Validation timed out after " + timeout + "ms]"
      }
      resolve(output)
    })

    proc.on("error", (err) => {
      clearTimeout(timeoutTimer)
      abortSignal.removeEventListener("abort", abortHandler)
      reject(err)
    })
  })
}

function parseValidationOutput(output: string, result: ValidationResult): void {
  const lines = output.split("\n")
  
  for (const line of lines) {
    const trimmed = line.trim()
    
    // Parse error patterns - look for ✗, FATAL, ERROR
    if (trimmed.includes("✗") || trimmed.includes("FATAL") || trimmed.includes("ERROR")) {
      const issue = parseIssueLine(trimmed, "error")
      if (issue && !isDuplicateIssue(result.issues, issue)) {
        result.issues.push(issue)
        result.errorCount++
      }
    }
    
    // Parse warning patterns - look for ⚠, WARNING
    if (trimmed.includes("⚠") || trimmed.includes("WARNING")) {
      const issue = parseIssueLine(trimmed, "warning")
      if (issue && !isDuplicateIssue(result.issues, issue)) {
        result.issues.push(issue)
        result.warningCount++
      }
    }
  }
}

function parseIssueLine(line: string, severity: "error" | "warning"): ValidationIssue | null {
  // Remove ANSI color codes
  const cleanLine = line.replace(/\x1b\[[0-9;]*m/g, "")
  
  // Try to extract file path and line number
  const fileMatch = cleanLine.match(/([\w\/\\.-]+\.php):(\d+)/)
  const file = fileMatch ? fileMatch[1] : undefined
  const lineNum = fileMatch ? parseInt(fileMatch[2], 10) : undefined
  
  // Try to extract category from patterns like "[1/11] Checking for..."
  const categoryMatch = cleanLine.match(/\[\d+\/\d+\]\s+Checking\s+(?:for\s+)?(.+?)\.\.\./i)
  const category = categoryMatch ? categoryMatch[1] : extractCategory(cleanLine)
  
  // Extract message - remove common prefixes
  let message = cleanLine
    .replace(/^\s*[✗⚠]\s*/, "")
    .replace(/^\s*FATAL:\s*/i, "")
    .replace(/^\s*ERROR:\s*/i, "")
    .replace(/^\s*WARNING:\s*/i, "")
    .replace(/^\s*SECURITY\s+RISK\s+in\s+/, "")
    .replace(/^\s*SQL\s+INJECTION\s+RISK\s+in\s+/, "")
    .replace(/^\s*Found\s+/, "")
    .trim()
  
  // Skip empty or generic messages
  if (!message || message.length < 10) {
    return null
  }
  
  return {
    severity,
    category,
    file,
    line: lineNum,
    message: message.substring(0, 200), // Limit message length
  }
}

function extractCategory(line: string): string {
  // Try to identify the category from common patterns
  if (line.includes("forbidden") || line.includes("eval") || line.includes("exec")) {
    return "security"
  }
  if (line.includes("SQL") || line.includes("wpdb")) {
    return "sql-injection"
  }
  if (line.includes("XSS") || line.includes("sanitize") || line.includes("escape")) {
    return "xss"
  }
  if (line.includes("syntax") || line.includes("parse")) {
    return "syntax"
  }
  if (line.includes("duplicate") || line.includes("redeclare")) {
    return "duplicates"
  }
  if (line.includes("missing") || line.includes("undefined")) {
    return "undefined"
  }
  if (line.includes("class") || line.includes("function")) {
    return "structure"
  }
  return "general"
}

function isDuplicateIssue(issues: ValidationIssue[], newIssue: ValidationIssue): boolean {
  return issues.some(i => 
    i.message === newIssue.message && 
    i.file === newIssue.file && 
    i.line === newIssue.line
  )
}
