/**
 * PDF Generation Service
 *
 * Generiert PDFs aus HTML oder URLs mittels Playwright.
 * Unterstützt verschiedene Formate und Optionen.
 */

import { chromium, Browser, Page } from 'playwright'
import * as fs from 'fs/promises'
import * as path from 'path'
import { lookup } from 'dns/promises'
import { isIP } from 'net'
import { recordSecurityEvent } from '../security/security-observability'

// Umgebungsvariablen
const PDF_OUTPUT_DIR = process.env.PDF_OUTPUT_DIR || '/tmp/payload-pdfs'
const PDF_GENERATION_DISABLED = process.env.PDF_GENERATION_DISABLED === 'true'
const PDF_ALLOWED_HOSTS = (process.env.PDF_ALLOWED_HOSTS || '')
  .split(',')
  .map((host) => host.trim().toLowerCase())
  .filter(Boolean)
const PDF_ALLOW_HTTP_URLS =
  process.env.NODE_ENV !== 'production' && process.env.PDF_ALLOW_HTTP_URLS === 'true'

// Browser-Instanz (wird wiederverwendet)
let browserInstance: Browser | null = null

function isPrivateIPv4(ip: string): boolean {
  const octets = ip.split('.').map((part) => Number(part))
  if (octets.length !== 4 || octets.some((octet) => Number.isNaN(octet) || octet < 0 || octet > 255)) {
    return true
  }

  const [a, b] = octets
  if (a === 10) return true
  if (a === 127) return true
  if (a === 0) return true
  if (a === 169 && b === 254) return true
  if (a === 172 && b >= 16 && b <= 31) return true
  if (a === 192 && b === 168) return true

  return false
}

function isPrivateIPv6(ip: string): boolean {
  const normalized = ip.toLowerCase()
  if (normalized === '::1') return true
  if (normalized.startsWith('fc') || normalized.startsWith('fd')) return true
  if (normalized.startsWith('fe80')) return true
  return false
}

function isPrivateOrLoopbackIp(ip: string): boolean {
  const version = isIP(ip)
  if (version === 4) return isPrivateIPv4(ip)
  if (version === 6) return isPrivateIPv6(ip)
  return true
}

function hostnameMatchesAllowedHost(hostname: string): boolean {
  if (PDF_ALLOWED_HOSTS.length === 0) {
    return true
  }

  return PDF_ALLOWED_HOSTS.some((allowedHost) => {
    if (allowedHost.startsWith('.')) {
      return hostname.endsWith(allowedHost)
    }
    return hostname === allowedHost
  })
}

function denyPdfSourceUrl(rawUrl: string, reason: string, host?: string): { valid: boolean; reason: string } {
  recordSecurityEvent('pdf_ssrf_blocked', {
    endpoint: '/api/generate-pdf',
    reason,
    targetUrl: rawUrl,
    host,
  })
  return { valid: false, reason }
}

export async function validatePdfSourceUrl(rawUrl: string): Promise<{ valid: boolean; reason?: string }> {
  let parsedUrl: URL
  try {
    parsedUrl = new URL(rawUrl)
  } catch {
    return denyPdfSourceUrl(rawUrl, 'Invalid URL format')
  }

  const isHttps = parsedUrl.protocol === 'https:'
  const isAllowedHttp = parsedUrl.protocol === 'http:' && PDF_ALLOW_HTTP_URLS
  if (!isHttps && !isAllowedHttp) {
    return denyPdfSourceUrl(
      rawUrl,
      'Only HTTPS URLs are allowed (or HTTP with PDF_ALLOW_HTTP_URLS=true in non-production)',
      parsedUrl.hostname,
    )
  }

  if (parsedUrl.username || parsedUrl.password) {
    return denyPdfSourceUrl(rawUrl, 'URL credentials are not allowed', parsedUrl.hostname)
  }

  const hostname = parsedUrl.hostname.trim().toLowerCase()
  if (!hostname) {
    return denyPdfSourceUrl(rawUrl, 'URL host is required')
  }

  if (hostname === 'localhost' || hostname === '0.0.0.0') {
    return denyPdfSourceUrl(rawUrl, 'Localhost is not allowed', hostname)
  }

  if (!hostnameMatchesAllowedHost(hostname)) {
    return denyPdfSourceUrl(rawUrl, 'Host is not in PDF_ALLOWED_HOSTS allowlist', hostname)
  }

  if (isIP(hostname)) {
    if (isPrivateOrLoopbackIp(hostname)) {
      return denyPdfSourceUrl(rawUrl, 'Private or loopback IP addresses are not allowed', hostname)
    }
    return { valid: true }
  }

  try {
    const records = await lookup(hostname, { all: true, verbatim: true })
    if (!records.length) {
      return denyPdfSourceUrl(rawUrl, 'Host could not be resolved', hostname)
    }

    if (records.some((record) => isPrivateOrLoopbackIp(record.address))) {
      return denyPdfSourceUrl(rawUrl, 'Host resolves to private or loopback addresses', hostname)
    }
  } catch {
    return denyPdfSourceUrl(rawUrl, 'Host lookup failed', hostname)
  }

  return { valid: true }
}

export interface PdfOptions {
  format?: 'A4' | 'A3' | 'Letter' | 'Legal'
  landscape?: boolean
  margin?: {
    top?: string
    right?: string
    bottom?: string
    left?: string
  }
  printBackground?: boolean
  scale?: number
  headerTemplate?: string
  footerTemplate?: string
  displayHeaderFooter?: boolean
}

export interface PdfResult {
  success: boolean
  buffer?: Buffer
  outputPath?: string
  filename?: string
  pageCount?: number
  fileSize?: number
  error?: string
  duration?: number
}

/**
 * Holt oder erstellt eine Browser-Instanz
 */
async function getBrowser(): Promise<Browser> {
  if (!browserInstance || !browserInstance.isConnected()) {
    console.log('[PDF] Launching browser...')
    browserInstance = await chromium.launch({
      headless: true,
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',
        '--disable-gpu',
      ],
    })
  }
  return browserInstance
}

/**
 * Schließt die Browser-Instanz
 */
export async function closeBrowser(): Promise<void> {
  if (browserInstance) {
    await browserInstance.close()
    browserInstance = null
    console.log('[PDF] Browser closed')
  }
}

/**
 * Stellt sicher, dass das Output-Verzeichnis existiert
 */
async function ensureOutputDir(): Promise<void> {
  try {
    await fs.mkdir(PDF_OUTPUT_DIR, { recursive: true })
  } catch {
    // Verzeichnis existiert bereits
  }
}

/**
 * Generiert ein PDF aus HTML-Content
 */
export async function generatePdfFromHtml(
  html: string,
  options: PdfOptions = {},
  outputPath?: string,
): Promise<PdfResult> {
  const startTime = Date.now()

  // Check ob PDF-Generierung deaktiviert ist (Test-Modus)
  if (PDF_GENERATION_DISABLED) {
    console.log('[PDF] Generation disabled - returning mock result')
    return {
      success: true,
      buffer: Buffer.from('mock-pdf-content'),
      filename: 'mock.pdf',
      pageCount: 1,
      fileSize: 17,
      duration: Date.now() - startTime,
    }
  }

  let page: Page | null = null

  try {
    const browser = await getBrowser()
    page = await browser.newPage()

    // HTML laden
    await page.setContent(html, { waitUntil: 'networkidle' })

    // PDF-Optionen
    const pdfOptions = {
      format: options.format || 'A4',
      landscape: options.landscape || false,
      margin: options.margin || {
        top: '20mm',
        right: '15mm',
        bottom: '20mm',
        left: '15mm',
      },
      printBackground: options.printBackground !== false,
      scale: options.scale || 1,
      displayHeaderFooter: options.displayHeaderFooter || false,
      headerTemplate: options.headerTemplate || '',
      footerTemplate: options.footerTemplate || '',
    }

    // PDF generieren
    const pdfBuffer = await page.pdf(pdfOptions)

    // Seitenzahl ermitteln (geschätzt aus Dateigröße oder via PDF-Parser)
    // Für genauere Zählung würde man einen PDF-Parser brauchen
    const pageCount = Math.max(1, Math.ceil(pdfBuffer.length / 50000))

    const result: PdfResult = {
      success: true,
      buffer: pdfBuffer,
      pageCount,
      fileSize: pdfBuffer.length,
      duration: Date.now() - startTime,
    }

    // Optional: In Datei speichern
    if (outputPath) {
      await ensureOutputDir()
      const fullPath = path.isAbsolute(outputPath)
        ? outputPath
        : path.join(PDF_OUTPUT_DIR, outputPath)

      await fs.writeFile(fullPath, pdfBuffer)
      result.outputPath = fullPath
      result.filename = path.basename(fullPath)
      console.log(`[PDF] Saved to ${fullPath}`)
    }

    console.log(`[PDF] Generated successfully (${pdfBuffer.length} bytes, ~${pageCount} pages, ${result.duration}ms)`)

    return result
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : 'Unknown error'
    console.error('[PDF] Generation failed:', errorMessage)

    return {
      success: false,
      error: errorMessage,
      duration: Date.now() - startTime,
    }
  } finally {
    if (page) {
      await page.close()
    }
  }
}

/**
 * Generiert ein PDF von einer URL
 */
export async function generatePdfFromUrl(
  url: string,
  options: PdfOptions = {},
  outputPath?: string,
): Promise<PdfResult> {
  const startTime = Date.now()

  // Check ob PDF-Generierung deaktiviert ist (Test-Modus)
  if (PDF_GENERATION_DISABLED) {
    console.log('[PDF] Generation disabled - returning mock result')
    return {
      success: true,
      buffer: Buffer.from('mock-pdf-content'),
      filename: 'mock.pdf',
      pageCount: 1,
      fileSize: 17,
      duration: Date.now() - startTime,
    }
  }

  let page: Page | null = null

  try {
    const urlValidation = await validatePdfSourceUrl(url)
    if (!urlValidation.valid) {
      return {
        success: false,
        error: `URL validation failed: ${urlValidation.reason || 'Invalid URL'}`,
        duration: Date.now() - startTime,
      }
    }

    const browser = await getBrowser()
    page = await browser.newPage()

    // URL laden
    await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 })

    // PDF-Optionen
    const pdfOptions = {
      format: options.format || 'A4',
      landscape: options.landscape || false,
      margin: options.margin || {
        top: '20mm',
        right: '15mm',
        bottom: '20mm',
        left: '15mm',
      },
      printBackground: options.printBackground !== false,
      scale: options.scale || 1,
      displayHeaderFooter: options.displayHeaderFooter || false,
      headerTemplate: options.headerTemplate || '',
      footerTemplate: options.footerTemplate || '',
    }

    // PDF generieren
    const pdfBuffer = await page.pdf(pdfOptions)
    const pageCount = Math.max(1, Math.ceil(pdfBuffer.length / 50000))

    const result: PdfResult = {
      success: true,
      buffer: pdfBuffer,
      pageCount,
      fileSize: pdfBuffer.length,
      duration: Date.now() - startTime,
    }

    // Optional: In Datei speichern
    if (outputPath) {
      await ensureOutputDir()
      const fullPath = path.isAbsolute(outputPath)
        ? outputPath
        : path.join(PDF_OUTPUT_DIR, outputPath)

      await fs.writeFile(fullPath, pdfBuffer)
      result.outputPath = fullPath
      result.filename = path.basename(fullPath)
      console.log(`[PDF] Saved to ${fullPath}`)
    }

    console.log(`[PDF] Generated from URL successfully (${pdfBuffer.length} bytes, ~${pageCount} pages, ${result.duration}ms)`)

    return result
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : 'Unknown error'
    console.error('[PDF] Generation from URL failed:', errorMessage)

    return {
      success: false,
      error: errorMessage,
      duration: Date.now() - startTime,
    }
  } finally {
    if (page) {
      await page.close()
    }
  }
}

/**
 * Generiert einen eindeutigen Dateinamen für eine PDF
 */
export function generatePdfFilename(
  prefix: string = 'document',
  tenantId?: number,
  documentType?: string,
): string {
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
  const parts = [prefix]

  if (documentType) {
    parts.push(documentType)
  }
  if (tenantId) {
    parts.push(`tenant-${tenantId}`)
  }
  parts.push(timestamp)

  return `${parts.join('_')}.pdf`
}

/**
 * Löscht eine generierte PDF-Datei
 */
export async function deletePdfFile(filePath: string): Promise<boolean> {
  try {
    await fs.unlink(filePath)
    console.log(`[PDF] Deleted ${filePath}`)
    return true
  } catch {
    console.warn(`[PDF] Could not delete ${filePath}`)
    return false
  }
}

/**
 * Liest eine PDF-Datei als Buffer
 */
export async function readPdfFile(filePath: string): Promise<Buffer | null> {
  try {
    return await fs.readFile(filePath)
  } catch {
    console.warn(`[PDF] Could not read ${filePath}`)
    return null
  }
}
