"use node"

import tls from "tls"

import { action } from "./_generated/server"
import { api } from "./_generated/api"
import { v } from "convex/values"
import type { Id } from "./_generated/dataModel"

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

async function sendSmtpMail(cfg: { host: string; port: number; username: string; password: string; from: string }, to: string, subject: string, html: string) {
  return new Promise<void>((resolve, reject) => {
    const socket = tls.connect(cfg.port, cfg.host, { rejectUnauthorized: false }, () => {
      let buffer = ""
      const send = (line: string) => socket.write(line + "\r\n")
      const wait = (expected: string | RegExp) =>
        new Promise<void>((res) => {
          const onData = (data: Buffer) => {
            buffer += data.toString()
            const lines = buffer.split(/\r?\n/)
            const last = lines.filter(Boolean).slice(-1)[0] ?? ""
            if (typeof expected === "string" ? last.startsWith(expected) : expected.test(last)) {
              socket.removeListener("data", onData)
              res()
            }
          }
          socket.on("data", onData)
          socket.on("error", reject)
        })

      ;(async () => {
        await wait(/^220 /)
        send(`EHLO ${cfg.host}`)
        await wait(/^250-/)
        await wait(/^250 /)
        send("AUTH LOGIN")
        await wait(/^334 /)
        send(b64(cfg.username))
        await wait(/^334 /)
        send(b64(cfg.password))
        await wait(/^235 /)
        send(`MAIL FROM:<${cfg.from.match(/<(.+)>/)?.[1] ?? cfg.from}>`)
        await wait(/^250 /)
        send(`RCPT TO:<${to}>`)
        await wait(/^250 /)
        send("DATA")
        await wait(/^354 /)
        const headers = [
          `From: ${cfg.from}`,
          `To: ${to}`,
          `Subject: ${subject}`,
          "MIME-Version: 1.0",
          "Content-Type: text/html; charset=UTF-8",
        ].join("\r\n")
        send(headers + "\r\n\r\n" + html + "\r\n.")
        await wait(/^250 /)
        send("QUIT")
        socket.end()
        resolve()
      })().catch(reject)
    })
    socket.on("error", reject)
  })
}

export const sendHoursUsageAlerts = action({
  args: { range: v.optional(v.string()), threshold: v.optional(v.number()) },
  handler: async (ctx, { range, threshold }) => {
    const R = (range ?? "30d") as string
    const T = typeof threshold === "number" ? threshold : 90

    const smtp = {
      host: process.env.SMTP_ADDRESS!,
      port: Number(process.env.SMTP_PORT ?? 465),
      username: process.env.SMTP_USERNAME!,
      password: process.env.SMTP_PASSWORD!,
      from: process.env.MAILER_SENDER_EMAIL || "no-reply@example.com",
    }
    if (!smtp.host || !smtp.username || !smtp.password) {
      console.warn("SMTP not configured; skipping alerts send")
      return { sent: 0 }
    }

    const targetHour = Number(process.env.ALERTS_LOCAL_HOUR ?? 8)
    const now = new Date()
    const fmt = new Intl.DateTimeFormat("en-CA", { timeZone: "America/Sao_Paulo", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false })
    const parts = Object.fromEntries(fmt.formatToParts(now).map((p) => [p.type, p.value])) as Record<string, string>
    const hourSP = Number(parts.hour)
    if (hourSP !== targetHour) {
      return { skipped: true, reason: "hour_guard" }
    }

    const dayKey = `${parts.year}-${parts.month}-${parts.day}`
    const startSP = new Date(`${dayKey}T00:00:00-03:00`).getTime()
    const endSP = startSP + 24 * 60 * 60 * 1000

    const tenants = await ctx.runQuery(api.alerts.tenantIds, {})
    let totalSent = 0

    for (const tenantId of tenants) {
      const report = await ctx.runQuery(api.reports.hoursByClientInternal, { tenantId, range: R })
      type Item = {
        companyId: Id<"companies">
        name: string
        internalMs: number
        externalMs: number
        totalMs: number
        contractedHoursPerMonth: number | null
      }
      const items = (report.items ?? []) as Item[]
      const candidates = items.filter((i) => i.contractedHoursPerMonth != null && (i.totalMs / 3600000) / (i.contractedHoursPerMonth || 1) * 100 >= T)

      for (const item of candidates) {
        const already = await ctx.runQuery(api.alerts.existsForCompanyRange, { tenantId, companyId: item.companyId, start: startSP, end: endSP })
        if (already) continue
        const managers = await ctx.runQuery(api.alerts.managersForCompany, { tenantId, companyId: item.companyId })
        if (managers.length === 0) continue
        const usagePct = (((item.totalMs / 3600000) / (item.contractedHoursPerMonth || 1)) * 100)
        const subject = `Alerta: uso de horas em ${item.name} acima de ${T}%`
        const body = `
          <p>Olá,</p>
          <p>O uso de horas contratadas para <strong>${item.name}</strong> atingiu <strong>${usagePct.toFixed(1)}%</strong>.</p>
          <ul>
            <li>Horas internas: <strong>${(item.internalMs/3600000).toFixed(2)}</strong></li>
            <li>Horas externas: <strong>${(item.externalMs/3600000).toFixed(2)}</strong></li>
            <li>Total: <strong>${(item.totalMs/3600000).toFixed(2)}</strong></li>
            <li>Contratadas/mês: <strong>${item.contractedHoursPerMonth}</strong></li>
          </ul>
          <p>Reveja a alocação da equipe e, se necessário, ajuste o atendimento.</p>
        `
        let delivered = 0
        for (const m of managers) {
          try {
            await sendSmtpMail(smtp, m.email, subject, body)
            delivered += 1
          } catch (error) {
            console.error("Failed to send alert to", m.email, error)
          }
        }
        totalSent += delivered
        await ctx.runMutation(api.alerts.log, {
          tenantId,
          companyId: item.companyId,
          companyName: item.name,
          usagePct,
          threshold: T,
          range: R,
          recipients: managers.map((m) => m.email),
          deliveredCount: delivered,
        })
      }
    }

    return { sent: totalSent }
  },
})

