/**
 * rehype-iframely
 *
 * A rehype plugin for processing embed code blocks with Iframely integration.
 * This plugin finds code blocks with language "embed", extracts their content,
 * and replaces them with wrapper elements that can be enhanced client-side
 * using the IframelyEmbed React component.
 *
 * Similar to rehype-functionplot, rehype-abc, rehype-mermaid, this plugin:
 * - Processes server-side for better performance and SEO
 * - Creates semantic wrapper elements with data attributes
 * - Enables client-side enhancement while maintaining SSR benefits
 *
 * Expected input format:
 * ```embed
 * ---
 * width: 440
 * height: 250
 * ---
 * https://example.com
 * ```
 *
 * Or without frontmatter:
 * ```embed
 * https://example.com
 * ```
 */

import { visit } from 'unist-util-visit'
import type { Element, Root } from 'hast'
import type { Plugin } from 'unified'
import { MAX_CODE_LENGTH, createErrorElement } from './security-constants'
import { logger } from '~/lib/logger'

interface IframelyOptions {
  // Default configuration options for iframe dimensions
  width?: number
  height?: number

  /**
   * Maximum number of embeds allowed in a single document.
   * Additional embeds will be ignored.
   * @default 10
   */
  maxEmbedsPerDoc?: number

  /**
   * Maximum time (in milliseconds) allowed for processing embeds.
   * @default 1000
   */
  performanceThreshold?: number
}

// Default values
const DEFAULT_WIDTH = 444
const DEFAULT_HEIGHT = 250
const DEFAULT_MAX_EMBEDS_PER_DOC = 10
const DEFAULT_PERFORMANCE_THRESHOLD = 1000
const MAX_URL_LENGTH = 2048

// Security: Block private IP ranges to prevent SSRF attacks
const BLOCKED_IP_PATTERNS = [
  // Localhost
  /^https?:\/\/(localhost|127\.[\d.]+)/i,
  // Private IPv4 ranges (RFC 1918)
  /^https?:\/\/10\./i,                              // 10.0.0.0/8
  /^https?:\/\/172\.(1[6-9]|2[0-9]|3[01])\./i,      // 172.16.0.0/12
  /^https?:\/\/192\.168\./i,                        // 192.168.0.0/16
  // Link-local (AWS metadata endpoint - CRITICAL!)
  /^https?:\/\/169\.254\./i,                        // 169.254.0.0/16
  // Loopback IPv6
  /^https?:\/\/\[::1\]/i,
  /^https?:\/\/\[0:0:0:0:0:0:0:1\]/i,
  // Broadcast and special addresses
  /^https?:\/\/(0\.0\.0\.0|255\.255\.255\.255)/i,
]

// Generate a unique ID for each embed
const generateEmbedId = (): string => {
  return 'iframely-embed-' + Math.random().toString(36).substr(2, 9)
}

// Validate URL format and security
const isValidUrl = (url: string): boolean => {
  try {
    // Check URL length to prevent DoS
    if (url.length > MAX_URL_LENGTH) {
      console.warn(`Embed URL exceeds maximum length (${MAX_URL_LENGTH} chars)`)
      return false
    }

    const parsed = new URL(url)

    // Only allow http and https protocols
    if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
      return false
    }

    // Block private IP ranges and localhost (SSRF prevention)
    if (BLOCKED_IP_PATTERNS.some(pattern => pattern.test(url))) {
      console.warn(`Blocked embed URL targeting private IP or localhost: ${parsed.hostname}`)
      return false
    }

    // Domain whitelisting is handled by Iframely service
    // See: ../iframely/whitelist/iframely-domains.json
    return true
  } catch {
    return false
  }
}

const rehypeIframely: Plugin<[IframelyOptions?], Root> = (options = {}) => {
  const {
    width: defaultWidth = DEFAULT_WIDTH,
    height: defaultHeight = DEFAULT_HEIGHT,
    maxEmbedsPerDoc = DEFAULT_MAX_EMBEDS_PER_DOC,
    performanceThreshold = DEFAULT_PERFORMANCE_THRESHOLD
  } = options

  return (tree: Root) => {
    let embedCount = 0
    const startTime = Date.now()

    visit(tree, 'element', (node: Element, index: number | undefined, parent: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
      // Look for code blocks with language-embed class
      if (
        node.tagName === 'pre' &&
        node.children.length === 1 &&
        node.children[0].type === 'element' &&
        node.children[0].tagName === 'code'
      ) {
        const codeElement = node.children[0] as Element
        const classNames = codeElement.properties?.className as string[] | undefined

        if (classNames?.includes('language-embed')) {
          // Check processing time
          if (Date.now() - startTime > performanceThreshold) {
            console.warn('Embed processing exceeded time threshold')
            if (parent && typeof index === 'number') {
              parent.children[index] = createErrorElement(
                'Embed processing exceeded time limit.',
                'embed-error'
              )
            }
            return
          }

          // Check embed count
          if (embedCount >= maxEmbedsPerDoc) {
            console.warn('Maximum number of embeds exceeded')
            if (parent && typeof index === 'number') {
              parent.children[index] = createErrorElement(
                'Maximum number of embeds exceeded.',
                'embed-error'
              )
            }
            return
          }

          // Extract the embed code and frontmatter
          let fullCode = ''
          visit(codeElement, 'text', (textNode: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
            fullCode += textNode.value
          })

          const trimmedCode = fullCode.trim()

          // Skip empty blocks
          if (!trimmedCode) return

          // Validate code length
          if (trimmedCode.length > MAX_CODE_LENGTH) {
            if (parent && typeof index === 'number') {
              parent.children[index] = createErrorElement(
                'Embed code exceeds maximum allowed length.',
                'embed-error'
              )
            }
            return
          }

          // Parse frontmatter and URL
          let embedUrl = trimmedCode
          let embedWidth = defaultWidth
          let embedHeight = defaultHeight

          // Check for frontmatter between --- markers
          const frontmatterMatch = fullCode.match(/^---[\r\n]+(.*?)[\r\n]+---[\r\n]+([\s\S]*)$/s)

          if (frontmatterMatch) {
            try {
              const [, frontmatter, url] = frontmatterMatch

              // Parse frontmatter key-value pairs
              const frontmatterLines = frontmatter.split(/\r?\n/)
              for (const line of frontmatterLines) {
                const trimmedLine = line.trim()
                if (!trimmedLine) continue

                const separatorIndex = trimmedLine.indexOf(':')
                if (separatorIndex === -1) continue

                const key = trimmedLine.slice(0, separatorIndex).trim()
                const value = trimmedLine.slice(separatorIndex + 1).trim()

                if (!key || !value) continue

                switch (key) {
                  case 'width': {
                    const parsedWidth = parseInt(value)
                    if (!isNaN(parsedWidth) && parsedWidth > 0) {
                      embedWidth = parsedWidth
                    }
                    break
                  }
                  case 'height': {
                    const parsedHeight = parseInt(value)
                    if (!isNaN(parsedHeight) && parsedHeight > 0) {
                      embedHeight = parsedHeight
                    }
                    break
                  }
                }
              }

              // Use the extracted URL if frontmatter was present
              embedUrl = url.trim()
            } catch (error) {
              logger.error({ error }, 'Error parsing embed frontmatter:')
              // Keep the original code if parsing fails
            }
          }

          // Validate URL
          if (!isValidUrl(embedUrl)) {
            if (parent && typeof index === 'number') {
              parent.children[index] = createErrorElement(
                'Invalid or missing URL in embed block.',
                'embed-error'
              )
            }
            return
          }

          // Increment embed counter
          embedCount++

          // Generate unique ID for this embed
          const embedId = generateEmbedId()

          // Create wrapper with data attributes for client-side processing
          const wrapper: Element = {
            type: 'element',
            tagName: 'div',
            properties: {
              className: ['iframely-embed-wrapper'],
              dataEmbedUrl: embedUrl,
              dataEmbedId: embedId,
              dataEmbedWidth: String(embedWidth),
              dataEmbedHeight: String(embedHeight)
            },
            children: [
              // Container for the hydrated embed component
              {
                type: 'element',
                tagName: 'div',
                properties: {
                  id: embedId,
                  className: ['iframely-embed-container']
                },
                children: [
                  // Placeholder content (will be replaced by React component)
                  {
                    type: 'element',
                    tagName: 'p',
                    properties: {
                      className: ['text-gray-500', 'text-sm']
                    },
                    children: [
                      { type: 'text', value: 'Loading embed...' }
                    ]
                  }
                ]
              }
            ]
          }

          // Replace the code block with our wrapper
          if (parent && typeof index === 'number') {
            parent.children[index] = wrapper
          }
        }
      }
    })
  }
}

export default rehypeIframely
