import nodemailer from "nodemailer";
import type { EmailAlertLog } from "../src/types.ts";
import { Store } from "./store.ts";
export function escapeHtml(value: string): string {
  return value.replace(
    /[&<>"']/g,
    (c) =>
      ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[
        c
      ]!,
  );
}
export type SendMail = (log: EmailAlertLog) => Promise<void>;
export class Mailer {
  busy = false;
  configured: boolean;
  send: SendMail;
  constructor(
    public store: Store,
    send?: SendMail,
  ) {
    this.configured =
      !!send ||
      !!(
        process.env.SMTP_HOST &&
        process.env.SMTP_USER &&
        process.env.SMTP_PASS
      );
    if (this.configured)
      store.db
        .prepare(
          "UPDATE notifications SET status='pending',next_attempt=0 WHERE status='disabled'",
        )
        .run();
    this.send =
      send ||
      (async (log) => {
        const port = Number(process.env.SMTP_PORT || 587);
        const transport = nodemailer.createTransport({
          host: process.env.SMTP_HOST,
          port,
          secure: port === 465,
          requireTLS: port !== 465,
          auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
          connectionTimeout: 10000,
          greetingTimeout: 10000,
          socketTimeout: 15000,
        });
        const subject = `[Alfatih Client Centre] ${log.errorType}: ${log.siteName}`;
        const message = await transport.sendMail({
          from: process.env.SMTP_FROM || process.env.SMTP_USER,
          to: log.recipient,
          subject,
          text: `${log.siteName}\n${log.siteUrl}\n${log.errorType}\n${log.errorMessage}\nMasa: ${new Date().toLocaleString("ms-MY", { timeZone: "Asia/Kuala_Lumpur" })}`,
          html: `<h2>Alfatih Client Centre</h2><p><strong>${escapeHtml(log.siteName)}</strong></p><p>${escapeHtml(log.errorType)}</p><p style="white-space:pre-line">${escapeHtml(log.errorMessage)}</p>${log.siteUrl ? `<p><a href="${escapeHtml(log.siteUrl)}">Buka website</a></p>` : ""}<p>Penerima: ${escapeHtml(log.recipient)}</p>`,
        });
        if (!message.accepted?.length)
          throw new Error("SMTP tidak menerima penerima.");
      });
  }
  async flush(onlyId?: string) {
    if (this.busy) return;
    this.busy = true;
    try {
      const rows = onlyId
        ? this.store.db
            .prepare(
              "SELECT * FROM notifications WHERE id=? AND status='pending' AND next_attempt<=?",
            )
            .all(onlyId, Date.now())
        : this.store.db
            .prepare(
              "SELECT * FROM notifications WHERE status='pending' AND next_attempt<=? ORDER BY rowid LIMIT 10",
            )
            .all(Date.now());
      for (const row of rows) {
        const data = JSON.parse(row.data as string) as EmailAlertLog;
        const attempts = Number(row.attempts) + 1;
        if (!this.configured) {
          this.store.db
            .prepare("UPDATE notifications SET status='disabled' WHERE id=?")
            .run(data.id);
          continue;
        }
        try {
          await this.send(data);
          data.sentAt = new Date().toISOString();
          delete data.deliveryError;
          this.store.db
            .prepare(
              "UPDATE notifications SET status='sent',attempts=?,data=? WHERE id=?",
            )
            .run(attempts, JSON.stringify(data), data.id);
        } catch (e) {
          data.deliveryError =
            "Penghantaran SMTP gagal. Semak konfigurasi server.";
          this.store.db
            .prepare(
              "UPDATE notifications SET status=?,attempts=?,next_attempt=?,data=? WHERE id=?",
            )
            .run(
              attempts >= 3 ? "failed" : "pending",
              attempts,
              Date.now() + attempts * 60000,
              JSON.stringify(data),
              data.id,
            );
          console.error(
            "SMTP delivery failed:",
            (e as { code?: string }).code || "delivery_error",
          );
        }
      }
    } finally {
      this.busy = false;
    }
  }
}
