import net from 'node:net'
import tls from 'node:tls'
import { Buffer } from 'node:buffer'

type ContactPayload = {
  company?: string
  email?: string
  message?: string
  name?: string
  privacy?: boolean
  website?: string
}

class SmtpClient {
  private buffer = ''
  private socket: net.Socket | tls.TLSSocket
  private waiters: Array<() => void> = []

  constructor(socket: net.Socket | tls.TLSSocket) {
    this.socket = socket
    this.attach(socket)
  }

  private attach(socket: net.Socket | tls.TLSSocket) {
    socket.setEncoding('utf8')
    socket.on('data', (chunk) => {
      this.buffer += chunk
      this.waiters.splice(0).forEach((resolve) => resolve())
    })
  }

  private waitForData() {
    return new Promise<void>((resolve, reject) => {
      const onError = (error: Error) => {
        cleanup()
        reject(error)
      }
      const onClose = () => {
        cleanup()
        reject(new Error('SMTP-Verbindung wurde geschlossen.'))
      }
      const cleanup = () => {
        this.socket.off('error', onError)
        this.socket.off('close', onClose)
      }
      this.socket.once('error', onError)
      this.socket.once('close', onClose)
      this.waiters.push(() => {
        cleanup()
        resolve()
      })
    })
  }

  private async readLine() {
    while (!this.buffer.includes('\r\n')) {
      await this.waitForData()
    }

    const index = this.buffer.indexOf('\r\n')
    const line = this.buffer.slice(0, index)
    this.buffer = this.buffer.slice(index + 2)
    return line
  }

  private async readResponse() {
    const lines: string[] = []

    while (true) {
      const line = await this.readLine()
      lines.push(line)

      if (/^\d{3} /.test(line)) {
        const code = Number(line.slice(0, 3))
        return { code, message: lines.join('\n') }
      }
    }
  }

  async connect(expected = 220) {
    const response = await this.readResponse()
    this.expect(response, expected)
  }

  async command(command: string, expected: number | number[]) {
    this.socket.write(`${command}\r\n`)
    const response = await this.readResponse()
    this.expect(response, expected)
    return response
  }

  async data(content: string) {
    this.socket.write(`${content.replace(/^\./gm, '..')}\r\n.\r\n`)
    const response = await this.readResponse()
    this.expect(response, 250)
  }

  async startTls(host: string) {
    this.socket = tls.connect({
      rejectUnauthorized: process.env.SMTP_REJECT_UNAUTHORIZED !== 'false',
      servername: host,
      socket: this.socket
    })
    this.buffer = ''
    this.attach(this.socket)
    await new Promise<void>((resolve, reject) => {
      this.socket.once('secureConnect', resolve)
      this.socket.once('error', reject)
    })
  }

  close() {
    this.socket.end()
  }

  private expect(response: { code: number, message: string }, expected: number | number[]) {
    const expectedCodes = Array.isArray(expected) ? expected : [expected]

    if (!expectedCodes.includes(response.code)) {
      throw new Error(`SMTP-Fehler ${response.code}: ${response.message}`)
    }
  }
}

const cleanHeader = (value: string) => value.replace(/[\r\n]+/g, ' ').trim()

const isValidEmail = (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)

const encodeHeader = (value: string) => {
  const safeValue = cleanHeader(value)
  return /^[\x20-\x7e]*$/.test(safeValue)
    ? safeValue
    : `=?UTF-8?B?${Buffer.from(safeValue, 'utf8').toString('base64')}?=`
}

const createEmail = (payload: Required<Pick<ContactPayload, 'email' | 'message' | 'name'>> & Pick<ContactPayload, 'company'>, from: string, to: string) => {
  const subject = `FEDEO Kontaktanfrage von ${payload.name}`
  const lines = [
    `Name: ${payload.name}`,
    `E-Mail: ${payload.email}`,
    `Unternehmen: ${payload.company || '-'}`,
    '',
    payload.message
  ]

  return [
    `From: ${encodeHeader('FEDEO Webseite')} <${cleanHeader(from)}>`,
    `To: ${cleanHeader(to)}`,
    `Reply-To: ${cleanHeader(payload.email)}`,
    `Subject: ${encodeHeader(subject)}`,
    'MIME-Version: 1.0',
    'Content-Type: text/plain; charset=UTF-8',
    'Content-Transfer-Encoding: 8bit',
    '',
    lines.join('\n')
  ].join('\r\n')
}

const sendMail = async (payload: Required<Pick<ContactPayload, 'email' | 'message' | 'name'>> & Pick<ContactPayload, 'company'>) => {
  const host = process.env.SMTP_HOST
  const user = process.env.SMTP_USER
  const password = process.env.SMTP_PASSWORD
  const to = process.env.CONTACT_TO

  if (!host || !user || !password || !to) {
    throw createError({
      statusCode: 500,
      statusMessage: 'Das Kontaktformular ist noch nicht vollständig konfiguriert.'
    })
  }

  const secure = process.env.SMTP_SECURE === 'true'
  const port = Number(process.env.SMTP_PORT || (secure ? 465 : 587))
  const from = process.env.SMTP_FROM || user
  const client = new SmtpClient(secure
    ? tls.connect({ host, port, rejectUnauthorized: process.env.SMTP_REJECT_UNAUTHORIZED !== 'false', servername: host })
    : net.connect({ host, port })
  )

  try {
    await client.connect()
    await client.command(`EHLO ${process.env.SMTP_HELO || 'fedeo.de'}`, 250)

    if (!secure) {
      await client.command('STARTTLS', 220)
      await client.startTls(host)
      await client.command(`EHLO ${process.env.SMTP_HELO || 'fedeo.de'}`, 250)
    }

    await client.command('AUTH LOGIN', 334)
    await client.command(Buffer.from(user).toString('base64'), 334)
    await client.command(Buffer.from(password).toString('base64'), 235)
    await client.command(`MAIL FROM:<${cleanHeader(from)}>`, 250)
    await client.command(`RCPT TO:<${cleanHeader(to)}>`, [250, 251])
    await client.command('DATA', 354)
    await client.data(createEmail(payload, from, to))
    await client.command('QUIT', 221)
  } finally {
    client.close()
  }
}

export default defineEventHandler(async (event) => {
  const body = await readBody<ContactPayload>(event)
  const name = body.name?.trim() || ''
  const email = body.email?.trim() || ''
  const company = body.company?.trim() || ''
  const message = body.message?.trim() || ''

  if (body.website) {
    return { ok: true }
  }

  if (!name || name.length > 120) {
    throw createError({ statusCode: 400, statusMessage: 'Bitte gib deinen Namen an.' })
  }

  if (!email || !isValidEmail(email) || email.length > 180) {
    throw createError({ statusCode: 400, statusMessage: 'Bitte gib eine gültige E-Mail-Adresse an.' })
  }

  if (!message || message.length < 10 || message.length > 5000) {
    throw createError({ statusCode: 400, statusMessage: 'Bitte beschreibe dein Anliegen etwas genauer.' })
  }

  if (!body.privacy) {
    throw createError({ statusCode: 400, statusMessage: 'Bitte bestätige die Datenschutzhinweise.' })
  }

  await sendMail({ company, email, message, name })

  return { ok: true }
})
