/**
 * Phạm vi dữ liệu theo vai trò.
 *
 * Nguyên tắc: mọi truy vấn dữ liệu nghiệp vụ đều bắt đầu bằng `scopeFilter()`.
 * Hàm này luôn kèm điều kiện tenant, nên không thể vô tình đọc dữ liệu của
 * khách hàng khác — kể cả khi quên thêm điều kiện lọc khác.
 *
 *   DIRECTOR → toàn bộ dữ liệu trong tenant
 *   MANAGER  → dữ liệu của nhánh phòng ban mình phụ trách
 *   SALES    → chỉ khách hàng / đơn hàng do mình phụ trách
 *
 * ## Vì sao phân quyền theo phòng ban chứ không theo khu vực
 *
 * Khu vực trả lời "khách hàng ở đâu", phòng ban trả lời "ai quản ai". Hai chiều
 * này trùng nhau khi công ty chia team thuần theo miền, nhưng tách ra ngay khi có
 * team theo dòng sản phẩm (ví dụ "Sale Solar Team – Hồ Chí Minh") hoặc khi hai
 * team cùng phụ trách một miền. Dùng khu vực làm chiều phân quyền thì không diễn
 * tả được các trường hợp đó.
 *
 * Với người dùng chưa được gán phòng ban, hệ thống lùi về phạm vi khu vực cũ để
 * dữ liệu đang chạy không đứt khi chuyển đổi.
 */
import { and, eq, inArray, or, type SQL } from "drizzle-orm";
import type { PgColumn } from "drizzle-orm/pg-core";
import { db } from "@/db";
import { userTerritories, territories, departments, users } from "@/db/schema";
import type { SessionUser } from "./auth";

export type ScopeColumns = {
  tenantId: PgColumn;
  /** Cột NVKD phụ trách — dùng cho vai trò SALES và MANAGER. */
  ownerId?: PgColumn;
  /** Cột khu vực — chỉ dùng khi người dùng chưa có phòng ban. */
  territoryId?: PgColumn;
};

/** UUID không bao giờ tồn tại — dùng để tạo điều kiện luôn sai. */
const NULL_UUID = "00000000-0000-0000-0000-000000000000";

/* -------------------------------------------------------------------------- */
/* Cây phòng ban                                                              */
/* -------------------------------------------------------------------------- */

/** Bung một phòng ban xuống toàn bộ nhánh con. */
export async function departmentSubtree(tenantId: string, rootIds: string[]): Promise<string[]> {
  if (rootIds.length === 0) return [];

  const all = await db
    .select({ id: departments.id, parentId: departments.parentId })
    .from(departments)
    .where(eq(departments.tenantId, tenantId));

  const childrenOf = new Map<string, string[]>();
  for (const row of all) {
    if (!row.parentId) continue;
    const list = childrenOf.get(row.parentId) ?? [];
    list.push(row.id);
    childrenOf.set(row.parentId, list);
  }

  const result = new Set<string>();
  const queue = [...rootIds];
  while (queue.length > 0) {
    const id = queue.pop()!;
    if (result.has(id)) continue;
    result.add(id);
    queue.push(...(childrenOf.get(id) ?? []));
  }
  return [...result];
}

/**
 * Danh sách phòng ban người dùng được xem, đã bung xuống các nhánh con.
 * DIRECTOR trả về null nghĩa là không giới hạn.
 */
export async function visibleDepartmentIds(session: SessionUser): Promise<string[] | null> {
  if (session.role === "DIRECTOR") return null;

  const [me] = await db
    .select({ departmentId: users.departmentId })
    .from(users)
    .where(eq(users.id, session.userId))
    .limit(1);

  if (!me?.departmentId) return [];

  // NVKD chỉ thấy dữ liệu của chính mình, không thấy cả phòng ban.
  if (session.role === "SALES") return [me.departmentId];

  return departmentSubtree(session.tenantId, [me.departmentId]);
}

/**
 * Danh sách người dùng mà người xem được phép thấy dữ liệu.
 * null nghĩa là không giới hạn (Ban giám đốc).
 */
export async function visibleUserIds(session: SessionUser): Promise<string[] | null> {
  if (session.role === "DIRECTOR") return null;
  if (session.role === "SALES") return [session.userId];

  const departmentIds = await visibleDepartmentIds(session);

  // Quản lý chưa được gán phòng ban: lùi về phạm vi khu vực cũ.
  if (!departmentIds || departmentIds.length === 0) return null;

  const rows = await db
    .select({ id: users.id })
    .from(users)
    .where(and(eq(users.tenantId, session.tenantId), inArray(users.departmentId, departmentIds)));

  // Luôn kèm chính mình, phòng khi quản lý không nằm trong phòng ban nào.
  return [...new Set([session.userId, ...rows.map((r) => r.id)])];
}

/* -------------------------------------------------------------------------- */
/* Cây khu vực — chỉ còn dùng cho phân tích thị trường và bản lùi              */
/* -------------------------------------------------------------------------- */

/**
 * Khu vực được gán trực tiếp cho người dùng, chưa bung xuống cấp con — dùng để
 * đối chiếu đúng phạm vi khi tra chỉ tiêu (chỉ tiêu giao theo khu vực nào thì
 * chỉ khu vực đó có scopeId trùng, không phải toàn bộ cây con).
 */
export async function assignedTerritoryIds(session: SessionUser): Promise<string[]> {
  if (session.role === "MANAGER" || session.role === "GDKV") {
    const rows = await db
      .select({ territoryId: userTerritories.territoryId })
      .from(userTerritories)
      .where(
        and(eq(userTerritories.tenantId, session.tenantId), eq(userTerritories.userId, session.userId)),
      );
    return rows.map((r) => r.territoryId);
  }
  return session.territoryId ? [session.territoryId] : [];
}

/**
 * Danh sách khu vực một người được xem, đã bung xuống các cấp con.
 * DIRECTOR trả về null nghĩa là không giới hạn khu vực.
 */
export async function visibleTerritoryIds(session: SessionUser): Promise<string[] | null> {
  if (session.role === "DIRECTOR") return null;

  const assigned = await assignedTerritoryIds(session);
  if (assigned.length === 0) return [];

  return expandTerritoryDescendants(session.tenantId, assigned);
}

/** Bung một danh sách khu vực xuống toàn bộ cấp con (tỉnh → quận → phường). */
export async function expandTerritoryDescendants(tenantId: string, rootIds: string[]): Promise<string[]> {
  const all = await db
    .select({ id: territories.id, parentId: territories.parentId })
    .from(territories)
    .where(eq(territories.tenantId, tenantId));

  const childrenOf = new Map<string, string[]>();
  for (const row of all) {
    if (!row.parentId) continue;
    const list = childrenOf.get(row.parentId) ?? [];
    list.push(row.id);
    childrenOf.set(row.parentId, list);
  }

  const result = new Set<string>();
  const queue = [...rootIds];
  while (queue.length > 0) {
    const id = queue.pop()!;
    if (result.has(id)) continue;
    result.add(id);
    queue.push(...(childrenOf.get(id) ?? []));
  }
  return [...result];
}

/** Miền (khu vực cấp REGION) cha của một khu vực bất kỳ — đi ngược `parentId` tới gốc. */
export async function regionOf(tenantId: string, territoryId: string): Promise<string> {
  const all = await db
    .select({ id: territories.id, parentId: territories.parentId })
    .from(territories)
    .where(eq(territories.tenantId, tenantId));
  const parentOf = new Map(all.map((t) => [t.id, t.parentId]));

  let current = territoryId;
  const seen = new Set<string>();
  while (parentOf.get(current) && !seen.has(current)) {
    seen.add(current);
    current = parentOf.get(current)!;
  }
  return current;
}

/* -------------------------------------------------------------------------- */
/* Điều kiện WHERE                                                            */
/* -------------------------------------------------------------------------- */

/**
 * Điều kiện WHERE giới hạn dữ liệu theo vai trò. Luôn dùng kết quả của hàm này
 * làm điều kiện gốc, rồi `and()` thêm bộ lọc của màn hình.
 */
export async function scopeFilter(
  session: SessionUser,
  columns: ScopeColumns,
): Promise<SQL | undefined> {
  const tenantCondition = eq(columns.tenantId, session.tenantId);

  if (session.role === "DIRECTOR") return tenantCondition;

  if (session.role === "SALES") {
    if (columns.ownerId) return and(tenantCondition, eq(columns.ownerId, session.userId));
    return tenantCondition;
  }

  // MANAGER: theo nhánh phòng ban phụ trách.
  const userIds = await visibleUserIds(session);

  if (userIds && columns.ownerId) {
    return and(
      tenantCondition,
      userIds.length > 0 ? inArray(columns.ownerId, userIds) : eq(columns.tenantId, NULL_UUID),
    );
  }

  // Chưa gán phòng ban: lùi về phạm vi khu vực như trước.
  if (columns.territoryId) {
    const territoryIds = await visibleTerritoryIds(session);
    if (territoryIds) {
      if (territoryIds.length === 0) return and(tenantCondition, eq(columns.tenantId, NULL_UUID));
      const territoryCondition = inArray(columns.territoryId, territoryIds);
      return columns.ownerId
        ? and(tenantCondition, or(territoryCondition, eq(columns.ownerId, session.userId)))
        : and(tenantCondition, territoryCondition);
    }
  }

  return tenantCondition;
}

export function canManageSettings(session: SessionUser): boolean {
  return session.role === "DIRECTOR";
}

export function canViewAllStaff(session: SessionUser): boolean {
  return session.role === "DIRECTOR" || session.role === "GDKV" || session.role === "MANAGER";
}

export function canEditCatalog(session: SessionUser): boolean {
  return session.role === "DIRECTOR" || session.role === "GDKV" || session.role === "MANAGER";
}

/**
 * Được duyệt đơn hàng — Giám đốc Khu vực (đơn thuộc nhánh phòng ban mình phụ
 * trách) hoặc Ban giám đốc (mọi đơn). Trưởng bộ phận (MANAGER) không duyệt được:
 * theo khách xác nhận, chỉ cần đúng 1 cấp GĐKV duyệt là đơn đi được.
 */
export function canApproveOrders(session: SessionUser): boolean {
  return session.role === "DIRECTOR" || session.role === "GDKV";
}

/**
 * Được tải văn bản công ty lên — Ban giám đốc mặc định có, hoặc bất kỳ ai được
 * cấp riêng cờ `canUploadDocuments` (ví dụ HCNS) mà không cần lên hẳn vai trò DIRECTOR.
 */
export async function canUploadCompanyDocuments(session: SessionUser): Promise<boolean> {
  if (session.role === "DIRECTOR") return true;
  const [row] = await db
    .select({ canUploadDocuments: users.canUploadDocuments })
    .from(users)
    .where(eq(users.id, session.userId))
    .limit(1);
  return row?.canUploadDocuments ?? false;
}

/**
 * Người dùng có được thấy một đơn hàng không — dùng cho trang chi tiết.
 *
 * Danh sách đơn của quản lý lọc theo khu vực, còn quyền duyệt/sửa lại theo nhánh phòng ban;
 * ở trang chi tiết chấp nhận cả hai, để đơn nào đã hiện trong danh sách thì bấm vào là mở được,
 * nhưng đơn của nhánh/khu vực khác thì vẫn không xem được bằng cách đoán đường dẫn.
 */
export async function orderViewChecker(
  session: SessionUser,
): Promise<(order: { salesUserId: string | null; territoryId: string | null }) => boolean> {
  if (session.role === "DIRECTOR") return () => true;
  if (session.role === "SALES") return (order) => order.salesUserId === session.userId;

  // Cùng quy tắc với danh sách đơn (`scopeFilter`): theo nhánh phòng ban; chỉ khi chưa gán phòng ban mới lùi về khu vực.
  const userIds = await visibleUserIds(session);
  if (userIds) return (order) => !!order.salesUserId && userIds.includes(order.salesUserId);
  const territoryIds = await visibleTerritoryIds(session);
  return (order) => !!order.territoryId && !!territoryIds && territoryIds.includes(order.territoryId);
}

export async function canViewOrder(
  session: SessionUser,
  order: { salesUserId: string | null; territoryId: string | null },
): Promise<boolean> {
  return (await orderViewChecker(session))(order);
}

/**
 * Cấp trên chỉ được giao chỉ tiêu cho đối tượng NẰM DƯỚI mình (cha giao cho con, không giao ngang hay ngược lên).
 * Ban giám đốc giao được mọi đối tượng kể cả toàn công ty; quản lý không giao được chỉ tiêu toàn công ty và
 * không giao được cho chính mình hay người cùng cấp ngoài nhánh phụ trách.
 */
export async function canAssignTargetTo(
  session: SessionUser,
  scopeType: "COMPANY" | "DEPARTMENT" | "TERRITORY" | "USER" | "CUSTOMER",
  scopeId: string | null,
): Promise<boolean> {
  if (session.role === "DIRECTOR") return true;
  if (session.role === "SALES" || scopeType === "COMPANY" || !scopeId) return false;
  if (scopeType === "USER") {
    const ids = await visibleUserIds(session);
    return scopeId !== session.userId && (!ids || ids.includes(scopeId));
  }
  if (scopeType === "DEPARTMENT") {
    const ids = await visibleDepartmentIds(session);
    return !ids || ids.includes(scopeId);
  }
  if (scopeType === "TERRITORY") {
    const ids = await visibleTerritoryIds(session);
    return !ids || ids.includes(scopeId);
  }
  // CUSTOMER: khách phải thuộc phạm vi quản lý — kiểm ở nơi gọi bằng scopeFilter.
  return true;
}

/**
 * Tên đầy đủ của từng phòng ban/team dạng "Phòng kinh doanh › Team Miền Bắc" — hiển thị đúng 5 bậc
 * Công ty › Khu vực › Phòng ban › Team › Nhân viên (Team chỉ là cấp con của phòng ban, phòng nào không chia Team thì chỉ có tên phòng).
 */
export async function departmentPaths(tenantId: string): Promise<Map<string, string>> {
  const rows = await db
    .select({ id: departments.id, name: departments.name, parentId: departments.parentId })
    .from(departments)
    .where(eq(departments.tenantId, tenantId));
  const byId = new Map(rows.map((row) => [row.id, row]));
  const paths = new Map<string, string>();
  for (const row of rows) {
    const parts = [row.name];
    let cursor = row.parentId ? byId.get(row.parentId) : undefined;
    let guard = 0; // chống vòng lặp nếu dữ liệu cây bị lỗi
    while (cursor && guard++ < 10) {
      parts.unshift(cursor.name);
      cursor = cursor.parentId ? byId.get(cursor.parentId) : undefined;
    }
    paths.set(row.id, parts.join(" › "));
  }
  return paths;
}
