/**
 * Báo cáo chuyển giao (mục Báo cáo → Chuyển giao): số lớp theo LOẠI LỚP theo ngày / tuần / tháng / quý / năm, xem được theo toàn công ty,
 * khu vực, phòng ban / team hoặc từng nhân viên; kèm lượt mời, lượt tham dự, kết quả sau lớp và khách hoàn thành khóa chuyển giao.
 *
 * Mọi truy vấn lớp đi qua `eventScope` (vai trò + bộ chọn thu hẹp): quản lý chỉ thấy lớp do nhân viên trong nhánh mình phụ trách.
 * Loại lớp là chuỗi tự do (`events.kind`) nên báo cáo gom theo đúng chuỗi có trong dữ liệu — loại lạ vẫn hiện.
 */
import "server-only";
import { and, count, desc, eq, gte, lte, sql } from "drizzle-orm";
import { db } from "@/db";
import { customerCourseCompletions, customers, events, territories, trainingCourses, users } from "@/db/schema";
import type { SessionUser } from "./auth";
import { customerScope, type DateRange } from "./reports";
import { eventScope } from "./events-scope";
import { orderKinds } from "./event-kinds";
import { depthAmong, descendantsOf, inTreeOrder, loadDepartmentTree, loadTerritoryTree, visibleTreeNodes, type TreeRow } from "./sales-analysis";
import { weekStart } from "./period";

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

export type TransferAgg = { classes: number; invited: number; attended: number; orders: number; revenue: number; ratePct: number | null };
const emptyAgg = () => ({ classes: 0, invited: 0, attended: 0, orders: 0, revenue: 0 });
const withRate = (a: { classes: number; invited: number; attended: number; orders: number; revenue: number }): TransferAgg => ({ ...a, ratePct: a.invited > 0 ? (a.attended / a.invited) * 100 : null });

function inRange(range: DateRange) {
  return and(gte(events.eventDate, range.start), lte(events.eventDate, range.end));
}

/* -------------------------------------------------------------------------- */
/* Tổng quan và theo loại lớp                                                 */
/* -------------------------------------------------------------------------- */

export type TransferOverview = {
  totals: TransferAgg & { owners: number };
  byKind: (TransferAgg & { kind: string; sharePct: number })[];
};

export async function transferOverview(session: SessionUser, range: DateRange): Promise<TransferOverview> {
  const scope = await eventScope(session);
  const rows = await db
    .select({
      kind: events.kind,
      classes: sql<number>`count(*)::int`,
      invited: sql<number>`coalesce(sum(${events.invitedCount}), 0)::int`,
      attended: sql<number>`coalesce(sum(${events.attendedCount}), 0)::int`,
      orders: sql<number>`coalesce(sum(${events.resultOrderCount}), 0)::int`,
      revenue: sql<number>`coalesce(sum(${events.resultRevenue}), 0)::bigint`,
    })
    .from(events)
    .where(and(scope, inRange(range)))
    .groupBy(events.kind);
  const [ownerRow] = await db
    .select({ n: sql<number>`count(distinct ${events.ownerId})::int` })
    .from(events)
    .where(and(scope, inRange(range)));

  const total = rows.reduce((acc, r) => ({ classes: acc.classes + num(r.classes), invited: acc.invited + num(r.invited), attended: acc.attended + num(r.attended), orders: acc.orders + num(r.orders), revenue: acc.revenue + num(r.revenue) }), emptyAgg());
  const order = orderKinds(rows.map((r) => r.kind));
  const byKind = order.map((kind) => {
    const r = rows.find((x) => x.kind === kind)!;
    const agg = { classes: num(r.classes), invited: num(r.invited), attended: num(r.attended), orders: num(r.orders), revenue: num(r.revenue) };
    return { kind, ...withRate(agg), sharePct: total.classes > 0 ? (agg.classes / total.classes) * 100 : 0 };
  });
  return { totals: { ...withRate(total), owners: num(ownerRow?.n) }, byKind };
}

/* -------------------------------------------------------------------------- */
/* Xu hướng theo thời gian, chia theo loại lớp                                */
/* -------------------------------------------------------------------------- */

export type TrendGranularity = "ngay" | "tuan" | "thang" | "quy" | "nam";
export type TransferBucket = { key: string; label: string; start: string; end: string; total: number; attended: number; byKind: Record<string, number> };

const addDays = (iso: string, days: number) => new Date(Date.parse(`${iso}T00:00:00Z`) + days * 86_400_000).toISOString().slice(0, 10);
const dm = (iso: string) => `${iso.slice(8, 10)}/${iso.slice(5, 7)}`;
const WEEKDAY = ["CN", "T2", "T3", "T4", "T5", "T6", "T7"];

/** Các cột thời gian của biểu đồ: ngày → 7 ngày tới ngày đang xem; tuần → 7 ngày; tháng → các tuần; quý và năm → các tháng. */
export function buildBuckets(range: DateRange, granularity: TrendGranularity): { key: string; label: string; start: string; end: string }[] {
  if (granularity === "ngay" || granularity === "tuan") {
    const first = granularity === "ngay" ? addDays(range.end, -6) : range.start;
    return Array.from({ length: 7 }, (_, i) => {
      const day = addDays(first, i);
      return { key: day, label: `${WEEKDAY[new Date(`${day}T00:00:00Z`).getUTCDay()]} ${dm(day)}`, start: day, end: day };
    });
  }
  if (granularity === "thang") {
    const out: { key: string; label: string; start: string; end: string }[] = [];
    let cursor = range.start;
    while (cursor <= range.end) {
      const end = [addDays(weekStart(cursor), 6), range.end].sort()[0];
      out.push({ key: cursor, label: cursor === end ? dm(cursor) : `${cursor.slice(8, 10)}–${end.slice(8, 10)}/${end.slice(5, 7)}`, start: cursor, end });
      cursor = addDays(end, 1);
    }
    return out;
  }
  // Quý / năm: theo tháng.
  const out: { key: string; label: string; start: string; end: string }[] = [];
  let year = Number(range.start.slice(0, 4));
  let month = Number(range.start.slice(5, 7));
  const lastKey = range.end.slice(0, 7);
  for (let guard = 0; guard < 12; guard++) {
    const key = `${year}-${String(month).padStart(2, "0")}`;
    if (key > lastKey) break;
    const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
    out.push({ key, label: `T${month}`, start: `${key}-01`, end: `${key}-${String(lastDay).padStart(2, "0")}` });
    month++;
    if (month > 12) {
      month = 1;
      year++;
    }
  }
  return out;
}

export async function transferTrend(session: SessionUser, range: DateRange, granularity: TrendGranularity): Promise<{ buckets: TransferBucket[]; kinds: string[] }> {
  const buckets = buildBuckets(range, granularity);
  const span = { start: buckets[0]?.start ?? range.start, end: buckets[buckets.length - 1]?.end ?? range.end };
  const scope = await eventScope(session);
  const rows = await db
    .select({
      day: sql<string>`${events.eventDate}::text`,
      kind: events.kind,
      classes: sql<number>`count(*)::int`,
      attended: sql<number>`coalesce(sum(${events.attendedCount}), 0)::int`,
    })
    .from(events)
    .where(and(scope, inRange(span)))
    .groupBy(events.eventDate, events.kind);

  const kinds = orderKinds(rows.map((r) => r.kind));
  const result: TransferBucket[] = buckets.map((b) => ({ ...b, total: 0, attended: 0, byKind: {} }));
  for (const r of rows) {
    const bucket = result.find((b) => r.day >= b.start && r.day <= b.end);
    if (!bucket) continue;
    bucket.byKind[r.kind] = (bucket.byKind[r.kind] ?? 0) + num(r.classes);
    bucket.total += num(r.classes);
    bucket.attended += num(r.attended);
  }
  return { buckets: result, kinds };
}

/* -------------------------------------------------------------------------- */
/* Theo khu vực / phòng ban / nhân viên                                       */
/* -------------------------------------------------------------------------- */

export async function transferByGroup(session: SessionUser, range: DateRange, dimension: "khu-vuc" | "phong-ban" | "nhan-vien"): Promise<TreeRow<TransferAgg>[]> {
  const scope = await eventScope(session);
  const list = await db
    .select({
      territoryId: events.territoryId,
      ownerId: events.ownerId,
      departmentId: users.departmentId,
      ownerName: users.fullName,
      invited: events.invitedCount,
      attended: events.attendedCount,
      orders: events.resultOrderCount,
      revenue: events.resultRevenue,
    })
    .from(events)
    .leftJoin(users, eq(events.ownerId, users.id))
    .where(and(scope, inRange(range)));

  const keyOf = (r: (typeof list)[number]) => (dimension === "khu-vuc" ? r.territoryId : dimension === "phong-ban" ? r.departmentId : r.ownerId) ?? "";
  const own = new Map<string, ReturnType<typeof emptyAgg>>();
  for (const r of list) {
    const bucket = own.get(keyOf(r)) ?? emptyAgg();
    bucket.classes++;
    bucket.invited += r.invited;
    bucket.attended += r.attended;
    bucket.orders += r.orders;
    bucket.revenue += num(r.revenue);
    own.set(keyOf(r), bucket);
  }
  const row = (id: string, name: string, depth: number, agg: ReturnType<typeof emptyAgg>): TreeRow<TransferAgg> => ({ id, name, depth, ...withRate(agg) });

  if (dimension === "nhan-vien") {
    const names = new Map(list.map((r) => [r.ownerId ?? "", r.ownerName ?? "Chưa phân công"]));
    return [...own.entries()].map(([id, agg]) => row(id || "none", names.get(id) ?? "Chưa phân công", 0, agg)).sort((a, b) => b.classes - a.classes || b.attended - a.attended || a.name.localeCompare(b.name, "vi"));
  }

  const nodes = dimension === "khu-vuc" ? await loadTerritoryTree(session.tenantId) : await loadDepartmentTree(session.tenantId);
  const descendants = descendantsOf(nodes);
  // Cây phòng ban: nhánh người xem thấy CỘNG các phòng có lớp trong phạm vi báo cáo (lớp tổ chức tại khu vực của mình nhưng do nhân viên nhánh khác dạy);
  // tổ tiên ngoài phạm vi và không có lớp riêng (ví dụ "Ban giám đốc") bị ẩn để không mang số của riêng phạm vi người xem như số toàn công ty.
  // Cây khu vực không lọc theo quyền khu vực: nơi tổ chức lớp chỉ là thông tin, nút nào không có lớp thì tự ẩn.
  const visible = dimension === "phong-ban" ? await visibleTreeNodes(session, dimension, nodes) : null;
  const shown = visible ? new Set([...visible, ...[...own.keys()].filter(Boolean)]) : null;
  const rows: TreeRow<TransferAgg>[] = [];
  for (const { node } of inTreeOrder(nodes)) {
    if (shown && !shown.has(node.id)) continue;
    const sum = emptyAgg();
    for (const id of descendants.get(node.id) ?? [node.id]) {
      const a = own.get(id);
      if (!a) continue;
      sum.classes += a.classes;
      sum.invited += a.invited;
      sum.attended += a.attended;
      sum.orders += a.orders;
      sum.revenue += a.revenue;
    }
    if (sum.classes <= 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" ? "Toàn quốc / chưa gắn khu vực" : "Chưa phân công", 0, loose));
  return rows;
}

/* -------------------------------------------------------------------------- */
/* Danh sách lớp                                                              */
/* -------------------------------------------------------------------------- */

export type TransferClassRow = {
  id: string;
  name: string;
  kind: string;
  eventDate: string;
  invitedCount: number;
  attendedCount: number;
  resultOrderCount: number;
  resultRevenue: number;
  note: string | null;
  territoryName: string | null;
  ownerId: string | null;
  ownerName: string | null;
  attendanceRate: number | null;
};

export async function transferList(session: SessionUser, range: DateRange, options: { page?: number; pageSize?: number } = {}): Promise<{ rows: TransferClassRow[]; total: number; page: number; totalPages: number }> {
  const scope = and(await eventScope(session), inRange(range));
  const [{ n }] = await db.select({ n: count() }).from(events).where(scope);
  const total = num(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: events.id,
      name: events.name,
      kind: events.kind,
      eventDate: sql<string>`${events.eventDate}::text`,
      invitedCount: events.invitedCount,
      attendedCount: events.attendedCount,
      resultOrderCount: events.resultOrderCount,
      resultRevenue: events.resultRevenue,
      note: events.note,
      territoryName: territories.name,
      ownerId: events.ownerId,
      ownerName: users.fullName,
    })
    .from(events)
    .leftJoin(territories, eq(events.territoryId, territories.id))
    .leftJoin(users, eq(events.ownerId, users.id))
    .where(scope)
    .orderBy(desc(events.eventDate), events.name)
    .limit(pageSize)
    .offset((page - 1) * pageSize);

  return {
    rows: rows.map((r) => ({ ...r, resultRevenue: num(r.resultRevenue), attendanceRate: r.invitedCount > 0 ? (r.attendedCount / r.invitedCount) * 100 : null })),
    total,
    page,
    totalPages,
  };
}

/* -------------------------------------------------------------------------- */
/* Khách hoàn thành khóa chuyển giao                                          */
/* -------------------------------------------------------------------------- */

export type CourseCompletionRow = { courseId: string; name: string; completions: number; customers: number; free: number };

/** Lượt khách hoàn thành từng khóa chuyển giao trong kỳ — khách trong phạm vi người xem (vai trò + bộ chọn), chỉ khách còn hoạt động. */
export async function courseCompletions(session: SessionUser, range: DateRange): Promise<{ rows: CourseCompletionRow[]; totalCompletions: number; totalCustomers: number }> {
  const scope = and(eq(customerCourseCompletions.tenantId, session.tenantId), await customerScope(session), gte(customerCourseCompletions.completedDate, range.start), lte(customerCourseCompletions.completedDate, range.end));
  const [rows, totals] = await Promise.all([
    db
      .select({
        courseId: trainingCourses.id,
        name: trainingCourses.name,
        completions: sql<number>`count(*)::int`,
        customers: sql<number>`count(distinct ${customerCourseCompletions.customerId})::int`,
        free: sql<number>`count(*) filter (where ${customerCourseCompletions.isFree})::int`,
      })
      .from(customerCourseCompletions)
      .innerJoin(trainingCourses, eq(customerCourseCompletions.courseId, trainingCourses.id))
      .innerJoin(customers, eq(customerCourseCompletions.customerId, customers.id))
      .where(scope)
      .groupBy(trainingCourses.id, trainingCourses.name)
      .orderBy(desc(sql`count(*)`), trainingCourses.name),
    db
      .select({ completions: sql<number>`count(*)::int`, customers: sql<number>`count(distinct ${customerCourseCompletions.customerId})::int` })
      .from(customerCourseCompletions)
      .innerJoin(customers, eq(customerCourseCompletions.customerId, customers.id))
      .where(scope),
  ]);
  return {
    rows: rows.map((r) => ({ courseId: r.courseId, name: r.name, completions: num(r.completions), customers: num(r.customers), free: num(r.free) })),
    totalCompletions: num(totals[0]?.completions),
    totalCustomers: num(totals[0]?.customers),
  };
}
