/**
 * Báo cáo khách hàng (mục Báo cáo → Khách hàng): xếp hạng doanh thu / doanh số / công nợ theo kỳ, số lượng và tỷ lệ khách mở mới
 * trên tổng khách, danh sách khách mở mới — xem được theo toàn công ty, khu vực, phòng ban / team hoặc nhân viên.
 *
 * Phạm vi: khách đi qua `customerScope` (vai trò + bộ chọn thu hẹp + khách còn hoạt động), đơn đi qua `orderScope`. Ngày tạo khách
 * tính theo GIỜ VIỆT NAM (`vnDate`) — cắt theo múi giờ cơ sở dữ liệu làm khách tạo sau 0 giờ sáng bị tính vào hôm trước.
 *
 * Định nghĩa dùng chung: doanh thu = tiền hàng đơn đã duyệt; doanh số = đơn đã lập chưa huỷ; công nợ = doanh thu + phụ thu − đã thu
 * của các đơn trong kỳ. "Tổng khách hàng" = khách còn hoạt động đã tạo tới hết kỳ. Chuỗi từ URL chỉ dùng để chọn trong danh sách cố định.
 */
import "server-only";
import { and, desc, eq, gte, inArray, lte, sql } from "drizzle-orm";
import { db } from "@/db";
import { channels, customerClassifications, customers, orderPayments, orders, territories, users } from "@/db/schema";
import type { SessionUser } from "./auth";
import { customerScope, orderScope, type DateRange } from "./reports";
import { depthAmong, descendantsOf, inTreeOrder, loadDepartmentTree, loadTerritoryTree, visibleTreeNodes, type TreeRow } from "./sales-analysis";
import { monthsInPeriod } from "./period";
import { vnDate } from "./sql-dates";

const num = (value: unknown) => Number(value ?? 0);

export const CUSTOMER_STAGE_FILTERS = ["DEALER", "PROSPECT", "LEAD"] as const;
export type CustomerStageFilter = "" | (typeof CUSTOMER_STAGE_FILTERS)[number];
export const CUSTOMER_RANK_SORTS = ["doanhthu", "doanhso", "no"] as const;
export type CustomerRankSort = (typeof CUSTOMER_RANK_SORTS)[number];

/** Điều kiện khách trong phạm vi + loại khách đang chọn. */
async function customerConditions(session: SessionUser, stage: CustomerStageFilter) {
  return and(eq(customers.tenantId, session.tenantId), await customerScope(session), stage ? eq(customers.stage, stage) : undefined);
}

/* -------------------------------------------------------------------------- */
/* Tổng quan: số lượng và tỷ lệ khách mở mới                                  */
/* -------------------------------------------------------------------------- */

export type CustomerOverview = {
  total: number;
  totalByStage: Record<string, number>;
  newCount: number;
  newByStage: Record<string, number>;
  /** Khách mở mới ÷ tổng khách hàng đến hết kỳ. */
  ratioPct: number | null;
  /** Khách có đơn đã duyệt trong kỳ (mọi khách trong phạm vi) và riêng trong số khách mới. */
  buyers: number;
  newBuyers: number;
};

export async function customerOverview(session: SessionUser, range: DateRange, stage: CustomerStageFilter): Promise<CustomerOverview> {
  const scope = await customerConditions(session, stage);
  const [totalRows, newRows, buyerRows, newBuyerRows] = await Promise.all([
    db
      .select({ stage: customers.stage, n: sql<number>`count(*)::int` })
      .from(customers)
      .where(and(scope, lte(vnDate(customers.createdAt), range.end)))
      .groupBy(customers.stage),
    db
      .select({ stage: customers.stage, n: sql<number>`count(*)::int` })
      .from(customers)
      .where(and(scope, gte(vnDate(customers.createdAt), range.start), lte(vnDate(customers.createdAt), range.end)))
      .groupBy(customers.stage),
    db
      .select({ n: sql<number>`count(distinct ${orders.customerId})::int` })
      .from(orders)
      .innerJoin(customers, eq(orders.customerId, customers.id))
      .where(and(await orderScope(session, "confirmed"), scope, gte(orders.orderDate, range.start), lte(orders.orderDate, range.end))),
    db
      .select({ n: sql<number>`count(distinct ${orders.customerId})::int` })
      .from(orders)
      .innerJoin(customers, eq(orders.customerId, customers.id))
      .where(
        and(
          await orderScope(session, "confirmed"),
          scope,
          gte(orders.orderDate, range.start),
          lte(orders.orderDate, range.end),
          gte(vnDate(customers.createdAt), range.start),
          lte(vnDate(customers.createdAt), range.end),
        ),
      ),
  ]);
  const byStage = (rows: { stage: string; n: number }[]) => Object.fromEntries(rows.map((r) => [r.stage, num(r.n)]));
  const total = totalRows.reduce((sum, r) => sum + num(r.n), 0);
  const newCount = newRows.reduce((sum, r) => sum + num(r.n), 0);
  return {
    total,
    totalByStage: byStage(totalRows),
    newCount,
    newByStage: byStage(newRows),
    ratioPct: total > 0 ? (newCount / total) * 100 : null,
    buyers: num(buyerRows[0]?.n),
    newBuyers: num(newBuyerRows[0]?.n),
  };
}

/* -------------------------------------------------------------------------- */
/* Xếp hạng khách hàng                                                        */
/* -------------------------------------------------------------------------- */

export type CustomerRankRow = {
  rank: number;
  customerId: string;
  code: string;
  orgName: string;
  contactName: string | null;
  phone: string | null;
  stage: string;
  tier: string | null;
  ownerName: string | null;
  territoryName: string | null;
  orderCount: number;
  revenue: number;
  sales: number;
  debt: number;
  lastOrderDate: string | null;
};

/**
 * Xếp hạng khách theo doanh thu / doanh số / công nợ của các đơn trong kỳ, cao → thấp; chỉ khách có giá trị > 0 ở tiêu chí đang xếp.
 * Đồng hạng khi bằng điểm; bằng điểm thì doanh thu cao hơn trước rồi theo tên. Phân trang ở đây vì danh sách khách có thể dài hàng trăm dòng.
 */
export async function customerRanking(
  session: SessionUser,
  range: DateRange,
  period: string,
  stage: CustomerStageFilter,
  sort: CustomerRankSort,
  options: { page?: number; pageSize?: number } = {},
): Promise<{ rows: CustomerRankRow[]; total: number; page: number; totalPages: number; sumRevenue: number; sumSales: number; sumDebt: number }> {
  const scope = await customerConditions(session, stage);
  const [confirmedScope, activeScope] = await Promise.all([orderScope(session, "confirmed"), orderScope(session, "active")]);
  const dateFilter = and(gte(orders.orderDate, range.start), lte(orders.orderDate, range.end));

  const [confirmedRows, paidRows, salesRows] = await Promise.all([
    db
      .select({
        id: orders.customerId,
        revenue: sql<number>`coalesce(sum(${orders.totalAmount}), 0)::bigint`,
        surcharge: sql<number>`coalesce(sum(${orders.surchargeAmount}), 0)::bigint`,
        orderCount: sql<number>`count(*)::int`,
        last: sql<string>`max(${orders.orderDate})::text`,
      })
      .from(orders)
      .innerJoin(customers, eq(orders.customerId, customers.id))
      .where(and(confirmedScope, scope, dateFilter))
      .groupBy(orders.customerId),
    db
      .select({ id: orders.customerId, paid: sql<number>`coalesce(sum(${orderPayments.amount}), 0)::bigint` })
      .from(orderPayments)
      .innerJoin(orders, eq(orderPayments.orderId, orders.id))
      .innerJoin(customers, eq(orders.customerId, customers.id))
      .where(and(confirmedScope, scope, dateFilter))
      .groupBy(orders.customerId),
    db
      .select({ id: orders.customerId, sales: sql<number>`coalesce(sum(${orders.totalAmount}), 0)::bigint` })
      .from(orders)
      .innerJoin(customers, eq(orders.customerId, customers.id))
      .where(and(activeScope, scope, dateFilter))
      .groupBy(orders.customerId),
  ]);
  const paid = new Map(paidRows.map((r) => [r.id, num(r.paid)]));
  const sales = new Map(salesRows.map((r) => [r.id, num(r.sales)]));
  const confirmed = new Map(confirmedRows.map((r) => [r.id, r]));
  const ids = [...new Set([...confirmed.keys(), ...sales.keys()])];

  const metrics = ids.map((id) => {
    const c = confirmed.get(id);
    const revenue = num(c?.revenue);
    return {
      id,
      revenue,
      sales: sales.get(id) ?? 0,
      debt: Math.max(0, revenue + num(c?.surcharge) - (paid.get(id) ?? 0)),
      orderCount: num(c?.orderCount),
      last: c?.last ?? null,
    };
  });
  const key = { doanhthu: "revenue", doanhso: "sales", no: "debt" }[sort] as "revenue" | "sales" | "debt";
  const ranked = metrics.filter((m) => m[key] > 0);
  const info = new Map<string, { orgName: string }>();
  if (ranked.length > 0) {
    const names = await db
      .select({ id: customers.id, orgName: customers.orgName })
      .from(customers)
      .where(and(eq(customers.tenantId, session.tenantId), inArray(customers.id, ranked.map((m) => m.id))));
    for (const n of names) info.set(n.id, n);
  }
  ranked.sort((a, b) => b[key] - a[key] || b.revenue - a.revenue || (info.get(a.id)?.orgName ?? "").localeCompare(info.get(b.id)?.orgName ?? "", "vi"));

  let rank = 0;
  let previous: number | null = null;
  const withRank = ranked.map((m, index) => {
    if (previous === null || m[key] !== previous) rank = index + 1;
    previous = m[key];
    return { ...m, rank };
  });

  const pageSize = options.pageSize ?? 30;
  const totalPages = Math.max(1, Math.ceil(withRank.length / pageSize));
  const page = Math.min(Math.max(1, options.page ?? 1), totalPages);
  const slice = withRank.slice((page - 1) * pageSize, page * pageSize);

  // Thông tin hiển thị chỉ tải cho trang đang xem.
  const details =
    slice.length > 0
      ? await db
          .select({
            id: customers.id,
            code: customers.code,
            orgName: customers.orgName,
            contactName: customers.contactName,
            phone: customers.phone,
            stage: customers.stage,
            ownerName: users.fullName,
            territoryName: territories.name,
          })
          .from(customers)
          .leftJoin(users, eq(customers.ownerId, users.id))
          .leftJoin(territories, eq(customers.territoryId, territories.id))
          .where(and(eq(customers.tenantId, session.tenantId), inArray(customers.id, slice.map((m) => m.id))))
      : [];
  const detailMap = new Map(details.map((d) => [d.id, d]));
  // Hạng đại lý (A–E) theo tháng cuối của kỳ.
  const anchorMonth = monthsInPeriod(period).slice(-1)[0];
  const tiers =
    slice.length > 0
      ? await db
          .select({ id: customerClassifications.customerId, tier: customerClassifications.revenueTier })
          .from(customerClassifications)
          .where(and(eq(customerClassifications.tenantId, session.tenantId), eq(customerClassifications.periodKey, anchorMonth), inArray(customerClassifications.customerId, slice.map((m) => m.id))))
      : [];
  const tierMap = new Map(tiers.map((t) => [t.id, t.tier as string | null]));

  const rows: CustomerRankRow[] = slice.map((m) => {
    const d = detailMap.get(m.id);
    return {
      rank: m.rank,
      customerId: m.id,
      code: d?.code ?? "—",
      orgName: d?.orgName ?? "Không xác định",
      contactName: d?.contactName ?? null,
      phone: d?.phone ?? null,
      stage: d?.stage ?? "DEALER",
      tier: tierMap.get(m.id) ?? null,
      ownerName: d?.ownerName ?? null,
      territoryName: d?.territoryName ?? null,
      orderCount: m.orderCount,
      revenue: m.revenue,
      sales: m.sales,
      debt: m.debt,
      lastOrderDate: m.last,
    };
  });

  return {
    rows,
    total: withRank.length,
    page,
    totalPages,
    sumRevenue: withRank.reduce((sum, m) => sum + m.revenue, 0),
    sumSales: withRank.reduce((sum, m) => sum + m.sales, 0),
    sumDebt: withRank.reduce((sum, m) => sum + m.debt, 0),
  };
}

/* -------------------------------------------------------------------------- */
/* Danh sách khách hàng mới                                                   */
/* -------------------------------------------------------------------------- */

export type NewCustomerRow = {
  id: string;
  code: string;
  orgName: string;
  contactName: string | null;
  phone: string | null;
  stage: string;
  createdDate: string;
  channelName: string | null;
  territoryName: string | null;
  ownerName: string | null;
  orderCount: number;
  revenue: number;
};

export async function newCustomerList(
  session: SessionUser,
  range: DateRange,
  stage: CustomerStageFilter,
  options: { page?: number; pageSize?: number } = {},
): Promise<{ rows: NewCustomerRow[]; total: number; page: number; totalPages: number }> {
  const scope = and(await customerConditions(session, stage), gte(vnDate(customers.createdAt), range.start), lte(vnDate(customers.createdAt), range.end));
  const [countRow] = await db.select({ n: sql<number>`count(*)::int` }).from(customers).where(scope);
  const total = num(countRow?.n);
  const pageSize = options.pageSize ?? 30;
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
  const page = Math.min(Math.max(1, options.page ?? 1), totalPages);

  const rows = await db
    .select({
      id: customers.id,
      code: customers.code,
      orgName: customers.orgName,
      contactName: customers.contactName,
      phone: customers.phone,
      stage: customers.stage,
      createdDate: sql<string>`${vnDate(customers.createdAt)}::text`,
      channelName: channels.name,
      territoryName: territories.name,
      ownerName: users.fullName,
    })
    .from(customers)
    .leftJoin(channels, eq(customers.channelId, channels.id))
    .leftJoin(territories, eq(customers.territoryId, territories.id))
    .leftJoin(users, eq(customers.ownerId, users.id))
    .where(scope)
    .orderBy(desc(customers.createdAt), customers.code)
    .limit(pageSize)
    .offset((page - 1) * pageSize);

  // Đơn của khách mới tính tới hết kỳ đang xem (tổng hợp riêng rồi ghép — không JOIN đơn vào truy vấn khách).
  const stats =
    rows.length > 0
      ? await db
          .select({
            id: orders.customerId,
            revenue: sql<number>`coalesce(sum(${orders.totalAmount}), 0)::bigint`,
            orderCount: sql<number>`count(*)::int`,
          })
          .from(orders)
          .where(and(eq(orders.tenantId, session.tenantId), eq(orders.status, "CONFIRMED"), lte(orders.orderDate, range.end), inArray(orders.customerId, rows.map((r) => r.id))))
          .groupBy(orders.customerId)
      : [];
  const statMap = new Map(stats.map((s) => [s.id, s]));
  return {
    rows: rows.map((r) => ({ ...r, stage: r.stage as string, orderCount: num(statMap.get(r.id)?.orderCount), revenue: num(statMap.get(r.id)?.revenue) })),
    total,
    page,
    totalPages,
  };
}

/* -------------------------------------------------------------------------- */
/* Khách mở mới theo khu vực / phòng ban / nhân viên                          */
/* -------------------------------------------------------------------------- */

export type NewByGroup = { newCount: number; total: number; ratioPct: number | null; buyers: number };

/**
 * Số khách mở mới và tỷ lệ trên tổng khách, chia theo khu vực (miền → tỉnh), phòng ban (theo phòng của NVKD phụ trách) hoặc từng nhân viên.
 * Khách chưa có người phụ trách / khu vực gộp vào dòng "Chưa …" để cộng cột luôn bằng tổng.
 */
export async function newCustomersByGroup(
  session: SessionUser,
  range: DateRange,
  stage: CustomerStageFilter,
  dimension: "khu-vuc" | "phong-ban" | "nhan-vien",
): Promise<TreeRow<NewByGroup>[]> {
  const scope = await customerConditions(session, stage);
  const people = await db
    .select({
      id: customers.id,
      territoryId: customers.territoryId,
      ownerId: customers.ownerId,
      departmentId: users.departmentId,
      ownerName: users.fullName,
      isNew: sql<boolean>`(${vnDate(customers.createdAt)} >= ${range.start}::date)`,
    })
    .from(customers)
    .leftJoin(users, eq(customers.ownerId, users.id))
    .where(and(scope, lte(vnDate(customers.createdAt), range.end)));

  // Khách có đơn đã duyệt trong kỳ.
  const buyerIds = new Set(
    (
      await db
        .selectDistinct({ id: orders.customerId })
        .from(orders)
        .where(and(await orderScope(session, "confirmed"), gte(orders.orderDate, range.start), lte(orders.orderDate, range.end)))
    ).map((r) => r.id),
  );

  const keyOf = (c: (typeof people)[number]) => (dimension === "khu-vuc" ? c.territoryId : dimension === "phong-ban" ? c.departmentId : c.ownerId) ?? "";
  const own = new Map<string, { newCount: number; total: number; buyers: number }>();
  for (const c of people) {
    const bucket = own.get(keyOf(c)) ?? { newCount: 0, total: 0, buyers: 0 };
    bucket.total++;
    if (c.isNew) bucket.newCount++;
    if (buyerIds.has(c.id)) bucket.buyers++;
    own.set(keyOf(c), bucket);
  }
  const row = (id: string, name: string, depth: number, sum: { newCount: number; total: number; buyers: number }): TreeRow<NewByGroup> => ({
    id,
    name,
    depth,
    ...sum,
    ratioPct: sum.total > 0 ? (sum.newCount / sum.total) * 100 : null,
  });

  if (dimension === "nhan-vien") {
    const names = new Map(people.map((c) => [c.ownerId ?? "", c.ownerName ?? "Chưa giao NVKD"]));
    return [...own.entries()]
      .map(([id, sum]) => row(id || "none", names.get(id) ?? "Chưa giao NVKD", 0, sum))
      .sort((a, b) => b.newCount - a.newCount || b.total - a.total || a.name.localeCompare(b.name, "vi"));
  }

  const nodes = dimension === "khu-vuc" ? await loadTerritoryTree(session.tenantId) : await loadDepartmentTree(session.tenantId);
  const descendants = descendantsOf(nodes);
  const shown = await visibleTreeNodes(session, dimension, nodes);
  const rows: TreeRow<NewByGroup>[] = [];
  for (const { node } of inTreeOrder(nodes)) {
    if (shown && !shown.has(node.id)) continue;
    const ids = [...(descendants.get(node.id) ?? [node.id])];
    const sum = ids.reduce((acc, id) => ({ newCount: acc.newCount + (own.get(id)?.newCount ?? 0), total: acc.total + (own.get(id)?.total ?? 0), buyers: acc.buyers + (own.get(id)?.buyers ?? 0) }), { newCount: 0, total: 0, buyers: 0 });
    if (sum.total <= 0) continue;
    rows.push(row(node.id, node.name, depthAmong(nodes, node.id, shown), sum));
  }
  const loose = own.get("");
  if (loose) rows.push(row("none", dimension === "khu-vuc" ? "Chưa phân khu vực" : "Chưa giao NVKD / phòng ban", 0, loose));
  return rows;
}
