import "server-only";
import { and, eq, inArray, sql, desc } from "drizzle-orm";
import { db } from "@/db";
import {
  inviteEvents,
  inviteEventTerritoryQuotas,
  inviteEventDeptQuotas,
  inviteEventStaffQuotas,
  inviteEventGuests,
  territories,
  departments,
  users,
} from "@/db/schema";
import type { SessionUser } from "./auth";
import {
  assignedTerritoryIds,
  departmentSubtree,
  visibleDepartmentIds,
  visibleTerritoryIds,
  visibleUserIds,
} from "./rbac";
import { today } from "./period";

/** Toàn bộ danh sách sự kiện có giao chỉ tiêu — ai cũng xem được danh sách, chi tiết mới lọc theo phạm vi. */
export async function listInviteEvents(session: SessionUser) {
  return db
    .select({
      id: inviteEvents.id,
      name: inviteEvents.name,
      eventDate: inviteEvents.eventDate,
      inviteDeadline: inviteEvents.inviteDeadline,
      note: inviteEvents.note,
    })
    .from(inviteEvents)
    .where(eq(inviteEvents.tenantId, session.tenantId))
    .orderBy(desc(inviteEvents.eventDate));
}

const NONE = "00000000-0000-0000-0000-000000000000";

/**
 * Những người mà người xem được thấy chỉ tiêu và khách mời của họ (cha thấy con, con không thấy anh em):
 *  - Ban giám đốc: tất cả (null);
 *  - NVKD: chỉ chính mình;
 *  - Giám đốc khu vực / Quản lý: cả nhánh phòng ban mình phụ trách; chưa gán phòng ban thì lùi về nhân viên thuộc khu vực mình phụ trách.
 */
export async function inviteScopeUserIds(session: SessionUser): Promise<string[] | null> {
  if (session.role === "DIRECTOR") return null;
  const ids = await visibleUserIds(session);
  if (ids) return ids;

  const territoryIds = (await visibleTerritoryIds(session)) ?? [];
  if (territoryIds.length === 0) return [session.userId];
  const rows = await db
    .select({ id: users.id })
    .from(users)
    .where(and(eq(users.tenantId, session.tenantId), inArray(users.territoryId, territoryIds)));
  return [...new Set([session.userId, ...rows.map((r) => r.id)])];
}

/** Người xem có được sửa/xoá khách mời do `invitedBy` ghi nhận không: của chính mình, hoặc của cấp dưới trong nhánh. */
export async function canManageInviteGuest(session: SessionUser, invitedBy: string): Promise<boolean> {
  if (invitedBy === session.userId || session.role === "DIRECTOR") return true;
  if (session.role === "SALES") return false;
  const ids = await inviteScopeUserIds(session);
  return !ids || ids.includes(invitedBy);
}

export async function getInviteEventDetail(session: SessionUser, eventId: string) {
  const [event] = await db
    .select({
      id: inviteEvents.id,
      name: inviteEvents.name,
      eventDate: inviteEvents.eventDate,
      inviteDeadline: inviteEvents.inviteDeadline,
      note: inviteEvents.note,
    })
    .from(inviteEvents)
    .where(and(eq(inviteEvents.tenantId, session.tenantId), eq(inviteEvents.id, eventId)))
    .limit(1);
  if (!event) return null;

  const scopeIds = await inviteScopeUserIds(session);
  const userFilter = (column: Parameters<typeof inArray>[0]) =>
    scopeIds === null ? undefined : inArray(column, scopeIds.length > 0 ? scopeIds : [NONE]);

  const [me] = await db
    .select({ departmentId: users.departmentId })
    .from(users)
    .where(eq(users.id, session.userId))
    .limit(1);
  const deptScope = session.role === "DIRECTOR" ? null : ((await visibleDepartmentIds(session)) ?? []);

  const [guestRows, staffQuotaRows, deptQuotaRows, territoryQuotaRows, allDepartments, allUsers, allGuestRegionRows] =
    await Promise.all([
      db
        .select({
          id: inviteEventGuests.id,
          guestName: inviteEventGuests.guestName,
          guestType: inviteEventGuests.guestType,
          invitedDate: inviteEventGuests.invitedDate,
          attended: inviteEventGuests.attended,
          wantsCme: inviteEventGuests.wantsCme,
          wantsCertificate: inviteEventGuests.wantsCertificate,
          note: inviteEventGuests.note,
          invitedBy: inviteEventGuests.invitedBy,
          invitedByName: users.fullName,
          territoryId: inviteEventGuests.territoryId,
          territoryName: territories.name,
        })
        .from(inviteEventGuests)
        .leftJoin(users, eq(inviteEventGuests.invitedBy, users.id))
        .leftJoin(territories, eq(inviteEventGuests.territoryId, territories.id))
        .where(
          and(
            eq(inviteEventGuests.tenantId, session.tenantId),
            eq(inviteEventGuests.eventId, eventId),
            userFilter(inviteEventGuests.invitedBy),
          ),
        )
        .orderBy(desc(inviteEventGuests.invitedDate), desc(inviteEventGuests.createdAt)),
      db
        .select({
          userId: inviteEventStaffQuotas.userId,
          userName: users.fullName,
          departmentId: users.departmentId,
          territoryId: inviteEventStaffQuotas.territoryId,
          territoryName: territories.name,
          quota: inviteEventStaffQuotas.quota,
        })
        .from(inviteEventStaffQuotas)
        .leftJoin(users, eq(inviteEventStaffQuotas.userId, users.id))
        .leftJoin(territories, eq(inviteEventStaffQuotas.territoryId, territories.id))
        .where(
          and(
            eq(inviteEventStaffQuotas.tenantId, session.tenantId),
            eq(inviteEventStaffQuotas.eventId, eventId),
            userFilter(inviteEventStaffQuotas.userId),
          ),
        ),
      db
        .select({ departmentId: inviteEventDeptQuotas.departmentId, quota: inviteEventDeptQuotas.quota })
        .from(inviteEventDeptQuotas)
        .where(
          and(
            eq(inviteEventDeptQuotas.tenantId, session.tenantId),
            eq(inviteEventDeptQuotas.eventId, eventId),
            deptScope === null ? undefined : inArray(inviteEventDeptQuotas.departmentId, deptScope.length > 0 ? deptScope : [NONE]),
          ),
        ),
      // Chỉ tiêu theo khu vực (miền) chỉ hiện với Ban giám đốc và Giám đốc khu vực — cấp thấp hơn không cần thấy số của miền.
      session.role === "DIRECTOR" || session.role === "GDKV"
        ? db
            .select({
              territoryId: inviteEventTerritoryQuotas.territoryId,
              territoryName: territories.name,
              quota: inviteEventTerritoryQuotas.quota,
            })
            .from(inviteEventTerritoryQuotas)
            .leftJoin(territories, eq(inviteEventTerritoryQuotas.territoryId, territories.id))
            .where(
              and(
                eq(inviteEventTerritoryQuotas.tenantId, session.tenantId),
                eq(inviteEventTerritoryQuotas.eventId, eventId),
                session.role === "DIRECTOR"
                  ? undefined
                  : inArray(inviteEventTerritoryQuotas.territoryId, (await assignedTerritoryIds(session)).length > 0 ? await assignedTerritoryIds(session) : [NONE]),
              ),
            )
        : Promise.resolve([] as { territoryId: string; territoryName: string | null; quota: number }[]),
      db
        .select({ id: departments.id, name: departments.name, parentId: departments.parentId })
        .from(departments)
        .where(eq(departments.tenantId, session.tenantId)),
      db
        .select({ id: users.id, fullName: users.fullName, departmentId: users.departmentId, territoryId: users.territoryId, role: users.role, isActive: users.isActive })
        .from(users)
        .where(eq(users.tenantId, session.tenantId)),
      // Đếm khách theo miền cho bảng khu vực (Ban giám đốc / GĐKV): toàn bộ khách của sự kiện gom theo khu vực.
      session.role === "DIRECTOR" || session.role === "GDKV"
        ? db
            .select({ territoryId: inviteEventGuests.territoryId, attended: inviteEventGuests.attended, count: sql<number>`count(*)::int` })
            .from(inviteEventGuests)
            .where(and(eq(inviteEventGuests.tenantId, session.tenantId), eq(inviteEventGuests.eventId, eventId)))
            .groupBy(inviteEventGuests.territoryId, inviteEventGuests.attended)
        : Promise.resolve([] as { territoryId: string; attended: boolean | null; count: number }[]),
    ]);

  /* --- Đếm theo người / phòng ban --- */
  const invitedByUser = new Map<string, number>();
  const attendedByUser = new Map<string, number>();
  for (const g of guestRows) {
    invitedByUser.set(g.invitedBy, (invitedByUser.get(g.invitedBy) ?? 0) + 1);
    if (g.attended === true) attendedByUser.set(g.invitedBy, (attendedByUser.get(g.invitedBy) ?? 0) + 1);
  }
  const userDept = new Map(allUsers.map((u) => [u.id, u.departmentId]));
  const deptName = new Map(allDepartments.map((d) => [d.id, d.name]));

  const deptQuotas = [];
  for (const row of deptQuotaRows) {
    const subtree = new Set(await departmentSubtree(session.tenantId, [row.departmentId]));
    let invited = 0;
    let attended = 0;
    for (const [uid, n] of invitedByUser) if (userDept.get(uid) && subtree.has(userDept.get(uid)!)) invited += n;
    for (const [uid, n] of attendedByUser) if (userDept.get(uid) && subtree.has(userDept.get(uid)!)) attended += n;
    // Tổng chỉ tiêu đã chia xuống nhân viên của phòng này, để cảnh báo giao thiếu/vượt.
    const allocated = staffQuotaRows.filter((s) => s.departmentId && subtree.has(s.departmentId)).reduce((sum, s) => sum + s.quota, 0);
    deptQuotas.push({ ...row, departmentName: deptName.get(row.departmentId) ?? "—", invitedCount: invited, attendedCount: attended, allocated });
  }

  // Khu vực: miền của từng tỉnh để cộng khách về đúng miền.
  const territoryParents = await db
    .select({ id: territories.id, parentId: territories.parentId })
    .from(territories)
    .where(eq(territories.tenantId, session.tenantId));
  const parentOf = new Map(territoryParents.map((t) => [t.id, t.parentId]));
  const regionOfTerritory = (territoryId: string): string => {
    let current = territoryId;
    const seen = new Set<string>();
    while (parentOf.get(current) && !seen.has(current)) {
      seen.add(current);
      current = parentOf.get(current)!;
    }
    return current;
  };
  const invitedByRegion = new Map<string, number>();
  const attendedByRegion = new Map<string, number>();
  for (const row of allGuestRegionRows) {
    const region = regionOfTerritory(row.territoryId);
    invitedByRegion.set(region, (invitedByRegion.get(region) ?? 0) + row.count);
    if (row.attended === true) attendedByRegion.set(region, (attendedByRegion.get(region) ?? 0) + row.count);
  }
  const territoryQuotas = territoryQuotaRows.map((r) => {
    // Đã chia xuống phòng ban / nhân viên thuộc miền này (theo khu vực của nhân viên).
    return {
      ...r,
      invitedCount: invitedByRegion.get(r.territoryId) ?? 0,
      attendedCount: attendedByRegion.get(r.territoryId) ?? 0,
    };
  });

  const staffQuotas = staffQuotaRows.map((r) => ({
    ...r,
    invitedCount: invitedByUser.get(r.userId) ?? 0,
    attendedCount: attendedByUser.get(r.userId) ?? 0,
  }));

  /* --- Theo loại khách và theo ngày (trong phạm vi người xem) --- */
  const typeMap = new Map<string, { invited: number; attended: number }>();
  for (const g of guestRows) {
    const key = (g.guestType ?? "").trim() || "Chưa phân loại";
    const cur = typeMap.get(key) ?? { invited: 0, attended: 0 };
    cur.invited += 1;
    if (g.attended === true) cur.attended += 1;
    typeMap.set(key, cur);
  }
  const byType = [...typeMap.entries()].map(([type, v]) => ({ type, ...v })).sort((a, b) => b.invited - a.invited);

  const dayMap = new Map<string, number>();
  for (const g of guestRows) dayMap.set(g.invitedDate, (dayMap.get(g.invitedDate) ?? 0) + 1);
  let running = 0;
  const byDay = [...dayMap.entries()]
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([date, count]) => ({ date, count, cumulative: (running += count) }));

  /* --- Tổng của người xem + tiến độ theo hạn chót --- */
  const invitedTotal = guestRows.length;
  const attendedTotal = guestRows.filter((g) => g.attended === true).length;
  const decidedTotal = guestRows.filter((g) => g.attended !== null).length;

  let quotaTotal: number;
  let quotaLabel: string;
  if (session.role === "DIRECTOR" || session.role === "GDKV") {
    quotaTotal = territoryQuotas.reduce((s, r) => s + r.quota, 0);
    quotaLabel = session.role === "DIRECTOR" ? "Chỉ tiêu toàn công ty" : "Chỉ tiêu khu vực của tôi";
  } else if (session.role === "MANAGER") {
    const own = deptQuotas.find((d) => d.departmentId === me?.departmentId);
    quotaTotal = own ? own.quota : staffQuotas.reduce((s, r) => s + r.quota, 0);
    quotaLabel = own ? "Chỉ tiêu phòng/team của tôi" : "Tổng chỉ tiêu đã giao cho nhân viên";
  } else {
    quotaTotal = staffQuotas.find((r) => r.userId === session.userId)?.quota ?? 0;
    quotaLabel = "Chỉ tiêu của tôi";
  }

  const deadline = event.inviteDeadline ?? event.eventDate;
  const todayIso = today();
  const daysLeft = Math.round((Date.parse(`${deadline}T00:00:00Z`) - Date.parse(`${todayIso}T00:00:00Z`)) / 86_400_000);
  const remaining = Math.max(0, quotaTotal - invitedTotal);
  const perDayNeeded = quotaTotal > 0 && daysLeft > 0 && remaining > 0 ? Math.ceil(remaining / daysLeft) : null;

  /* --- Quyền giao chỉ tiêu --- */
  const myAssignedTerritoryIds = await assignedTerritoryIds(session);
  const canSetTerritoryQuota = session.role === "DIRECTOR";
  // Giao xuống phòng ban: chỉ phòng nằm DƯỚI phòng của mình (cha giao cho con); Ban giám đốc giao bất kỳ phòng nào.
  const assignableDepartmentIds =
    session.role === "DIRECTOR"
      ? allDepartments.map((d) => d.id)
      : session.role === "SALES"
        ? []
        : (deptScope ?? []).filter((id) => id !== me?.departmentId);
  // Giao xuống nhân viên: NVKD thuộc phạm vi của mình (Quản lý giao cho nhân viên trong nhánh mình).
  const assignableStaffIds =
    session.role === "SALES"
      ? []
      : allUsers
          .filter((u) => u.role === "SALES" && u.isActive && u.id !== session.userId && (scopeIds === null || scopeIds.includes(u.id)))
          .map((u) => u.id);

  return {
    event: { ...event, deadline },
    deadline,
    daysLeft,
    perDayNeeded,
    quotaLabel,
    quotaTotal,
    invitedTotal,
    attendedTotal,
    decidedTotal,
    territoryQuotas,
    deptQuotas,
    staffQuotas,
    guests: guestRows,
    byType,
    byDay,
    canSetTerritoryQuota,
    assignableDepartments: allDepartments.filter((d) => assignableDepartmentIds.includes(d.id) && !deptQuotaRows.some((q) => q.departmentId === d.id)).map((d) => ({ id: d.id, name: d.name })),
    canSetDeptQuotaIds: new Set(assignableDepartmentIds),
    assignableStaff: allUsers
      .filter((u) => assignableStaffIds.includes(u.id) && !staffQuotaRows.some((q) => q.userId === u.id))
      .map((u) => ({ id: u.id, fullName: u.fullName, departmentName: u.departmentId ? (deptName.get(u.departmentId) ?? null) : null })),
    canSetStaffQuotaIds: new Set(assignableStaffIds),
    myAssignedTerritoryIds,
  };
}
