/**
 * Đọc và trình bày nhật ký thao tác (`audit_logs`) cho Ban giám đốc — yêu cầu bổ sung của khách:
 * "các thông tin chỉnh sửa được lưu lại, Admin xem được lịch sử chỉnh sửa".
 *
 * Nhật ký lưu mã nội bộ (uuid) cho khu vực, người phụ trách, kênh... nên nếu hiện thẳng thì "Người phụ trách: 4f1c… → 9a0e…" chẳng nói lên điều gì.
 * Ở đây mọi mã trong nội dung được dò tên thật (cùng công ty) trước khi hiển thị; nhãn trường và loại đối tượng đều bằng tiếng Việt.
 * Chỉ Ban giám đốc gọi các hàm này — nơi gọi phải tự kiểm `canManageSettings`.
 */
import { and, desc, eq, gte, inArray, lte, sql, type SQL } from "drizzle-orm";
import { db } from "@/db";
import { auditLogs, channels, customers, departments, products, territories, users } from "@/db/schema";
import { formatNumber } from "./format";
import { isRealDate } from "./period";
import { isUuid } from "./route-params";
import { vnDate } from "./sql-dates";

export const ACTION_LABEL: Record<string, string> = {
  CREATE: "Tạo mới",
  UPDATE: "Cập nhật",
  DELETE: "Xoá",
  LOGIN: "Đăng nhập",
  IMPORT: "Nhập dữ liệu",
  EXPORT: "Xuất dữ liệu",
};

export const ENTITY_LABEL: Record<string, string> = {
  order: "Đơn hàng",
  payment: "Phiếu thu",
  order_attachment: "Tệp đính kèm đơn",
  customer: "Khách hàng",
  customer_contact: "Người liên quan của khách",
  customer_note: "Thông tin trao đổi",
  customer_attachment: "Tệp đính kèm khách",
  customer_course_completion: "Khoá học của khách",
  proposal: "Phiếu đề xuất",
  user: "Người dùng",
  password_change: "Đổi mật khẩu",
  password_reset: "Đặt lại mật khẩu",
  target: "Chỉ tiêu",
  task: "Công việc",
  announcement: "Thông báo",
  company_document: "Văn bản công ty",
  training_course: "Khoá chuyển giao",
  event: "Chuyên đề & lớp học",
  invite_event: "Sự kiện mời khách",
  invite_guest: "Khách mời sự kiện",
  custom_kpi: "KPI tự tạo",
  custom_kpi_log: "Ghi nhận KPI tự tạo",
  custom_kpi_entry: "Lượt ghi KPI tự tạo",
  territory: "Khu vực",
  department: "Phòng ban",
  product: "Sản phẩm",
  tenant_settings: "Cài đặt chung",
  forecast: "Dự báo dự trù",
  alert: "Cảnh báo",
  daily_report: "Báo cáo ngày",
  weekly_summary: "Tổng kết tuần",
  inactive_reason: "Lý do đại lý không lấy hàng",
  recompute: "Tính lại số liệu",
  "san-pham": "Nhập sản phẩm",
  "khach-hang": "Nhập khách hàng",
  "don-hang": "Nhập đơn hàng",
};

const FIELD_LABEL: Record<string, string> = {
  orgName: "Tên cơ sở",
  taxCode: "Mã số thuế",
  contactName: "Người liên hệ",
  contactPosition: "Chức vụ người liên hệ",
  contactBirthDate: "Ngày sinh người liên hệ",
  phone: "Điện thoại",
  email: "Email",
  address: "Địa chỉ",
  ward: "Phường/xã",
  channelId: "Kênh",
  territoryId: "Khu vực",
  ownerId: "Người phụ trách",
  note: "Ghi chú",
  receiverName: "Người nhận hàng",
  receiverPhone: "Điện thoại nhận hàng",
  receiverAddress: "Địa chỉ nhận hàng",
  leadNeed: "Nhu cầu tư vấn",
  leadSource: "Nguồn khách",
  fullName: "Họ tên",
  role: "Vai trò",
  jobTitle: "Chức vụ",
  departmentId: "Phòng ban",
  birthDate: "Ngày sinh",
  gender: "Giới tính",
  startDate: "Ngày vào làm",
  title: "Tiêu đề",
  body: "Nội dung",
  description: "Mô tả",
  dueDate: "Hạn",
  eventDate: "Ngày tổ chức",
  expiresAt: "Hết hạn",
  needsRevision: "Cần điều chỉnh",
  revisionNote: "Nội dung yêu cầu điều chỉnh",
  orderDate: "Ngày đặt",
  customerId: "Khách hàng",
  salesUserId: "Nhân viên kinh doanh",
  surchargeAmount: "Phụ thu phí",
  carrierName: "Nhà xe / vận chuyển",
  carrierPhone: "Điện thoại nhà xe",
  carrierAddress: "Địa chỉ nhà xe",
  status: "Trạng thái",
  name: "Tên",
  code: "Mã",
  amount: "Số tiền",
  paidOn: "Ngày thu",
  adjustedQty: "Số điều chỉnh",
  forecastQty: "Số máy tính",
  lines: "Dòng hàng",
  lineCount: "Số dòng hàng",
  totalAmount: "Tổng tiền hàng",
  approvedBy: "Người duyệt",
  previousStatus: "Trạng thái trước",
  kind: "Loại",
  content: "Nội dung",
  approvers: "Chuỗi duyệt",
  createdBy: "Người đề xuất",
  vatRequired: "Cần xuất VAT",
  periodKey: "Kỳ",
  reportDate: "Ngày báo cáo",
  weekStart: "Tuần bắt đầu",
  isActive: "Đang hoạt động",
  targetValue: "Chỉ tiêu",
  actualValue: "Kết quả",
  revenueTarget: "Chỉ tiêu doanh thu",
  orderTarget: "Chỉ tiêu số đơn",
  newCustomerTarget: "Chỉ tiêu khách mới",
  skuTarget: "Chỉ tiêu SKU",
  scopeType: "Cấp chỉ tiêu",
  scopeId: "Đối tượng",
  headUserId: "Trưởng phòng",
  parentId: "Cấp trên",
  reason: "Lý do",
  fileName: "Tên tệp",
  count: "Số lượng",
  guestType: "Loại khách",
  invitedDate: "Ngày mời",
  userId: "Nhân viên",
  eventId: "Sự kiện",
  productId: "Sản phẩm",
  unit: "Đơn vị",
  attended: "Có mặt",
  wantsCme: "Muốn CME",
  wantsCertificate: "Muốn chứng chỉ",
};

/** Khoá nội dung cho biết "cái gì" đã bị tác động — dùng làm dòng tiêu đề của một dòng nhật ký. */
const HEADLINE_KEYS = ["orgName", "name", "title", "fullName", "fileName", "code", "email"];

const UUID_IN_TEXT = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
const MAX_VALUE_LENGTH = 140;
export const AUDIT_PAGE_SIZE = 50;

export type AuditQuery = {
  action: string;
  entity: string;
  userId: string;
  from: string;
  to: string;
  text: string;
  page: number;
};

/** Đọc bộ lọc từ URL — giá trị lạ đều bị bỏ thay vì gây lỗi. */
export function parseAuditQuery(get: (key: string) => string | undefined): AuditQuery {
  const action = get("hanhdong") ?? "";
  const entity = get("doituong") ?? "";
  const userId = get("nguoi") ?? "";
  const from = get("tu") ?? "";
  const to = get("den") ?? "";
  const page = Number(get("trang"));
  return {
    action: action in ACTION_LABEL ? action : "",
    entity: entity in ENTITY_LABEL ? entity : "",
    userId: isUuid(userId) ? userId : "",
    from: isRealDate(from) ? from : "",
    to: isRealDate(to) ? to : "",
    text: (get("q") ?? "").trim().slice(0, 100),
    page: Number.isInteger(page) && page >= 1 && page <= 100_000 ? page : 1,
  };
}

export type AuditChange = { field: string; label: string; from: string; to: string };

export type AuditEntry = {
  id: string;
  createdAt: Date;
  action: string;
  actionLabel: string;
  entityType: string;
  entityLabel: string;
  entityId: string | null;
  userName: string | null;
  userEmail: string | null;
  headline: string;
  details: string[];
  changes: AuditChange[];
  href: string | null;
};

type RawRow = {
  id: string;
  createdAt: Date;
  action: string;
  entityType: string;
  entityId: string | null;
  payload: unknown;
  userName: string | null;
  userEmail: string | null;
};

/** Trang chi tiết của đối tượng còn tồn tại (xoá rồi thì không có link). */
function entityHref(action: string, entityType: string, entityId: string | null): string | null {
  if (!entityId || action === "DELETE" || !isUuid(entityId)) return null;
  if (entityType === "order" || entityType === "payment" || entityType === "order_attachment") return `/don-hang/${entityId}`;
  if (entityType.startsWith("customer")) return `/khach-hang/${entityId}`;
  if (entityType === "user") return `/danh-muc/nguoi-dung/${entityId}`;
  if (entityType === "proposal") return "/cong-viec/de-xuat";
  return null;
}

/** Mọi mã uuid xuất hiện ở bất kỳ đâu trong nội dung (kể cả nằm giữa một câu hay một mảng). */
function collectIds(payload: unknown, into: Set<string>) {
  const text = typeof payload === "string" ? payload : JSON.stringify(payload ?? null);
  for (const match of text.match(UUID_IN_TEXT) ?? []) into.add(match.toLowerCase());
}

/** Mã → tên thật, chỉ trong phạm vi công ty của người xem. */
async function resolveNames(tenantId: string, ids: string[]): Promise<Map<string, string>> {
  const names = new Map<string, string>();
  if (ids.length === 0) return names;
  const [u, t, c, d, cu, p] = await Promise.all([
    db.select({ id: users.id, name: users.fullName }).from(users).where(and(eq(users.tenantId, tenantId), inArray(users.id, ids))),
    db.select({ id: territories.id, name: territories.name }).from(territories).where(and(eq(territories.tenantId, tenantId), inArray(territories.id, ids))),
    db.select({ id: channels.id, name: channels.name }).from(channels).where(and(eq(channels.tenantId, tenantId), inArray(channels.id, ids))),
    db.select({ id: departments.id, name: departments.name }).from(departments).where(and(eq(departments.tenantId, tenantId), inArray(departments.id, ids))),
    db.select({ id: customers.id, name: customers.orgName }).from(customers).where(and(eq(customers.tenantId, tenantId), inArray(customers.id, ids))),
    db.select({ id: products.id, name: products.name }).from(products).where(and(eq(products.tenantId, tenantId), inArray(products.id, ids))),
  ]);
  for (const row of [...u, ...t, ...c, ...d, ...cu, ...p]) names.set(row.id, row.name);
  return names;
}

const STATUS_LABEL: Record<string, string> = {
  DRAFT: "Chờ duyệt",
  PENDING: "Chờ duyệt",
  CONFIRMED: "Đã duyệt",
  APPROVED: "Đã duyệt",
  REJECTED: "Từ chối",
  CANCELLED: "Đã huỷ",
  WITHDRAWN: "Đã thu hồi",
  OPEN: "Chưa xử lý",
  ACKNOWLEDGED: "Đã xử lý",
  RESOLVED: "Hết hiệu lực",
};
const SCOPE_LABEL: Record<string, string> = { COMPANY: "Toàn công ty", DEPARTMENT: "Phòng ban", TERRITORY: "Khu vực", USER: "Nhân viên", CUSTOMER: "Khách hàng" };
const ROLE_LABEL: Record<string, string> = { DIRECTOR: "Ban giám đốc", GDKV: "Giám đốc khu vực", MANAGER: "Quản lý", SALES: "Nhân viên" };

/** Giá trị đưa ra màn hình: mã → tên thật, enum → tiếng Việt, chuỗi dài được cắt gọn. `field` để biết enum nào cần dịch. */
function formatValue(value: unknown, names: Map<string, string>, field = ""): string {
  if (value === null || value === undefined || value === "") return "—";
  if (typeof value === "boolean") return value ? "Có" : "Không";
  if (typeof value === "number") return formatNumber(value);
  const text = typeof value === "string" ? value : Array.isArray(value) ? value.map(String).join("; ") : JSON.stringify(value);
  if (typeof value === "string") {
    if (field === "status" || field === "previousStatus") return STATUS_LABEL[value] ?? value;
    if (field === "role") return ROLE_LABEL[value] ?? value;
    if (field === "scopeType") return SCOPE_LABEL[value] ?? value;
    // Ngày ISO → dd/mm/yyyy như mọi nơi khác trên màn hình.
    const iso = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
    if (iso) return `${iso[3]}/${iso[2]}/${iso[1]}`;
  }
  const resolved = text.replace(UUID_IN_TEXT, (id) => names.get(id.toLowerCase()) ?? "(đã xoá)");
  return resolved.length > MAX_VALUE_LENGTH ? `${resolved.slice(0, MAX_VALUE_LENGTH)}…` : resolved;
}

function toEntry(row: RawRow, names: Map<string, string>): AuditEntry {
  const payload = row.payload && typeof row.payload === "object" && !Array.isArray(row.payload) ? (row.payload as Record<string, unknown>) : {};

  const headlineKey = HEADLINE_KEYS.find((key) => typeof payload[key] === "string" && payload[key] !== "");
  const headline = headlineKey ? formatValue(payload[headlineKey], names, headlineKey) : "";

  const details = Object.entries(payload)
    .filter(([key, value]) => key !== "changes" && key !== headlineKey && value !== null && value !== undefined && value !== "" && !(typeof value === "object" && Object.keys(value as object).length === 0))
    .slice(0, 6)
    .map(([key, value]) => `${FIELD_LABEL[key] ?? key}: ${formatValue(value, names, key)}`);

  const rawChanges = payload.changes && typeof payload.changes === "object" ? (payload.changes as Record<string, { from?: unknown; to?: unknown }>) : {};
  const changes = Object.entries(rawChanges).map(([field, change]) => ({
    field,
    label: FIELD_LABEL[field] ?? field,
    from: formatValue(change?.from, names, field),
    to: formatValue(change?.to, names, field),
  }));

  return {
    id: row.id,
    createdAt: row.createdAt,
    action: row.action,
    actionLabel: ACTION_LABEL[row.action] ?? row.action,
    entityType: row.entityType,
    entityLabel: ENTITY_LABEL[row.entityType] ?? row.entityType,
    entityId: row.entityId,
    userName: row.userName,
    userEmail: row.userEmail,
    headline,
    details,
    changes,
    href: entityHref(row.action, row.entityType, row.entityId),
  };
}

async function present(tenantId: string, rows: RawRow[]): Promise<AuditEntry[]> {
  const ids = new Set<string>();
  for (const row of rows) collectIds(row.payload, ids);
  const names = await resolveNames(tenantId, [...ids]);
  return rows.map((row) => toEntry(row, names));
}

const rowColumns = {
  id: auditLogs.id,
  action: auditLogs.action,
  entityType: auditLogs.entityType,
  entityId: auditLogs.entityId,
  payload: auditLogs.payload,
  createdAt: auditLogs.createdAt,
  userName: users.fullName,
  userEmail: users.email,
};

function likePattern(text: string): string {
  return `%${text.replace(/[\\%_]/g, (ch) => `\\${ch}`)}%`;
}

function auditFilters(tenantId: string, query: AuditQuery): SQL {
  const conditions: SQL[] = [eq(auditLogs.tenantId, tenantId)];
  if (query.action) conditions.push(eq(auditLogs.action, query.action));
  if (query.entity) conditions.push(eq(auditLogs.entityType, query.entity));
  if (query.userId) conditions.push(eq(auditLogs.userId, query.userId));
  // Ngày theo giờ Việt Nam: thao tác lúc 01:00 sáng 27/09 phải nằm trong ngày 27/09, không phải 26/09.
  if (query.from) conditions.push(gte(vnDate(auditLogs.createdAt), sql`${query.from}::date`));
  if (query.to) conditions.push(lte(vnDate(auditLogs.createdAt), sql`${query.to}::date`));
  if (query.text) {
    const pattern = likePattern(query.text);
    conditions.push(sql`(${auditLogs.payload}::text ilike ${pattern} or ${auditLogs.entityId} ilike ${pattern})`);
  }
  return and(...conditions) as SQL;
}

export async function loadAuditPage(tenantId: string, query: AuditQuery) {
  const where = auditFilters(tenantId, query);
  const [{ n }] = await db.select({ n: sql<number>`count(*)::int` }).from(auditLogs).where(where);
  const total = Number(n);
  const totalPages = Math.max(1, Math.ceil(total / AUDIT_PAGE_SIZE));
  const page = Math.min(query.page, totalPages);

  const rows = await db
    .select(rowColumns)
    .from(auditLogs)
    .leftJoin(users, eq(auditLogs.userId, users.id))
    .where(where)
    .orderBy(desc(auditLogs.createdAt), desc(auditLogs.id))
    .limit(AUDIT_PAGE_SIZE)
    .offset((page - 1) * AUDIT_PAGE_SIZE);

  return { entries: await present(tenantId, rows), total, page, totalPages };
}

/** Toàn bộ nhật ký khớp bộ lọc, tối đa `limit` dòng — dùng để xuất Excel. */
export async function loadAuditForExport(tenantId: string, query: AuditQuery, limit = 5000): Promise<AuditEntry[]> {
  const rows = await db
    .select(rowColumns)
    .from(auditLogs)
    .leftJoin(users, eq(auditLogs.userId, users.id))
    .where(auditFilters(tenantId, query))
    .orderBy(desc(auditLogs.createdAt), desc(auditLogs.id))
    .limit(limit);
  return present(tenantId, rows);
}

/** Lịch sử chỉnh sửa của MỘT bản ghi (theo mã và các loại đối tượng con của nó), mới nhất trước. */
export async function loadEntityHistory(tenantId: string, entityId: string, entityTypes: string[], limit = 100): Promise<AuditEntry[]> {
  if (!isUuid(entityId)) return [];
  const rows = await db
    .select(rowColumns)
    .from(auditLogs)
    .leftJoin(users, eq(auditLogs.userId, users.id))
    .where(and(eq(auditLogs.tenantId, tenantId), eq(auditLogs.entityId, entityId), inArray(auditLogs.entityType, entityTypes)))
    .orderBy(desc(auditLogs.createdAt), desc(auditLogs.id))
    .limit(limit);
  return present(tenantId, rows);
}

/** Lịch sử chỉnh sửa của NHIỀU bản ghi cùng lúc (một truy vấn), gom theo mã bản ghi — dùng cho danh sách phiếu. */
export async function loadHistoryByEntity(tenantId: string, entityIds: string[], entityTypes: string[], perEntity = 30): Promise<Record<string, AuditEntry[]>> {
  const ids = entityIds.filter(isUuid);
  if (ids.length === 0) return {};
  const rows = await db
    .select(rowColumns)
    .from(auditLogs)
    .leftJoin(users, eq(auditLogs.userId, users.id))
    .where(and(eq(auditLogs.tenantId, tenantId), inArray(auditLogs.entityId, ids), inArray(auditLogs.entityType, entityTypes)))
    .orderBy(desc(auditLogs.createdAt), desc(auditLogs.id))
    .limit(ids.length * perEntity);
  const entries = await present(tenantId, rows);
  const grouped: Record<string, AuditEntry[]> = {};
  for (const entry of entries) {
    if (!entry.entityId) continue;
    const list = grouped[entry.entityId] ?? [];
    if (list.length < perEntity) list.push(entry);
    grouped[entry.entityId] = list;
  }
  return grouped;
}

/** Danh sách người dùng để chọn ở bộ lọc "Người thực hiện". */
export async function auditActors(tenantId: string) {
  return db
    .select({ id: users.id, name: users.fullName })
    .from(users)
    .where(eq(users.tenantId, tenantId))
    .orderBy(users.fullName)
    .limit(1000);
}
