/**
 * Tự động phân hạng đại lý (yêu cầu mục 2).
 *
 * Ba cách phân hạng chạy song song, không thay thế nhau:
 *   1. Theo doanh thu bình quân tháng:  A ≥50tr · B ≥20tr · C ≥10tr · D ≥5tr · E <5tr
 *   2. Theo số SKUs đang ứng dụng:      A >30 · B >20 · C >10 · D >5 · E ≤5
 *   3. Theo tần suất: số đơn tái hàng trong tháng, số tháng có lấy hàng trong quý/năm
 *
 * Kết quả ghi vào bảng `customer_classifications` theo từng kỳ tháng, nên hạng
 * của các tháng trước không bị viết đè — xem được đại lý lên/xuống hạng.
 *
 * Ngưỡng để trong hằng số ngay đây để khách đổi chính sách là sửa một chỗ.
 */
import { and, eq, gte, lte, sql } from "drizzle-orm";
import { db } from "@/db";
import { customers, orders, orderLines, customerClassifications } from "@/db/schema";
import { dayInTz, monthKey, periodRange, lastMonths, shiftMonth } from "./period";

/** Ngưỡng doanh thu bình quân tháng, VNĐ — xét từ cao xuống thấp. */
export const REVENUE_TIERS = [
  { tier: "A" as const, min: 50_000_000, label: "A · từ 50tr" },
  { tier: "B" as const, min: 20_000_000, label: "B · từ 20tr" },
  { tier: "C" as const, min: 10_000_000, label: "C · từ 10tr" },
  { tier: "D" as const, min: 5_000_000, label: "D · từ 5tr" },
  { tier: "E" as const, min: 0, label: "E · dưới 5tr" },
];

/** Ngưỡng số SKUs đang ứng dụng — xét từ cao xuống thấp. */
export const SKU_TIERS = [
  { tier: "A" as const, min: 31, label: "A · trên 30 SP" },
  { tier: "B" as const, min: 21, label: "B · trên 20 SP" },
  { tier: "C" as const, min: 11, label: "C · trên 10 SP" },
  { tier: "D" as const, min: 6, label: "D · trên 5 SP" },
  { tier: "E" as const, min: 0, label: "E · từ 5 SP trở xuống" },
];

/**
 * Số tháng gần nhất dùng để tính doanh thu bình quân.
 *
 * Khách hàng nói rõ trong phiếu yêu cầu tùy biến: "Doanh số trung bình tháng
 * **trong 1 năm**". Cửa sổ 12 tháng làm hạng đại lý ổn định hơn — một quý tốt
 * bất thường không đủ để nhảy hạng, đúng tinh thần dùng hạng làm cơ sở chính
 * sách chiết khấu.
 */
export const AVERAGE_WINDOW_MONTHS = 12;

/**
 * Số tháng dùng để đếm SKUs "đang ứng dụng".
 *
 * Ngắn hơn cửa sổ doanh thu: yêu cầu hỏi đại lý *đang* dùng bao nhiêu loại sản
 * phẩm, không phải đã từng dùng trong cả năm. Lấy 12 tháng ở đây sẽ đếm cả những
 * sản phẩm họ đã bỏ từ lâu.
 */
export const SKU_WINDOW_MONTHS = 3;

export type Tier = "A" | "B" | "C" | "D" | "E";

export function revenueTier(avgMonthlyRevenue: number): Tier {
  return REVENUE_TIERS.find((t) => avgMonthlyRevenue >= t.min)!.tier;
}

export function skuTier(skuCount: number): Tier {
  return SKU_TIERS.find((t) => skuCount >= t.min)!.tier;
}

export type ClassificationRow = {
  customerId: string;
  avgMonthlyRevenue: number;
  revenueTier: Tier;
  skuCount: number;
  skuTier: Tier;
  reorderCount: number;
  monthsPurchasedInQuarter: number;
  monthsPurchasedInYear: number;
  monthsInactive: number;
};

/**
 * Tính lại phân hạng cho toàn bộ đại lý của một tenant trong kỳ tháng đã cho,
 * rồi ghi đè bản ghi của đúng kỳ đó.
 *
 * Chạy trong job hằng đêm (`src/jobs/nightly.ts`) và có thể gọi tay từ màn hình
 * cài đặt khi cần tính lại sau khi sửa dữ liệu lịch sử.
 */
export async function recomputeClassifications(
  tenantId: string,
  periodKey: string,
): Promise<ClassificationRow[]> {
  const windowMonths = lastMonths(periodKey, AVERAGE_WINDOW_MONTHS);
  const windowStart = periodRange(windowMonths[0]).start;
  const skuWindowStart = periodRange(lastMonths(periodKey, SKU_WINDOW_MONTHS)[0]).start;
  const { start: monthStart, end: monthEnd } = periodRange(periodKey);
  const year = periodKey.slice(0, 4);
  const quarterMonths = quarterMonthsOf(periodKey);

  const dealers = await db
    .select({ id: customers.id, dealerSince: customers.dealerSince, createdAt: customers.createdAt })
    .from(customers)
    .where(and(eq(customers.tenantId, tenantId), eq(customers.stage, "DEALER")));

  if (dealers.length === 0) return [];

  /* Doanh thu theo từng khách hàng, từng tháng, trong cả năm — một truy vấn duy
     nhất rồi tổng hợp trong bộ nhớ, tránh N+1 khi số đại lý lớn. */
  const monthly = await db
    .select({
      customerId: orders.customerId,
      month: sql<string>`to_char(${orders.orderDate}, 'YYYY-MM')`.as("month"),
      revenue: sql<number>`coalesce(sum(${orders.totalAmount}), 0)::bigint`.as("revenue"),
      orderCount: sql<number>`count(*)::int`.as("order_count"),
    })
    .from(orders)
    .where(
      and(
        eq(orders.tenantId, tenantId),
        eq(orders.status, "CONFIRMED"),
        // Lấy đủ cửa sổ 12 tháng, không cắt theo đầu năm dương lịch — nếu cắt
        // thì mỗi tháng 1 hằng năm mọi đại lý đều tụt hạng vì thiếu dữ liệu.
        gte(orders.orderDate, windowStart),
        lte(orders.orderDate, monthEnd),
      ),
    )
    .groupBy(orders.customerId, sql`to_char(${orders.orderDate}, 'YYYY-MM')`);

  /* Số SKUs khác nhau mỗi khách đã lấy trong cửa sổ tính bình quân. */
  const skus = await db
    .select({
      customerId: orders.customerId,
      skuCount: sql<number>`count(distinct ${orderLines.productId})::int`.as("sku_count"),
    })
    .from(orderLines)
    .innerJoin(orders, eq(orderLines.orderId, orders.id))
    .where(
      and(
        eq(orders.tenantId, tenantId),
        eq(orders.status, "CONFIRMED"),
        gte(orders.orderDate, skuWindowStart),
        lte(orders.orderDate, monthEnd),
        sql`${orderLines.qtySold} > 0`,
      ),
    )
    .groupBy(orders.customerId);

  const skuByCustomer = new Map(skus.map((r) => [r.customerId, Number(r.skuCount)]));

  const byCustomer = new Map<string, Map<string, { revenue: number; orderCount: number }>>();
  for (const row of monthly) {
    const map = byCustomer.get(row.customerId) ?? new Map();
    map.set(row.month, { revenue: Number(row.revenue), orderCount: Number(row.orderCount) });
    byCustomer.set(row.customerId, map);
  }

  const results: ClassificationRow[] = [];

  for (const dealer of dealers) {
    const months = byCustomer.get(dealer.id) ?? new Map();

    const windowRevenue = windowMonths.reduce((sum, m) => sum + (months.get(m)?.revenue ?? 0), 0);
    const avgMonthlyRevenue = Math.round(windowRevenue / AVERAGE_WINDOW_MONTHS);

    const skuCount = skuByCustomer.get(dealer.id) ?? 0;

    // "Tái hàng" = các đơn từ đơn thứ hai trở đi trong tháng.
    const ordersThisMonth = months.get(periodKey)?.orderCount ?? 0;
    const reorderCount = Math.max(0, ordersThisMonth - 1);

    const monthsPurchasedInQuarter = quarterMonths.filter(
      (m) => (months.get(m)?.revenue ?? 0) > 0,
    ).length;

    const monthsPurchasedInYear = [...months.entries()].filter(
      ([m, v]) => m.startsWith(year) && v.revenue > 0,
    ).length;

    results.push({
      customerId: dealer.id,
      avgMonthlyRevenue,
      revenueTier: revenueTier(avgMonthlyRevenue),
      skuCount,
      skuTier: skuTier(skuCount),
      reorderCount,
      monthsPurchasedInQuarter,
      monthsPurchasedInYear,
      monthsInactive: countMonthsInactive(months, periodKey, dealer.dealerSince ? monthKey(dealer.dealerSince) : monthKey(dayInTz(dealer.createdAt))),
    });
  }

  // Ghi đè bản ghi của đúng kỳ này, giữ nguyên các kỳ trước.
  await db
    .delete(customerClassifications)
    .where(
      and(
        eq(customerClassifications.tenantId, tenantId),
        eq(customerClassifications.periodKey, periodKey),
      ),
    );

  const CHUNK = 500;
  for (let i = 0; i < results.length; i += CHUNK) {
    await db.insert(customerClassifications).values(
      results.slice(i, i + CHUNK).map((r) => ({
        tenantId,
        customerId: r.customerId,
        periodKey,
        avgMonthlyRevenue: r.avgMonthlyRevenue,
        revenueTier: r.revenueTier,
        skuCount: r.skuCount,
        skuTier: r.skuTier,
        reorderCount: r.reorderCount,
        monthsPurchasedInQuarter: r.monthsPurchasedInQuarter,
        monthsPurchasedInYear: r.monthsPurchasedInYear,
        monthsInactive: r.monthsInactive,
      })),
    );
  }

  void monthStart; // giữ lại để dễ mở rộng bộ lọc theo ngày trong tháng
  return results;
}

/**
 * Số tháng liên tiếp gần nhất không phát sinh doanh thu, tính tới hết `periodKey`.
 *
 * Không đếm lùi quá tháng đại lý bắt đầu (`sinceMonth` = tháng mở code / tạo khách): đại lý mới mở chưa có đơn đầu tiên
 * không phải "đã 24 tháng không lấy hàng" — trước đây thiếu giới hạn này nên đại lý vừa chuyển từ khách tiềm năng sang bị
 * cảnh báo NGHIÊM TRỌNG ngay đêm đầu tiên.
 */
export function countMonthsInactive(
  months: Map<string, { revenue: number }>,
  periodKey: string,
  sinceMonth: string,
): number {
  let count = 0;
  let cursor = periodKey;
  // Dừng ở 24 tháng: quá mốc này thì con số chính xác không còn ý nghĩa điều hành.
  while (count < 24 && cursor >= sinceMonth) {
    if ((months.get(cursor)?.revenue ?? 0) > 0) break;
    count += 1;
    cursor = shiftMonth(cursor, 1);
  }
  return count;
}

/** Ba tháng của quý chứa kỳ tháng đã cho. */
export function quarterMonthsOf(periodKey: string): string[] {
  const year = Number(periodKey.slice(0, 4));
  const month = Number(periodKey.slice(5, 7));
  const firstMonth = Math.floor((month - 1) / 3) * 3 + 1;
  return [0, 1, 2].map((i) => `${year}-${String(firstMonth + i).padStart(2, "0")}`);
}
