import { createHash } from 'crypto'

import { createCanvas } from 'canvas'

export const CAPTCHA_LENGTH = 6
const CAPTCHA_CHARS = 'ABCDEFGHIJKMNOPRSTUVWXYZ123456789' // Notice that ambiguous characters are removed

/** Hash a captcha value */
function hashCaptchaValue(value: string): string {
  return createHash('sha256').update(value).digest('hex')
}

/** Generate a captcha image as a data URI */
export function generateCaptchaImage(text: string) {
  const width = 144
  const height = 48
  const canvas = createCanvas(width, height)
  const ctx = canvas.getContext('2d')

  // Fill background with gradient
  const gradient = ctx.createLinearGradient(0, 0, width, height)
  gradient.addColorStop(0, '#1a202c')
  gradient.addColorStop(1, '#2d3748')
  ctx.fillStyle = gradient
  ctx.fillRect(0, 0, width, height)

  // Add grid pattern background
  ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)'
  ctx.lineWidth = 1
  for (let i = 0; i < width; i += 10) {
    ctx.beginPath()
    ctx.moveTo(i, 0)
    ctx.lineTo(i, height)
    ctx.stroke()
  }
  for (let i = 0; i < height; i += 10) {
    ctx.beginPath()
    ctx.moveTo(0, i)
    ctx.lineTo(width, i)
    ctx.stroke()
  }

  // Add wavy lines
  for (let i = 0; i < 3; i++) {
    const r = Math.floor(Math.random() * 200 + 55).toString()
    const g = Math.floor(Math.random() * 200 + 55).toString()
    const b = Math.floor(Math.random() * 200 + 55).toString()
    ctx.strokeStyle = 'rgba(' + r + ', ' + g + ', ' + b + ', 0.5)'
    ctx.lineWidth = Math.random() * 3 + 1

    ctx.beginPath()
    const startY = Math.random() * height
    let curveX = 0
    let curveY = startY

    ctx.moveTo(curveX, curveY)

    while (curveX < width) {
      const nextX = curveX + Math.random() * 20 + 10
      const nextY = startY + Math.sin(curveX / 20) * (Math.random() * 15 + 5)
      ctx.quadraticCurveTo(curveX + (nextX - curveX) / 2, curveY + Math.random() * 20 - 10, nextX, nextY)
      curveX = nextX
      curveY = nextY
    }
    ctx.stroke()
  }

  // Add random dots
  for (let i = 0; i < 100; i++) {
    const r = Math.floor(Math.random() * 200 + 55).toString()
    const g = Math.floor(Math.random() * 200 + 55).toString()
    const b = Math.floor(Math.random() * 200 + 55).toString()
    const alpha = (Math.random() * 0.5 + 0.1).toString()
    ctx.fillStyle = 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')'

    ctx.beginPath()
    ctx.arc(Math.random() * width, Math.random() * height, Math.random() * 2 + 1, 0, Math.PI * 2)
    ctx.fill()
  }

  // Draw captcha text with shadow and more distortion
  ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'
  ctx.shadowBlur = 3
  ctx.shadowOffsetX = 2
  ctx.shadowOffsetY = 2

  // Calculate text positioning
  const fontSize = Math.floor(height * 0.55)
  const padding = 15
  const charWidth = (width - padding * 2) / text.length

  // Draw each character with various effects
  for (let i = 0; i < text.length; i++) {
    const r = Math.floor(Math.random() * 100 + 155).toString()
    const g = Math.floor(Math.random() * 100 + 155).toString()
    const b = Math.floor(Math.random() * 100 + 155).toString()
    ctx.fillStyle = 'rgb(' + r + ', ' + g + ', ' + b + ')'

    // Vary font style for each character
    const fontStyle = Math.random() > 0.5 ? 'bold' : 'normal'
    const fontFamily = Math.random() > 0.5 ? 'monospace' : 'Arial'
    const fontSizeStr = fontSize.toString()
    ctx.font = fontStyle + ' ' + fontSizeStr + 'px ' + fontFamily

    // Position with variations
    const xPos = padding + i * charWidth + (Math.random() * 8 - 4)
    const yPos = height * 0.6 + (Math.random() * 12 - 6)
    const rotation = Math.random() * 0.6 - 0.3
    const scale = 0.8 + Math.random() * 0.4

    ctx.save()
    ctx.translate(xPos, yPos)
    ctx.rotate(rotation)
    ctx.scale(scale, scale)

    // Add slight perspective effect
    if (Math.random() > 0.7) {
      ctx.transform(1, 0, 0.1, 1, 0, 0)
    } else if (Math.random() > 0.5) {
      ctx.transform(1, 0, -0.1, 1, 0, 0)
    }

    ctx.fillText(text.charAt(i), 0, 0)
    ctx.restore()
  }

  // Add some noise on top
  for (let x = 0; x < width; x += 3) {
    for (let y = 0; y < height; y += 3) {
      if (Math.random() > 0.95) {
        const alpha = (Math.random() * 0.2 + 0.1).toString()
        ctx.fillStyle = 'rgba(255, 255, 255, ' + alpha + ')'
        ctx.fillRect(x, y, 2, 2)
      }
    }
  }

  return {
    src: canvas.toDataURL('image/png'),
    width,
    height,
    format: 'png',
  } as const satisfies ImageMetadata
}

/** Generate a new captcha with solution, its hash, and image data URI */
export function generateCaptcha() {
  const solution = Array.from({ length: CAPTCHA_LENGTH }, () =>
    CAPTCHA_CHARS.charAt(Math.floor(Math.random() * CAPTCHA_CHARS.length))
  ).join('')

  return {
    solution,
    solutionHash: hashCaptchaValue(solution),
    image: generateCaptchaImage(solution),
  }
}

/** Verify a captcha input against the expected hash */
export function verifyCaptcha(value: string, solutionHash: string): boolean {
  const correctedValue = value
    .toUpperCase()
    .replace(/[^A-Z0-9]/g, '')
    .replace(/0/g, 'O')
    .replace(/Q/g, 'O')
    .replace(/L/g, 'I')
  const valueHash = hashCaptchaValue(correctedValue)
  return valueHash === solutionHash
}
