/**
 * GitHub Comment Formatter
 * Formats scan results as markdown for GitHub PR comments
 */

import type { ScanResult, Vulnerability, VulnerabilitySeverity, VulnerabilityCategory } from '../../shared/types'
import { groupByTheme, limitPerGroup, getBlockingIssues, GroupedFindings } from './grouping'

/**
 * Severity badges for GitHub markdown
 */
const SEVERITY_BADGE: Record<VulnerabilitySeverity, string> = {
  critical: '🔴 Critical',
  high: '🟠 High',
  medium: '🟡 Medium',
  low: '🔵 Low',
  info: '⚪ Info',
}

/**
 * Category documentation URLs
 */
const CATEGORY_DOCS: Partial<Record<VulnerabilityCategory, string>> = {
  hardcoded_secret: 'https://oculum.dev/docs/rules/hardcoded-secrets',
  ai_prompt_injection: 'https://oculum.dev/docs/rules/prompt-injection',
  ai_unsafe_execution: 'https://oculum.dev/docs/rules/unsafe-execution',
  ai_overpermissive_tool: 'https://oculum.dev/docs/rules/overpermissive-tools',
  ai_rag_exfiltration: 'https://oculum.dev/docs/rules/rag-exfiltration',
  ai_endpoint_unprotected: 'https://oculum.dev/docs/rules/unprotected-endpoints',
  ai_schema_mismatch: 'https://oculum.dev/docs/rules/schema-validation',
  sql_injection: 'https://oculum.dev/docs/rules/sql-injection',
  xss: 'https://oculum.dev/docs/rules/xss',
  missing_auth: 'https://oculum.dev/docs/rules/missing-auth',
  data_exposure: 'https://oculum.dev/docs/rules/data-exposure',
}

/**
 * Helper to determine language from file path
 */
function getLanguageFromPath(filePath: string): string {
  const ext = filePath.split('.').pop()?.toLowerCase() || ''
  const langMap: Record<string, string> = {
    ts: 'typescript',
    tsx: 'typescript',
    js: 'javascript',
    jsx: 'javascript',
    py: 'python',
    go: 'go',
    java: 'java',
    rb: 'ruby',
    php: 'php',
    yaml: 'yaml',
    yml: 'yaml',
    json: 'json',
  }
  return langMap[ext] || ''
}

/**
 * Format a single finding as a markdown section with actionable info (PRO-82)
 */
function formatFinding(finding: Vulnerability, options: { showFile?: boolean; showDocs?: boolean } = {}): string {
  const { showFile = true, showDocs = true } = options
  const badge = SEVERITY_BADGE[finding.severity]
  const location = showFile
    ? `\`${finding.filePath}:${finding.lineNumber}\``
    : `Line ${finding.lineNumber}`

  let md = `#### ${badge} ${finding.title}\n\n`
  md += `📍 ${location}\n\n`

  // Impact (why this matters) - shown if available
  if (finding.impact) {
    md += `**Impact:** ${finding.impact}\n\n`
  }

  // Code snippet in collapsible
  if (finding.lineContent && finding.lineContent.trim()) {
    const language = getLanguageFromPath(finding.filePath)
    md += `<details>\n<summary>View code</summary>\n\n`
    md += `\`\`\`${language}\n${finding.lineContent.trim()}\n\`\`\`\n\n`
    md += `</details>\n\n`
  }

  // Fix steps - shown as numbered list (PRO-82)
  if (finding.fixSteps && finding.fixSteps.length > 0) {
    md += `**Fix:**\n`
    finding.fixSteps.forEach((step, i) => {
      md += `${i + 1}. ${step}\n`
    })
    md += '\n'
  } else if (finding.suggestedFix) {
    // Fallback to legacy field
    md += `💡 **Fix:** ${finding.suggestedFix}\n\n`
  }

  // Documentation links
  const docsUrl = CATEGORY_DOCS[finding.category]
  const referenceUrl = finding.references && finding.references.length > 0 ? finding.references[0] : null

  if (showDocs && (docsUrl || referenceUrl)) {
    const links: string[] = []
    if (docsUrl) links.push(`[Learn more](${docsUrl})`)
    if (referenceUrl && referenceUrl !== docsUrl) links.push(`[OWASP/CWE](${referenceUrl})`)
    md += links.join(' · ') + '\n\n'
  }

  return md
}

/**
 * Format a group of findings
 */
function formatGroup(group: GroupedFindings, maxFindings: number = 5): string {
  const { themeIcon, themeName, findings, severityCounts } = group

  // Count summary
  const counts: string[] = []
  if (severityCounts.critical > 0) counts.push(`${severityCounts.critical} critical`)
  if (severityCounts.high > 0) counts.push(`${severityCounts.high} high`)
  if (severityCounts.medium > 0) counts.push(`${severityCounts.medium} medium`)
  if (severityCounts.low > 0) counts.push(`${severityCounts.low} low`)
  if (severityCounts.info > 0) counts.push(`${severityCounts.info} info`)

  let md = `### ${themeIcon} ${themeName}\n`
  md += `> ${counts.join(', ')}\n\n`

  // Show top findings
  const shown = findings.slice(0, maxFindings)
  for (const finding of shown) {
    md += formatFinding(finding) + '\n'
  }

  // Show truncation notice if needed
  if (findings.length > maxFindings) {
    md += `<details>\n<summary>+ ${findings.length - maxFindings} more ${themeName.toLowerCase()} issues</summary>\n\n`
    for (const finding of findings.slice(maxFindings)) {
      md += formatFinding(finding) + '\n'
    }
    md += `</details>\n`
  }

  return md
}

/**
 * Options for GitHub comment formatting
 */
export interface GitHubCommentOptions {
  maxFindingsPerGroup?: number
  showAllFindings?: boolean
  includeFooter?: boolean
  scanDepth?: 'local' | 'verified' | 'deep'
  previousScanCounts?: {
    critical: number
    high: number
    medium: number
    low: number
    info: number
  }
}

/**
 * Format scan result as GitHub PR comment
 */
export function formatGitHubComment(result: ScanResult, options: GitHubCommentOptions = {}): string {
  const {
    maxFindingsPerGroup = 5,
    showAllFindings = false,
    includeFooter = true,
    scanDepth,
    previousScanCounts,
  } = options

  const { vulnerabilities, severityCounts, hasBlockingIssues } = result

  // Professional header with Oculum branding
  let md = `<!-- oculum-security-scan -->\n`
  md += `<div align="center">\n\n`
  md += `# 🛡️ Oculum Security Scan\n\n`
  md += `</div>\n\n`

  // Status banner
  if (hasBlockingIssues) {
    const blocking = severityCounts.critical + severityCounts.high
    md += `> 🚨 **${blocking} blocking issue${blocking === 1 ? '' : 's'} found** — These must be addressed before merging.\n\n`
  } else if (vulnerabilities.length > 0) {
    md += `> ⚠️ **${vulnerabilities.length} issue${vulnerabilities.length === 1 ? '' : 's'} found** — Review recommended, but no blocking issues.\n\n`
  } else {
    md += `> ✅ **No security issues detected** — This PR looks good!\n\n`
    md += formatScanMetadata(result, scanDepth)
    if (includeFooter) {
      md += formatFooter()
    }
    return md
  }

  // Comparison with previous scan if available
  if (previousScanCounts) {
    md += formatComparison(severityCounts, previousScanCounts)
  }

  // Summary section
  md += `## 📊 Summary\n\n`
  md += formatSummaryTable(severityCounts)
  md += '\n'

  // Scan metadata
  md += formatScanMetadata(result, scanDepth)

  // Blocking issues section (critical + high)
  const blockingIssues = getBlockingIssues(vulnerabilities)
  if (blockingIssues.length > 0) {
    md += `## 🚨 Blocking Issues\n\n`
    md += `> These issues must be fixed before merging.\n\n`

    for (const finding of blockingIssues.slice(0, 10)) {
      md += formatFinding(finding) + '\n'
    }

    if (blockingIssues.length > 10) {
      md += `<details>\n<summary>+ ${blockingIssues.length - 10} more blocking issues</summary>\n\n`
      for (const finding of blockingIssues.slice(10)) {
        md += formatFinding(finding, { showDocs: false }) + '\n'
      }
      md += `</details>\n`
    }
    md += '\n'
  }

  // All findings grouped by theme
  const grouped = groupByTheme(vulnerabilities)
  const limited = showAllFindings ? grouped : limitPerGroup(grouped, 10)

  // Only show non-blocking findings in "All Findings" section
  const hasNonBlockingFindings = vulnerabilities.some(
    v => v.severity !== 'critical' && v.severity !== 'high'
  )

  if (hasNonBlockingFindings) {
    md += `## 📋 All Findings by Category\n\n`

    for (const group of limited) {
      // Skip groups with only blocking issues (already shown above)
      const nonBlockingInGroup = group.findings.filter(
        f => f.severity !== 'critical' && f.severity !== 'high'
      )
      if (nonBlockingInGroup.length === 0) continue

      md += formatGroup(group, maxFindingsPerGroup) + '\n'
    }
  }

  // Quick actions
  md += formatQuickActions(hasBlockingIssues)

  // Footer
  if (includeFooter) {
    md += formatFooter()
  }

  return md
}

/**
 * Format summary table
 */
function formatSummaryTable(severityCounts: Record<VulnerabilitySeverity, number>): string {
  let md = `| Severity | Count | Status |\n`
  md += `|:---------|:-----:|:------:|\n`

  if (severityCounts.critical > 0) {
    md += `| 🔴 **Critical** | ${severityCounts.critical} | ❌ Blocking |\n`
  }
  if (severityCounts.high > 0) {
    md += `| 🟠 **High** | ${severityCounts.high} | ❌ Blocking |\n`
  }
  if (severityCounts.medium > 0) {
    md += `| 🟡 Medium | ${severityCounts.medium} | ⚠️ Review |\n`
  }
  if (severityCounts.low > 0) {
    md += `| 🔵 Low | ${severityCounts.low} | ℹ️ Info |\n`
  }
  if (severityCounts.info > 0) {
    md += `| ⚪ Info | ${severityCounts.info} | ℹ️ Info |\n`
  }

  return md
}

/**
 * Format comparison with previous scan
 */
function formatComparison(
  current: Record<VulnerabilitySeverity, number>,
  previous: Record<VulnerabilitySeverity, number>
): string {
  const currentTotal = Object.values(current).reduce((a, b) => a + b, 0)
  const previousTotal = Object.values(previous).reduce((a, b) => a + b, 0)
  const diff = currentTotal - previousTotal

  const currentBlocking = current.critical + current.high
  const previousBlocking = previous.critical + previous.high
  const blockingDiff = currentBlocking - previousBlocking

  let md = `### 📈 Changes from Previous Scan\n\n`

  if (diff === 0 && blockingDiff === 0) {
    md += `No change in findings.\n\n`
  } else {
    const parts: string[] = []

    if (blockingDiff > 0) {
      parts.push(`🔺 **${blockingDiff} new blocking issue${blockingDiff === 1 ? '' : 's'}**`)
    } else if (blockingDiff < 0) {
      parts.push(`🔻 **${Math.abs(blockingDiff)} blocking issue${Math.abs(blockingDiff) === 1 ? '' : 's'} resolved**`)
    }

    if (diff > 0 && diff !== blockingDiff) {
      parts.push(`${diff - blockingDiff} new non-blocking issue${diff - blockingDiff === 1 ? '' : 's'}`)
    } else if (diff < 0 && diff !== blockingDiff) {
      parts.push(`${Math.abs(diff - blockingDiff)} non-blocking issue${Math.abs(diff - blockingDiff) === 1 ? '' : 's'} resolved`)
    }

    md += parts.join(' • ') + '\n\n'
  }

  return md
}

/**
 * Format scan metadata
 */
function formatScanMetadata(result: ScanResult, scanDepth?: string): string {
  let md = `<details>\n<summary>📋 Scan Details</summary>\n\n`
  md += `| Metric | Value |\n`
  md += `|--------|-------|\n`
  md += `| Files scanned | ${result.filesScanned} |\n`
  md += `| Files skipped | ${result.filesSkipped} |\n`
  md += `| Scan duration | ${(result.scanDuration / 1000).toFixed(1)}s |\n`
  if (scanDepth) {
    const depthLabels: Record<string, string> = {
      local: 'Fast (pattern matching)',
      verified: 'Verified (AI-assisted)',
      deep: 'Deep (full semantic)',
    }
    md += `| Scan depth | ${depthLabels[scanDepth] || scanDepth} |\n`
  }
  md += `| Timestamp | ${result.timestamp} |\n`
  md += `\n</details>\n\n`
  return md
}

/**
 * Format quick actions section
 */
function formatQuickActions(hasBlockingIssues: boolean): string {
  let md = `## 🎯 Quick Actions\n\n`

  if (hasBlockingIssues) {
    md += `- [ ] Fix all blocking issues before merging\n`
    md += `- [ ] Review medium/low severity findings\n`
  } else {
    md += `- [ ] Review findings and address as needed\n`
  }

  md += `- [ ] Mark false positives with \`// oculum-ignore\` comment\n`
  md += `- 📖 [View documentation](https://oculum.dev/docs)\n`
  md += `- 🐛 [Report false positive](https://github.com/oculum-security/oculum/issues/new?template=false-positive.md)\n\n`

  return md
}

/**
 * Format footer
 */
function formatFooter(): string {
  let md = `---\n\n`
  md += `<div align="center">\n\n`
  md += `🛡️ Powered by [Oculum Security Scanner](https://oculum.dev) • `
  md += `[Documentation](https://oculum.dev/docs) • `
  md += `[Get Pro](https://oculum.dev/pricing)\n\n`
  md += `</div>\n`
  return md
}

/**
 * Format scan result as a short status comment (for check run summary)
 */
export function formatShortStatus(result: ScanResult): string {
  const { severityCounts, hasBlockingIssues, filesScanned, scanDuration } = result

  if (hasBlockingIssues) {
    const blocking = severityCounts.critical + severityCounts.high
    return `🚨 Found ${blocking} blocking security issue${blocking === 1 ? '' : 's'} (${severityCounts.critical} critical, ${severityCounts.high} high)`
  }

  const total = Object.values(severityCounts).reduce((a, b) => a + b, 0)
  if (total > 0) {
    return `⚠️ Found ${total} issue${total === 1 ? '' : 's'} (${severityCounts.medium} medium, ${severityCounts.low} low, ${severityCounts.info} info)`
  }

  return `✅ No security issues found (scanned ${filesScanned} files in ${(scanDuration / 1000).toFixed(1)}s)`
}

/**
 * Format as inline annotation for GitHub check run (PRO-82: actionable output)
 */
export function formatAnnotation(finding: Vulnerability): {
  path: string
  start_line: number
  end_line: number
  annotation_level: 'failure' | 'warning' | 'notice'
  message: string
  title: string
} {
  const level: 'failure' | 'warning' | 'notice' =
    finding.severity === 'critical' || finding.severity === 'high' ? 'failure' :
    finding.severity === 'medium' ? 'warning' : 'notice'

  // Build actionable message
  let message = ''

  // Impact first (why this matters)
  if (finding.impact) {
    message += `Impact: ${finding.impact}\n\n`
  }

  // Description
  message += finding.description

  // Fix steps or legacy suggestedFix
  if (finding.fixSteps && finding.fixSteps.length > 0) {
    message += '\n\n💡 Fix:\n'
    finding.fixSteps.forEach((step, i) => {
      message += `${i + 1}. ${step}\n`
    })
  } else if (finding.suggestedFix) {
    message += `\n\n💡 Fix: ${finding.suggestedFix}`
  }

  return {
    path: finding.filePath,
    start_line: finding.lineNumber,
    end_line: finding.lineNumber,
    annotation_level: level,
    title: `${SEVERITY_BADGE[finding.severity]} ${finding.title}`,
    message,
  }
}
