/**
 * Job hằng đêm — chạy lúc 1h sáng qua cron.
 *
 *   0 1 * * *  cd /duong/dan/crm-saas && npm run jobs:nightly >> logs/nightly.log 2>&1
 *
 * Thứ tự các bước có ràng buộc: phân hạng phải xong trước khi quét cảnh báo,
 * vì cảnh báo "ĐL ngừng lấy hàng" đọc `monthsInactive` do bước phân hạng tính ra.
 */
import "../db/load-env";
import { and, desc, eq } from "drizzle-orm";
import { db } from "../db";
import { tenants, alerts } from "../db/schema";
import { notifyAlerts } from "../lib/notify";
import { recomputeClassifications } from "../lib/classification";
import { recomputeKpi } from "../lib/kpi";
import { runAlertScan } from "../lib/alerts";
import { FORECAST_HORIZON_MONTHS, recomputeForecast } from "../lib/forecast";
import { today, monthKey, shiftMonth, lastCompletedMonth } from "../lib/period";

async function main() {
  const startedAt = Date.now();
  const currentMonth = monthKey(today());
  const forecastMonths = Array.from({ length: FORECAST_HORIZON_MONTHS }, (_, i) => shiftMonth(currentMonth, -(i + 1)));
  // Cảnh báo so kỳ này với kỳ trước, nên chỉ quét trên tháng đã đóng sổ.
  const scanMonth = lastCompletedMonth();

  const activeTenants = await db
    .select({ id: tenants.id, name: tenants.name })
    .from(tenants)
    .where(eq(tenants.isActive, true));

  console.log(`[${new Date().toISOString()}] Bắt đầu job hằng đêm — kỳ ${currentMonth}`);

  for (const tenant of activeTenants) {
    try {
      const classified = await recomputeClassifications(tenant.id, currentMonth);
      // Tính lại cả tháng trước: đơn hàng nhập bù sau ngày chốt sổ vẫn được phản ánh.
      await recomputeClassifications(tenant.id, shiftMonth(currentMonth, 1));

      const kpiCount = await recomputeKpi(tenant.id, currentMonth);
      await recomputeKpi(tenant.id, shiftMonth(currentMonth, 1));
      // Cảnh báo "không đạt chỉ tiêu 2 tháng liên tiếp" cần KPI của cả hai tháng đã đóng sổ gần nhất — tính luôn tháng thứ hai để chạy được ngay đêm đầu tiên.
      await recomputeKpi(tenant.id, shiftMonth(currentMonth, 2));

      const alertCount = await runAlertScan(tenant.id, scanMonth);
      const forecastRows = [];
      for (const target of forecastMonths) forecastRows.push(...(await recomputeForecast(tenant.id, target, scanMonth)));

      console.log(
        `  ✓ ${tenant.name}: ${classified.length} đại lý phân hạng · ${kpiCount} KPI · ` +
          `${alertCount} cảnh báo (kỳ ${scanMonth}) · ${forecastRows.length} dòng dự báo cho ${forecastMonths.join(", ")}`,
      );

      // Gửi bản tổng hợp cảnh báo chưa xử lý cho quản lý.
      const openAlerts = await db
        .select({
          type: alerts.type,
          severity: alerts.severity,
          title: alerts.title,
          message: alerts.message,
        })
        .from(alerts)
        .where(and(eq(alerts.tenantId, tenant.id), eq(alerts.status, "OPEN")))
        .orderBy(desc(alerts.severity))
        .limit(50);

      const delivery = await notifyAlerts(openAlerts, { tenantName: tenant.name });
      for (const result of delivery) {
        if (result.error) console.error(`    ! kênh ${result.channel}: ${result.error}`);
        else if (result.skipped) console.log(`    – kênh ${result.channel}: ${result.skipped}`);
        else console.log(`    → kênh ${result.channel}: đã gửi ${result.sent}`);
      }
    } catch (error) {
      // Một tenant lỗi không được làm dừng các tenant còn lại.
      console.error(`  ✗ ${tenant.name}:`, error);
    }
  }

  console.log(`Hoàn tất sau ${((Date.now() - startedAt) / 1000).toFixed(1)}s\n`);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
