/**
 * Gửi cảnh báo ra ngoài hệ thống.
 *
 * Thiết kế theo kiểu nhiều kênh gửi (channel), mỗi kênh là một hàm nhận danh sách
 * cảnh báo và tự lo việc gửi. Kênh nào thiếu cấu hình thì tự bỏ qua, không làm
 * hỏng job hằng đêm — mất một thông báo là phiền, nhưng để job dừng giữa chừng
 * khiến phân hạng và KPI không được tính mới là hỏng việc thật.
 *
 * Hiện có:
 *   console — luôn bật, ghi ra log để đối chiếu khi vận hành
 *   email   — gửi qua SMTP, bật khi khai báo SMTP_URL và ALERT_EMAIL_TO
 *   zalo    — gửi qua Zalo OA, bật khi khai báo ZALO_OA_TOKEN và ZALO_ALERT_USER_IDS
 *
 * Email và Zalo cần tài khoản của khách hàng nên chỉ được kích hoạt sau khi khách
 * cung cấp thông tin; trước đó hệ thống vẫn chạy đủ, chỉ là cảnh báo nằm trong app.
 */
import { ALERT_TYPE_LABEL } from "./format";

export type NotifiableAlert = {
  type: string;
  severity: "INFO" | "WARNING" | "CRITICAL";
  title: string;
  message: string;
};

export type NotifyResult = { channel: string; sent: number; skipped?: string; error?: string };

type Channel = {
  name: string;
  isConfigured: () => boolean;
  send: (alerts: NotifiableAlert[], context: { tenantName: string }) => Promise<number>;
};

const SEVERITY_LABEL: Record<NotifiableAlert["severity"], string> = {
  CRITICAL: "NGHIÊM TRỌNG",
  WARNING: "CẢNH BÁO",
  INFO: "LƯU Ý",
};

/** Soạn nội dung dạng văn bản thuần, dùng chung cho email và Zalo. */
export function formatAlertDigest(alerts: NotifiableAlert[], tenantName: string): string {
  const bySeverity = (level: NotifiableAlert["severity"]) =>
    alerts.filter((alert) => alert.severity === level);

  const lines: string[] = [
    `${tenantName} — cảnh báo kinh doanh`,
    `Tổng cộng ${alerts.length} cảnh báo cần xử lý.`,
    "",
  ];

  for (const level of ["CRITICAL", "WARNING", "INFO"] as const) {
    const group = bySeverity(level);
    if (group.length === 0) continue;

    lines.push(`${SEVERITY_LABEL[level]} (${group.length})`);
    for (const alert of group) {
      lines.push(`  • [${ALERT_TYPE_LABEL[alert.type] ?? alert.type}] ${alert.title}`);
      lines.push(`    ${alert.message}`);
    }
    lines.push("");
  }

  return lines.join("\n");
}

/* -------------------------------------------------------------------------- */

const consoleChannel: Channel = {
  name: "console",
  isConfigured: () => true,
  send: async (alerts, { tenantName }) => {
    console.log(formatAlertDigest(alerts, tenantName));
    return alerts.length;
  },
};

const emailChannel: Channel = {
  name: "email",
  isConfigured: () => Boolean(process.env.SMTP_URL && process.env.ALERT_EMAIL_TO),
  send: async (alerts, { tenantName }) => {
    // nodemailer chỉ được nạp khi kênh này thực sự bật, để dự án không phải cài
    // thêm thư viện khi khách chưa dùng email.
    const { createTransport } = await import("nodemailer");
    const transport = createTransport(process.env.SMTP_URL!);

    await transport.sendMail({
      from: process.env.ALERT_EMAIL_FROM ?? process.env.ALERT_EMAIL_TO!,
      to: process.env.ALERT_EMAIL_TO!,
      subject: `[${tenantName}] ${alerts.length} cảnh báo kinh doanh`,
      text: formatAlertDigest(alerts, tenantName),
    });

    return alerts.length;
  },
};

const zaloChannel: Channel = {
  name: "zalo",
  isConfigured: () => Boolean(process.env.ZALO_OA_TOKEN && process.env.ZALO_ALERT_USER_IDS),
  send: async (alerts, { tenantName }) => {
    const userIds = process.env.ZALO_ALERT_USER_IDS!.split(",")
      .map((id) => id.trim())
      .filter(Boolean);

    const text = formatAlertDigest(alerts, tenantName);
    let sent = 0;

    for (const userId of userIds) {
      const response = await fetch("https://openapi.zalo.me/v3.0/oa/message/cs", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          access_token: process.env.ZALO_OA_TOKEN!,
        },
        body: JSON.stringify({
          recipient: { user_id: userId },
          // Zalo giới hạn độ dài tin nhắn; phần vượt quá xem trong app.
          message: { text: text.slice(0, 2000) },
        }),
      });

      if (response.ok) sent += 1;
      else console.error(`Zalo trả lỗi cho ${userId}: ${response.status}`);
    }

    return sent;
  },
};

const CHANNELS: Channel[] = [consoleChannel, emailChannel, zaloChannel];

/**
 * Gửi cảnh báo qua mọi kênh đang được cấu hình.
 *
 * Không bao giờ ném lỗi ra ngoài: kết quả từng kênh trả về trong mảng để job
 * hằng đêm ghi log và chạy tiếp.
 */
export async function notifyAlerts(
  alerts: NotifiableAlert[],
  context: { tenantName: string },
): Promise<NotifyResult[]> {
  if (alerts.length === 0) return [];

  const results: NotifyResult[] = [];

  for (const channel of CHANNELS) {
    if (!channel.isConfigured()) {
      results.push({ channel: channel.name, sent: 0, skipped: "chưa cấu hình" });
      continue;
    }

    try {
      const sent = await channel.send(alerts, context);
      results.push({ channel: channel.name, sent });
    } catch (error) {
      results.push({
        channel: channel.name,
        sent: 0,
        error: error instanceof Error ? error.message : String(error),
      });
    }
  }

  return results;
}
