"use node"

import net from "net"
import tls from "tls"
import { action } from "./_generated/server"
import { v } from "convex/values"

import { renderSimpleNotificationEmailHtml } from "./reactEmail"
import { buildBaseUrl } from "./url"

// API do Next.js para verificar preferências
async function sendViaNextApi(params: {
  type: string
  to: { email: string; name?: string; userId?: string }
  subject: string
  data: Record<string, unknown>
  tenantId?: string
}): Promise<{ success: boolean; skipped?: boolean; reason?: string }> {
  const baseUrl = buildBaseUrl()
  const token = process.env.INTERNAL_HEALTH_TOKEN ?? process.env.REPORTS_CRON_SECRET

  if (!token) {
    console.warn("[ticketNotifications] Token interno não configurado, enviando diretamente")
    return { success: false, reason: "no_token" }
  }

  try {
    const response = await fetch(`${baseUrl}/api/notifications/send`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${token}`,
      },
      body: JSON.stringify(params),
    })

    if (!response.ok) {
      const error = await response.text()
      console.error("[ticketNotifications] Erro na API:", error)
      return { success: false, reason: "api_error" }
    }

    return await response.json()
  } catch (error) {
    console.error("[ticketNotifications] Erro ao chamar API:", error)
    return { success: false, reason: "fetch_error" }
  }
}

function b64(input: string) {
  return Buffer.from(input, "utf8").toString("base64")
}

function extractEnvelopeAddress(from: string): string {
  const angle = from.match(/<\s*([^>\s]+)\s*>/)
  if (angle?.[1]) return angle[1]

  const paren = from.match(/\(([^)\s]+@[^)\s]+)\)/)
  if (paren?.[1]) return paren[1]

  const email = from.match(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/)
  if (email?.[0]) return email[0]

  return from
}

type SmtpConfig = {
  host: string
  port: number
  username: string
  password: string
  from: string
  secure: boolean
  timeoutMs: number
}

function buildSmtpConfig(): SmtpConfig | null {
  const host = process.env.SMTP_ADDRESS || process.env.SMTP_HOST
  const port = Number(process.env.SMTP_PORT ?? 465)
  const username = process.env.SMTP_USERNAME || process.env.SMTP_USER
  const password = process.env.SMTP_PASSWORD || process.env.SMTP_PASS

  const legacyFrom = process.env.MAILER_SENDER_EMAIL
  const fromEmail = process.env.SMTP_FROM_EMAIL
  const fromName = process.env.SMTP_FROM_NAME || "Raven"
  const from = legacyFrom || (fromEmail ? `"${fromName}" <${fromEmail}>` : "Raven <no-reply@example.com>")

  if (!host || !username || !password) return null

  const secureFlag = (process.env.SMTP_SECURE ?? process.env.SMTP_TLS ?? "").toLowerCase()
  const secure = secureFlag ? secureFlag === "true" : port === 465

  return { host, port, username, password, from, secure, timeoutMs: 30000 }
}

type SmtpSocket = net.Socket | tls.TLSSocket

type SmtpResponse = { code: number; lines: string[] }

function createSmtpReader(socket: SmtpSocket, timeoutMs: number) {
  let buffer = ""
  let current: SmtpResponse | null = null
  const queue: SmtpResponse[] = []
  let pending:
    | { resolve: (response: SmtpResponse) => void; reject: (error: unknown) => void; timer: ReturnType<typeof setTimeout> }
    | null = null

  const finalize = (response: SmtpResponse) => {
    if (pending) {
      clearTimeout(pending.timer)
      const resolve = pending.resolve
      pending = null
      resolve(response)
      return
    }
    queue.push(response)
  }

  const onData = (data: Buffer) => {
    buffer += data.toString("utf8")
    const lines = buffer.split(/\r?\n/)
    buffer = lines.pop() ?? ""

    for (const line of lines) {
      if (!line) continue
      const match = line.match(/^(\d{3})([ -])\s?(.*)$/)
      if (!match) continue

      const code = Number(match[1])
      const isFinal = match[2] === " "

      if (!current) current = { code, lines: [] }
      current.lines.push(line)

      if (isFinal) {
        const response = current
        current = null
        finalize(response)
      }
    }
  }

  const onError = (error: unknown) => {
    if (pending) {
      clearTimeout(pending.timer)
      const reject = pending.reject
      pending = null
      reject(error)
    }
  }

  socket.on("data", onData)
  socket.on("error", onError)

  const read = () =>
    new Promise<SmtpResponse>((resolve, reject) => {
      const queued = queue.shift()
      if (queued) {
        resolve(queued)
        return
      }
      if (pending) {
        reject(new Error("smtp_concurrent_read"))
        return
      }
      const timer = setTimeout(() => {
        if (!pending) return
        const rejectPending = pending.reject
        pending = null
        rejectPending(new Error("smtp_timeout"))
      }, timeoutMs)
      pending = { resolve, reject, timer }
    })

  const dispose = () => {
    socket.off("data", onData)
    socket.off("error", onError)
    if (pending) {
      clearTimeout(pending.timer)
      pending = null
    }
  }

  return { read, dispose }
}

function isCapability(lines: string[], capability: string) {
  const upper = capability.trim().toUpperCase()
  return lines.some((line) => line.replace(/^(\d{3})([ -])/, "").trim().toUpperCase().startsWith(upper))
}

function assertCode(response: SmtpResponse, expected: number | ((code: number) => boolean), context: string) {
  const ok = typeof expected === "number" ? response.code === expected : expected(response.code)
  if (ok) return
  throw new Error(`smtp_unexpected_response:${context}:${response.code}:${response.lines.join(" | ")}`)
}

async function connectPlain(host: string, port: number, timeoutMs: number) {
  return new Promise<net.Socket>((resolve, reject) => {
    const socket = net.connect(port, host)
    const timer = setTimeout(() => {
      socket.destroy()
      reject(new Error("smtp_connect_timeout"))
    }, timeoutMs)
    socket.once("connect", () => {
      clearTimeout(timer)
      resolve(socket)
    })
    socket.once("error", (e) => {
      clearTimeout(timer)
      reject(e)
    })
  })
}

async function connectTls(host: string, port: number, timeoutMs: number) {
  return new Promise<tls.TLSSocket>((resolve, reject) => {
    const socket = tls.connect({ host, port, rejectUnauthorized: false, servername: host })
    const timer = setTimeout(() => {
      socket.destroy()
      reject(new Error("smtp_connect_timeout"))
    }, timeoutMs)
    socket.once("secureConnect", () => {
      clearTimeout(timer)
      resolve(socket)
    })
    socket.once("error", (e) => {
      clearTimeout(timer)
      reject(e)
    })
  })
}

async function upgradeToStartTls(socket: net.Socket, host: string, timeoutMs: number) {
  return new Promise<tls.TLSSocket>((resolve, reject) => {
    const tlsSocket = tls.connect({ socket, servername: host, rejectUnauthorized: false })
    const timer = setTimeout(() => {
      tlsSocket.destroy()
      reject(new Error("smtp_connect_timeout"))
    }, timeoutMs)
    tlsSocket.once("secureConnect", () => {
      clearTimeout(timer)
      resolve(tlsSocket)
    })
    tlsSocket.once("error", (e) => {
      clearTimeout(timer)
      reject(e)
    })
  })
}

async function sendSmtpMail(cfg: SmtpConfig, to: string, subject: string, html: string) {
  const timeoutMs = Math.max(1000, cfg.timeoutMs)

  let socket: SmtpSocket | null = null
  let reader: ReturnType<typeof createSmtpReader> | null = null

  const sendLine = (line: string) => socket?.write(line + "\r\n")
  const readExpected = async (expected: number | ((code: number) => boolean), context: string) => {
    if (!reader) throw new Error("smtp_reader_not_ready")
    const response = await reader.read()
    assertCode(response, expected, context)
    return response
  }

  try {
    socket = cfg.secure ? await connectTls(cfg.host, cfg.port, timeoutMs) : await connectPlain(cfg.host, cfg.port, timeoutMs)
    reader = createSmtpReader(socket, timeoutMs)

    await readExpected(220, "greeting")

    sendLine(`EHLO ${cfg.host}`)
    let ehlo = await readExpected(250, "ehlo")

    if (!cfg.secure && isCapability(ehlo.lines, "STARTTLS")) {
      sendLine("STARTTLS")
      await readExpected(220, "starttls")

      reader.dispose()
      socket = await upgradeToStartTls(socket as net.Socket, cfg.host, timeoutMs)
      reader = createSmtpReader(socket, timeoutMs)

      sendLine(`EHLO ${cfg.host}`)
      ehlo = await readExpected(250, "ehlo_starttls")
    }

    sendLine("AUTH LOGIN")
    await readExpected(334, "auth_login")
    sendLine(b64(cfg.username))
    await readExpected(334, "auth_username")
    sendLine(b64(cfg.password))
    await readExpected(235, "auth_password")

    const envelopeFrom = extractEnvelopeAddress(cfg.from)
    sendLine(`MAIL FROM:<${envelopeFrom}>`)
    await readExpected((code) => Math.floor(code / 100) === 2, "mail_from")

    sendLine(`RCPT TO:<${to}>`)
    await readExpected((code) => Math.floor(code / 100) === 2, "rcpt_to")

    sendLine("DATA")
    await readExpected(354, "data")

    const headers = [
      `From: ${cfg.from}`,
      `To: ${to}`,
      `Subject: ${subject}`,
      "MIME-Version: 1.0",
      "Content-Type: text/html; charset=UTF-8",
    ].join("\r\n")

    sendLine(headers + "\r\n\r\n" + html + "\r\n.")
    await readExpected((code) => Math.floor(code / 100) === 2, "message")

    sendLine("QUIT")
    await readExpected(221, "quit")
  } finally {
    reader?.dispose()
    socket?.end()
  }
}

export const sendTicketCreatedEmail = action({
  args: {
    to: v.string(),
    userId: v.optional(v.string()),
    userName: v.optional(v.string()),
    ticketId: v.string(),
    reference: v.number(),
    subject: v.string(),
    priority: v.string(),
    tenantId: v.optional(v.string()),
  },
  handler: async (_ctx, { to, userId, userName, ticketId, reference, subject, priority, tenantId }) => {
    const baseUrl = buildBaseUrl()
    const url = `${baseUrl}/portal/tickets/${ticketId}`

    const priorityLabels: Record<string, string> = {
      LOW: "Baixa",
      MEDIUM: "Média",
      HIGH: "Alta",
      URGENT: "Urgente",
    }
    const priorityLabel = priorityLabels[priority] ?? priority
    const mailSubject = `Novo chamado #${reference} aberto`

    // Tenta usar a API do Next.js para verificar preferências
    const apiResult = await sendViaNextApi({
      type: "ticket_created",
      to: { email: to, name: userName, userId },
      subject: mailSubject,
      data: {
        reference,
        subject,
        status: "Pendente",
        priority: priorityLabel,
        viewUrl: url,
      },
      tenantId,
    })

    if (apiResult.success || apiResult.skipped) {
      return apiResult
    }

    // Fallback: envia diretamente se a API falhar
    const smtp = buildSmtpConfig()
    if (!smtp) {
      console.warn("SMTP not configured; skipping ticket created email")
      return { skipped: true }
    }

    const html = await renderSimpleNotificationEmailHtml({
      title: `Novo chamado #${reference} aberto`,
      message: `Seu chamado foi registrado com sucesso. Nossa equipe irá analisá-lo em breve.\n\nAssunto: ${subject}\nPrioridade: ${priorityLabel}\nStatus: Pendente`,
      ctaLabel: "Ver chamado",
      ctaUrl: url,
    })
    await sendSmtpMail(smtp, to, mailSubject, html)
    return { ok: true }
  },
})

export const sendPublicCommentEmail = action({
  args: {
    to: v.string(),
    userId: v.optional(v.string()),
    userName: v.optional(v.string()),
    ticketId: v.string(),
    reference: v.number(),
    subject: v.string(),
    tenantId: v.optional(v.string()),
  },
  handler: async (_ctx, { to, userId, userName, ticketId, reference, subject, tenantId }) => {
    const baseUrl = buildBaseUrl()
    const url = `${baseUrl}/portal/tickets/${ticketId}`
    const mailSubject = `Atualização no chamado #${reference}: ${subject}`

    // Tenta usar a API do Next.js para verificar preferências
    const apiResult = await sendViaNextApi({
      type: "comment_public",
      to: { email: to, name: userName, userId },
      subject: mailSubject,
      data: {
        reference,
        subject,
        viewUrl: url,
      },
      tenantId,
    })

    if (apiResult.success || apiResult.skipped) {
      return apiResult
    }

    // Fallback: envia diretamente se a API falhar
    const smtp = buildSmtpConfig()
    if (!smtp) {
      console.warn("SMTP not configured; skipping ticket comment email")
      return { skipped: true }
    }

    const html = await renderSimpleNotificationEmailHtml({
      title: `Nova atualização no seu chamado #${reference}`,
      message: `Um novo comentário foi adicionado ao chamado "${subject}". Clique abaixo para visualizar e responder pelo portal.`,
      ctaLabel: "Abrir e responder",
      ctaUrl: url,
    })
    await sendSmtpMail(smtp, to, mailSubject, html)
    return { ok: true }
  },
})

export const sendResolvedEmail = action({
  args: {
    to: v.string(),
    userId: v.optional(v.string()),
    userName: v.optional(v.string()),
    ticketId: v.string(),
    reference: v.number(),
    subject: v.string(),
    tenantId: v.optional(v.string()),
  },
  handler: async (_ctx, { to, userId, userName, ticketId, reference, subject, tenantId }) => {
    const baseUrl = buildBaseUrl()
    const url = `${baseUrl}/portal/tickets/${ticketId}`
    const mailSubject = `Seu chamado #${reference} foi encerrado`

    // Tenta usar a API do Next.js para verificar preferências
    const apiResult = await sendViaNextApi({
      type: "ticket_resolved",
      to: { email: to, name: userName, userId },
      subject: mailSubject,
      data: {
        reference,
        subject,
        viewUrl: url,
      },
      tenantId,
    })

    if (apiResult.success || apiResult.skipped) {
      return apiResult
    }

    // Fallback: envia diretamente se a API falhar
    const smtp = buildSmtpConfig()
    if (!smtp) {
      console.warn("SMTP not configured; skipping ticket resolution email")
      return { skipped: true }
    }

    const html = await renderSimpleNotificationEmailHtml({
      title: `Chamado #${reference} encerrado`,
      message: `O chamado "${subject}" foi marcado como concluído. Caso necessário, você pode responder pelo portal para reabrir dentro do prazo.`,
      ctaLabel: "Ver detalhes",
      ctaUrl: url,
    })
    await sendSmtpMail(smtp, to, mailSubject, html)
    return { ok: true }
  },
})

export const sendAutomationEmail = action({
  args: {
    to: v.array(v.string()),
    subject: v.string(),
    html: v.string(),
  },
  handler: async (_ctx, { to, subject, html }) => {
    const smtp = buildSmtpConfig()
    if (!smtp) {
      console.warn("SMTP not configured; skipping automation email")
      return { skipped: true }
    }

    const recipients = to
      .map((email) => email.trim())
      .filter(Boolean)
      .slice(0, 50)

    if (recipients.length === 0) {
      return { skipped: true, reason: "no_recipients" }
    }

    for (const recipient of recipients) {
      await sendSmtpMail(smtp, recipient, subject, html)
    }

    return { ok: true, sent: recipients.length }
  },
})
