/**
 * Nhập dữ liệu từ Excel.
 *
 * Ba loại nhập: sản phẩm, khách hàng, đơn hàng lịch sử. Tất cả đi qua cùng một
 * khuôn: đọc file → kiểm tra từng dòng → trả về danh sách lỗi kèm số dòng → chỉ
 * ghi vào cơ sở dữ liệu khi người dùng bấm xác nhận.
 *
 * Nguyên tắc: **không ghi một phần**. File có lỗi thì không dòng nào được ghi.
 * Nhập nửa vời để lại dữ liệu rác mà người dùng không biết đường nào lần, còn tệ
 * hơn là báo lỗi và bắt sửa file.
 */
// Cố ý không đánh dấu 'server-only': module này còn được dùng từ script chạy bằng
// Node (nhập hàng loạt từ dòng lệnh, kiểm thử), mà guard đó chặn luôn cả trường
// hợp hợp lệ. Việc lọt vào bundle phía trình duyệt đã bị chặn từ trước, vì nó
// kéo theo tầng cơ sở dữ liệu.
import { and, eq, inArray } from "drizzle-orm";
import { db } from "@/db";
import {
  products,
  productGroups,
  technologies,
  customers,
  customerStageHistory,
  channels,
  territories,
  users,
  orders,
  orderLines,
} from "@/db/schema";
import { parseSheet } from "./excel";
import { isRealDate, today } from "./period";

export type ImportKind = "san-pham" | "khach-hang" | "don-hang";

export type RowError = { row: number; message: string };

export type ImportPreview = {
  kind: ImportKind;
  totalRows: number;
  validRows: number;
  errors: RowError[];
  /** Vài dòng đầu để người dùng đối chiếu trước khi xác nhận. */
  sample: Record<string, string>[];
};

export type ImportResult = { inserted: number; updated: number; errors: RowError[] };

/** Cột bắt buộc của từng loại — hiển thị trên màn hình để người dùng chuẩn bị file. */
export const REQUIRED_COLUMNS: Record<ImportKind, string[]> = {
  "san-pham": ["Mã SP", "Tên sản phẩm", "Đơn giá"],
  "khach-hang": ["Tên đơn vị"],
  "don-hang": ["Mã đơn", "Ngày", "Mã KH", "Mã SP", "SL bán"],
};

export const OPTIONAL_COLUMNS: Record<ImportKind, string[]> = {
  "san-pham": ["Nhóm", "Công nghệ", "ĐVT", "Dung tích / Trọng lượng", "Vai trò", "Giá bán lẻ", "Chiết khấu (%)", "Ghi chú"],
  "khach-hang": [
    "Mã KH",
    "Người liên hệ",
    "Điện thoại",
    "Email",
    "Địa chỉ",
    "Kênh",
    "Khu vực",
    "NVKD phụ trách",
    "Trạng thái",
    "Chức vụ",
    "Phường / Xã",
    "Người nhận hàng",
    "SĐT người nhận",
    "Chành xe / địa chỉ nhận",
    "Nhu cầu tư vấn",
    "Nguồn khách",
    "Ngày thêm mới",
  ],
  "don-hang": ["SL KM", "Đơn giá", "Ghi chú"],
};

/* -------------------------------------------------------------------------- */
/* Tiện ích ép kiểu                                                           */
/* -------------------------------------------------------------------------- */

/**
 * Đọc số từ ô Excel: chấp nhận "1.200.000", "1,200,000", "1200000 đ".
 *
 * Trả về null khi ô không chứa số. Lưu ý: sau khi lọc bỏ ký tự không phải số,
 * chuỗi rỗng phải bị coi là không hợp lệ — `Number("")` trả về 0, nên nếu không
 * kiểm tra, một ô ghi nhầm thành "abc" sẽ lặng lẽ biến thành đơn giá 0 đồng và
 * làm sai toàn bộ doanh thu về sau.
 */
function toNumber(value: string | undefined): number | null {
  if (!value) return null;
  const cleaned = value.replace(/[^\d,.-]/g, "").replace(/\.(?=\d{3}\b)/g, "").replace(/,/g, "");
  if (!/^-?\d+(\.\d+)?$/.test(cleaned)) return null;
  const parsed = Number(cleaned);
  return Number.isFinite(parsed) ? parsed : null;
}

/** Đọc ngày: chấp nhận 2026-08-10, 10/08/2026, 10-8-2026. */
function toDate(value: string | undefined): string | null {
  if (!value) return null;
  const iso = value.match(/^(\d{4})-(\d{2})-(\d{2})/);
  let result: string | null = iso ? `${iso[1]}-${iso[2]}-${iso[3]}` : null;

  const vn = value.match(/^(\d{1,2})[/-](\d{1,2})[/-](\d{4})/);
  if (!result && vn) {
    const [, d, m, y] = vn;
    result = `${y}-${m.padStart(2, "0")}-${d.padStart(2, "0")}`;
  }
  // 31/02, tháng 13... đọc được nhưng không có thật: coi như không đọc được để báo lỗi đúng dòng thay vì làm hỏng lúc ghi.
  return result && isRealDate(result) ? result : null;
}

const STAGE_FROM_TEXT: Record<string, "LEAD" | "PROSPECT" | "DEALER"> = {
  se: "LEAD",
  sẽ: "LEAD",
  lead: "LEAD",
  dang: "PROSPECT",
  đang: "PROSPECT",
  prospect: "PROSPECT",
  "đại lý": "DEALER",
  "dai ly": "DEALER",
  dl: "DEALER",
  dealer: "DEALER",
};

const ROLE_FROM_TEXT: Record<string, "CORE" | "POTENTIAL" | "RECOVERY" | "NORMAL"> = {
  "chủ lực": "CORE",
  "chu luc": "CORE",
  core: "CORE",
  "tiềm năng": "POTENTIAL",
  "tiem nang": "POTENTIAL",
  "khôi phục": "RECOVERY",
  "khoi phuc": "RECOVERY",
};

function normalize(value: string): string {
  return value.trim().toLowerCase();
}

/* -------------------------------------------------------------------------- */
/* Kiểm tra file trước khi ghi                                                */
/* -------------------------------------------------------------------------- */

export async function previewImport(
  tenantId: string,
  kind: ImportKind,
  buffer: ArrayBuffer,
): Promise<ImportPreview> {
  const { headers, rows } = await parseSheet(buffer);

  const missing = REQUIRED_COLUMNS[kind].filter((column) => !headers.includes(column));
  if (missing.length > 0) {
    return {
      kind,
      totalRows: rows.length,
      validRows: 0,
      errors: [{ row: 1, message: `File thiếu cột bắt buộc: ${missing.join(", ")}.` }],
      sample: rows.slice(0, 5),
    };
  }

  const errors = await validateRows(tenantId, kind, rows);

  return {
    kind,
    totalRows: rows.length,
    validRows: rows.length - new Set(errors.map((e) => e.row)).size,
    errors: errors.slice(0, 50),
    sample: rows.slice(0, 5),
  };
}

async function validateRows(
  tenantId: string,
  kind: ImportKind,
  rows: Record<string, string>[],
): Promise<RowError[]> {
  const errors: RowError[] = [];
  // Dòng 1 là tiêu đề, nên dòng dữ liệu đầu tiên là dòng 2 trong file Excel.
  const rowNumber = (index: number) => index + 2;

  if (kind === "san-pham") {
    const seen = new Set<string>();
    rows.forEach((row, index) => {
      const code = row["Mã SP"]?.trim();
      if (!code) errors.push({ row: rowNumber(index), message: "Thiếu mã sản phẩm." });
      else if (seen.has(code))
        errors.push({ row: rowNumber(index), message: `Mã sản phẩm ${code} bị trùng trong file.` });
      else seen.add(code);

      if (!row["Tên sản phẩm"]?.trim())
        errors.push({ row: rowNumber(index), message: "Thiếu tên sản phẩm." });

      if (toNumber(row["Đơn giá"]) == null)
        errors.push({ row: rowNumber(index), message: "Đơn giá không đọc được." });
    });
    return errors;
  }

  if (kind === "khach-hang") {
    const [channelRows, territoryRows, userRows, existingRows] = await Promise.all([
      db.select({ name: channels.name }).from(channels).where(eq(channels.tenantId, tenantId)),
      db.select({ name: territories.name }).from(territories).where(eq(territories.tenantId, tenantId)),
      db.select({ name: users.fullName }).from(users).where(and(eq(users.tenantId, tenantId), eq(users.isActive, true))),
      db.select({ code: customers.code, phone: customers.phone, taxCode: customers.taxCode }).from(customers).where(eq(customers.tenantId, tenantId)),
    ]);
    const channelNames = new Set(channelRows.map((c) => normalize(c.name)));
    const territoryNames = new Set(territoryRows.map((t) => normalize(t.name)));
    const userNames = new Set(userRows.map((u) => normalize(u.name)));
    const existingCodes = new Set(existingRows.map((c) => c.code));
    const phoneOwner = new Map(existingRows.filter((c) => c.phone).map((c) => [c.phone as string, c.code]));

    const seen = new Set<string>();
    const seenPhones = new Map<string, number>();
    rows.forEach((row, index) => {
      const line = rowNumber(index);
      const stageForName = STAGE_FROM_TEXT[normalize(row["Trạng thái"] ?? "")] ?? "LEAD";
      // Khách tiềm năng từ Marketing có thể chưa có tên cơ sở: lấy tên người liên hệ. Từ "Đang" trở đi phải có tên cơ sở.
      if (!row["Tên đơn vị"]?.trim() && (stageForName !== "LEAD" || !row["Người liên hệ"]?.trim())) {
        errors.push({ row: line, message: stageForName === "LEAD" ? "Thiếu tên đơn vị hoặc người liên hệ." : "Thiếu tên đơn vị." });
      }
      const addedText = row["Ngày thêm mới"]?.trim();
      if (addedText) {
        const added = toDate(addedText);
        if (!added) errors.push({ row: line, message: `Ngày thêm mới "${addedText}" không đọc được (dùng dd/mm/yyyy).` });
        else if (added > today()) errors.push({ row: line, message: "Ngày thêm mới không được ở tương lai." });
      }

      const code = row["Mã KH"]?.trim();
      if (code) {
        if (seen.has(code)) errors.push({ row: line, message: `Mã khách hàng ${code} bị trùng trong file.` });
        else seen.add(code);
      }

      const stageText = row["Trạng thái"]?.trim();
      if (stageText && !STAGE_FROM_TEXT[normalize(stageText)]) {
        errors.push({ row: line, message: `Trạng thái "${stageText}" không hợp lệ. Dùng: Sẽ / Đang / Đại lý.` });
      }
      const stage = STAGE_FROM_TEXT[normalize(stageText ?? "")] ?? "LEAD";

      // Tên kênh / khu vực / NVKD gõ sai phải báo lỗi — nếu lặng lẽ bỏ qua, khách không có người phụ trách sẽ biến mất khỏi mọi danh sách (trừ Ban giám đốc).
      const channel = row["Kênh"]?.trim();
      if (channel && !channelNames.has(normalize(channel))) errors.push({ row: line, message: `Kênh "${channel}" không có trong danh mục.` });
      const territory = row["Khu vực"]?.trim();
      if (territory && !territoryNames.has(normalize(territory))) errors.push({ row: line, message: `Khu vực "${territory}" không có trong danh mục.` });
      const owner = row["NVKD phụ trách"]?.trim();
      if (owner && !userNames.has(normalize(owner))) errors.push({ row: line, message: `NVKD "${owner}" không có trong danh sách nhân viên đang hoạt động.` });

      const phone = row["Điện thoại"]?.trim();
      if (phone) {
        if (!/^[0-9+\s.-]{8,15}$/.test(phone)) errors.push({ row: line, message: `Số điện thoại "${phone}" không hợp lệ.` });
        else {
          const earlier = seenPhones.get(phone);
          if (earlier) errors.push({ row: line, message: `Số điện thoại ${phone} trùng với dòng ${earlier} trong file.` });
          else seenPhones.set(phone, line);
          const holder = phoneOwner.get(phone);
          if (holder && holder !== code) errors.push({ row: line, message: `Số điện thoại ${phone} đã thuộc khách ${holder}.` });
        }
      }

      // Khách MỚI phải đủ thông tin bắt buộc như khi nhập tay; dòng cập nhật khách cũ được để trống ô = giữ nguyên.
      const isNew = !code || !existingCodes.has(code);
      if (isNew) {
        if (!channel) errors.push({ row: line, message: "Thiếu loại khách (cột Kênh)." });
        if (!row["Người liên hệ"]?.trim()) errors.push({ row: line, message: "Thiếu người liên hệ chính." });
        if (!phone) errors.push({ row: line, message: "Thiếu điện thoại." });
        if (!territory) errors.push({ row: line, message: "Thiếu khu vực (tỉnh / thành)." });
        if (stage !== "LEAD") {
          if (!row["Phường / Xã"]?.trim()) errors.push({ row: line, message: "Khách trạng thái Đang/Đại lý phải có Phường / Xã." });
          if (!row["Địa chỉ"]?.trim()) errors.push({ row: line, message: "Khách trạng thái Đang/Đại lý phải có Địa chỉ." });
        }
      }
    });
    return errors;
  }

  // Đơn hàng: phải đối chiếu mã khách và mã sản phẩm với danh mục đang có.
  const customerCodes = [...new Set(rows.map((row) => row["Mã KH"]?.trim()).filter(Boolean))] as string[];
  const productCodes = [...new Set(rows.map((row) => row["Mã SP"]?.trim()).filter(Boolean))] as string[];

  const [knownCustomers, knownProducts] = await Promise.all([
    customerCodes.length > 0
      ? db
          .select({ code: customers.code })
          .from(customers)
          .where(and(eq(customers.tenantId, tenantId), inArray(customers.code, customerCodes)))
      : Promise.resolve([]),
    productCodes.length > 0
      ? db
          .select({ code: products.code })
          .from(products)
          .where(and(eq(products.tenantId, tenantId), inArray(products.code, productCodes)))
      : Promise.resolve([]),
  ]);

  const customerSet = new Set(knownCustomers.map((c) => c.code));
  const productSet = new Set(knownProducts.map((p) => p.code));

  rows.forEach((row, index) => {
    const line = rowNumber(index);
    if (!row["Mã đơn"]?.trim()) errors.push({ row: line, message: "Thiếu mã đơn." });

    if (!toDate(row["Ngày"]))
      errors.push({ row: line, message: "Ngày không đọc được (dùng dd/mm/yyyy hoặc yyyy-mm-dd)." });

    const customerCode = row["Mã KH"]?.trim();
    if (!customerCode) errors.push({ row: line, message: "Thiếu mã khách hàng." });
    else if (!customerSet.has(customerCode))
      errors.push({ row: line, message: `Không tìm thấy khách hàng ${customerCode} trong hệ thống.` });

    const productCode = row["Mã SP"]?.trim();
    if (!productCode) errors.push({ row: line, message: "Thiếu mã sản phẩm." });
    else if (!productSet.has(productCode))
      errors.push({ row: line, message: `Không tìm thấy sản phẩm ${productCode} trong danh mục.` });

    const qty = toNumber(row["SL bán"]);
    if (qty == null || qty < 0) errors.push({ row: line, message: "Số lượng bán không hợp lệ." });
  });

  return errors;
}

/* -------------------------------------------------------------------------- */
/* Ghi dữ liệu                                                                */
/* -------------------------------------------------------------------------- */

export async function runImport(
  tenantId: string,
  userId: string,
  kind: ImportKind,
  buffer: ArrayBuffer,
): Promise<ImportResult> {
  const { rows } = await parseSheet(buffer);
  const errors = await validateRows(tenantId, kind, rows);

  // Không ghi một phần: file còn lỗi thì dừng lại toàn bộ.
  if (errors.length > 0) return { inserted: 0, updated: 0, errors: errors.slice(0, 50) };

  if (kind === "san-pham") return importProducts(tenantId, rows);
  if (kind === "khach-hang") return importCustomers(tenantId, userId, rows);
  return importOrders(tenantId, userId, rows);
}

async function importProducts(
  tenantId: string,
  rows: Record<string, string>[],
): Promise<ImportResult> {
  const [existingGroups, existingTech, existingProducts] = await Promise.all([
    db.select().from(productGroups).where(eq(productGroups.tenantId, tenantId)),
    db.select().from(technologies).where(eq(technologies.tenantId, tenantId)),
    db.select({ id: products.id, code: products.code }).from(products).where(eq(products.tenantId, tenantId)),
  ]);

  const groupByName = new Map(existingGroups.map((g) => [normalize(g.name), g.id]));
  const techByName = new Map(existingTech.map((t) => [normalize(t.name), t.id]));
  const productByCode = new Map(existingProducts.map((p) => [p.code, p.id]));

  /** Nhóm và công nghệ chưa có trong danh mục thì tạo mới, không bắt khai báo trước. */
  const ensureGroup = async (name: string | undefined): Promise<string | null> => {
    if (!name?.trim()) return null;
    const key = normalize(name);
    if (groupByName.has(key)) return groupByName.get(key)!;
    const [created] = await db
      .insert(productGroups)
      .values({ tenantId, code: slugCode(name), name: name.trim() })
      .returning();
    groupByName.set(key, created.id);
    return created.id;
  };

  const ensureTech = async (name: string | undefined): Promise<string | null> => {
    if (!name?.trim()) return null;
    const key = normalize(name);
    if (techByName.has(key)) return techByName.get(key)!;
    const [created] = await db
      .insert(technologies)
      .values({ tenantId, code: slugCode(name), name: name.trim() })
      .returning();
    techByName.set(key, created.id);
    return created.id;
  };

  let inserted = 0;
  let updated = 0;

  for (const row of rows) {
    const code = row["Mã SP"].trim();
    const values = {
      tenantId,
      code,
      name: row["Tên sản phẩm"].trim(),
      groupId: await ensureGroup(row["Nhóm"]),
      technologyId: await ensureTech(row["Công nghệ"]),
      unit: row["ĐVT"]?.trim() || "Hộp",
      unitPrice: toNumber(row["Đơn giá"]) ?? 0,
      retailPrice: toNumber(row["Giá bán lẻ"]) ?? 0,
      discountPercent: toNumber(row["Chiết khấu (%)"]) ?? 0,
      role: ROLE_FROM_TEXT[normalize(row["Vai trò"] ?? "")] ?? ("NORMAL" as const),
      // Cột tuỳ chọn mới: chỉ ghi khi file có cột đó — nhập lại file cũ không được xoá mất dữ liệu đã có.
      ...(row["Dung tích / Trọng lượng"] !== undefined ? { volume: row["Dung tích / Trọng lượng"].trim() || null } : {}),
      ...(row["Ghi chú"] !== undefined ? { note: row["Ghi chú"].trim() || null } : {}),
    };

    const existingId = productByCode.get(code);
    if (existingId) {
      await db.update(products).set(values).where(eq(products.id, existingId));
      updated += 1;
    } else {
      await db.insert(products).values(values);
      inserted += 1;
    }
  }

  return { inserted, updated, errors: [] };
}

/** Trả về `{ [field]: giá trị }` nếu file có cột đó, ngược lại trả `{}` (không đụng tới trường này). */
function optionalText<K extends string>(
  row: Record<string, string>,
  column: string,
  field: K,
  /** Cập nhật khách đã có: ô để trống nghĩa là giữ nguyên, không xoá dữ liệu đang có. */
  skipBlank = false,
): Partial<Record<K, string | null>> {
  if (row[column] === undefined) return {};
  const value = row[column].trim();
  if (!value && skipBlank) return {};
  return { [field]: value || null } as Partial<Record<K, string | null>>;
}

async function importCustomers(
  tenantId: string,
  userId: string,
  rows: Record<string, string>[],
): Promise<ImportResult> {
  const [channelRows, territoryRows, userRows, existing] = await Promise.all([
    db.select().from(channels).where(eq(channels.tenantId, tenantId)),
    db.select().from(territories).where(eq(territories.tenantId, tenantId)),
    db.select({ id: users.id, fullName: users.fullName }).from(users).where(and(eq(users.tenantId, tenantId), eq(users.isActive, true))),
    db.select({ id: customers.id, code: customers.code, stage: customers.stage, dealerSince: customers.dealerSince }).from(customers).where(eq(customers.tenantId, tenantId)),
  ]);

  const channelByName = new Map(channelRows.map((c) => [normalize(c.name), c.id]));
  const territoryByName = new Map(territoryRows.map((t) => [normalize(t.name), t.id]));
  const userByName = new Map(userRows.map((u) => [normalize(u.fullName), u.id]));
  const byCode = new Map(existing.map((c) => [c.code, c.id]));
  const infoByCode = new Map(existing.map((c) => [c.code, c]));

  let maxSeq = existing.reduce((max, row) => {
    const match = row.code.match(/^KH(\d+)$/);
    return match ? Math.max(max, Number(match[1])) : max;
  }, 0);

  let inserted = 0;
  let updated = 0;

  for (const row of rows) {
    const stageCell = row["Trạng thái"]?.trim();
    const stage = STAGE_FROM_TEXT[normalize(stageCell ?? "")] ?? ("LEAD" as const);
    const code = row["Mã KH"]?.trim() || `KH${String(++maxSeq).padStart(4, "0")}`;
    const existingId = byCode.get(code);
    const previous = infoByCode.get(code);
    // Cập nhật khách đã có: ô để trống = giữ nguyên (undefined bị bỏ qua khi ghi), tránh nhập lại file làm mất người phụ trách,
    // đưa đại lý về "Sẽ", hay xoá số điện thoại.
    const keep = <T,>(value: T | null): T | null | undefined => (existingId ? (value ?? undefined) : value);

    const values = {
      tenantId,
      code,
      // Khách đã có mà ô tên để trống: giữ tên cũ (không ghi đè bằng tên người liên hệ).
      orgName: existingId && !row["Tên đơn vị"]?.trim() ? undefined : row["Tên đơn vị"]?.trim() || row["Người liên hệ"]?.trim() || "",
      contactName: keep(row["Người liên hệ"]?.trim() || null),
      phone: keep(row["Điện thoại"]?.trim() || null),
      email: keep(row["Email"]?.trim() || null),
      address: keep(row["Địa chỉ"]?.trim() || null),
      channelId: keep(channelByName.get(normalize(row["Kênh"] ?? "")) ?? null),
      territoryId: keep(territoryByName.get(normalize(row["Khu vực"] ?? "")) ?? null),
      ownerId: keep(userByName.get(normalize(row["NVKD phụ trách"] ?? "")) ?? null),
      // Ngày Marketing thu được khách (chỉ áp cho khách mới; khách đã có giữ nguyên ngày tạo).
      createdAt: !existingId && toDate(row["Ngày thêm mới"]) ? new Date(`${toDate(row["Ngày thêm mới"])}T08:00:00+07:00`) : undefined,
      stage: existingId && !stageCell ? undefined : stage,
      dealerSince: !existingId && stage === "DEALER" ? today() : previous && stage === "DEALER" && !previous.dealerSince && stageCell ? today() : undefined,
      // Các cột hồ sơ mở rộng: chỉ ghi khi file có cột đó, để nhập lại file cũ không xoá dữ liệu đã có.
      ...optionalText(row, "Chức vụ", "contactPosition", !!existingId),
      ...optionalText(row, "Phường / Xã", "ward", !!existingId),
      ...optionalText(row, "Người nhận hàng", "receiverName", !!existingId),
      ...optionalText(row, "SĐT người nhận", "receiverPhone", !!existingId),
      ...optionalText(row, "Chành xe / địa chỉ nhận", "receiverAddress", !!existingId),
      ...optionalText(row, "Nhu cầu tư vấn", "leadNeed", !!existingId),
      ...optionalText(row, "Nguồn khách", "leadSource", !!existingId),
    };

    if (existingId) {
      await db.update(customers).set(values).where(eq(customers.id, existingId));
      if (stageCell && previous && previous.stage !== stage) {
        await db.insert(customerStageHistory).values({
          tenantId,
          customerId: existingId,
          fromStage: previous.stage,
          toStage: stage,
          changedBy: userId,
          note: "Cập nhật từ file Excel",
        });
      }
      updated += 1;
    } else {
      const [created] = await db.insert(customers).values({ ...values, orgName: values.orgName ?? "" }).returning();
      await db.insert(customerStageHistory).values({
        tenantId,
        customerId: created.id,
        toStage: stage,
        changedBy: userId,
        note: "Nhập từ file Excel",
      });
      byCode.set(code, created.id);
      infoByCode.set(code, { id: created.id, code, stage, dealerSince: created.dealerSince });
      inserted += 1;
    }
  }

  return { inserted, updated, errors: [] };
}

/**
 * Đơn hàng lịch sử: mỗi dòng trong file là một dòng hàng. Các dòng cùng "Mã đơn"
 * được gom lại thành một đơn — đúng cách đội kinh doanh vẫn ghi trên Excel.
 */
async function importOrders(
  tenantId: string,
  userId: string,
  rows: Record<string, string>[],
): Promise<ImportResult> {
  const [customerRows, productRows, existingOrders] = await Promise.all([
    db
      .select({
        id: customers.id,
        code: customers.code,
        ownerId: customers.ownerId,
        territoryId: customers.territoryId,
        channelId: customers.channelId,
      })
      .from(customers)
      .where(eq(customers.tenantId, tenantId)),
    db
      .select({ id: products.id, code: products.code, unitPrice: products.unitPrice })
      .from(products)
      .where(eq(products.tenantId, tenantId)),
    db.select({ code: orders.code }).from(orders).where(eq(orders.tenantId, tenantId)),
  ]);

  const customerByCode = new Map(customerRows.map((c) => [c.code, c]));
  const productByCode = new Map(productRows.map((p) => [p.code, p]));
  const existingCodes = new Set(existingOrders.map((o) => o.code));

  const grouped = new Map<string, Record<string, string>[]>();
  for (const row of rows) {
    const code = row["Mã đơn"].trim();
    const list = grouped.get(code) ?? [];
    list.push(row);
    grouped.set(code, list);
  }

  let inserted = 0;
  let skipped = 0;

  for (const [code, lines] of grouped) {
    // Mã đơn đã tồn tại thì bỏ qua, để nhập lại cùng file không nhân đôi doanh thu.
    if (existingCodes.has(code)) {
      skipped += 1;
      continue;
    }

    const first = lines[0];
    const customer = customerByCode.get(first["Mã KH"].trim())!;
    const orderDate = toDate(first["Ngày"])!;
    const orderId = crypto.randomUUID();

    let totalAmount = 0;
    let promoValue = 0;

    const lineValues = lines.map((line) => {
      const product = productByCode.get(line["Mã SP"].trim())!;
      const qtySold = toNumber(line["SL bán"]) ?? 0;
      const qtyPromo = toNumber(line["SL KM"]) ?? 0;
      const unitPrice = toNumber(line["Đơn giá"]) ?? Number(product.unitPrice);
      const amount = qtySold * unitPrice;
      totalAmount += amount;
      promoValue += qtyPromo * unitPrice;

      return {
        tenantId,
        orderId,
        productId: product.id,
        qtySold,
        qtyPromo,
        unitPrice,
        amount,
        orderDate,
      };
    });

    await db.insert(orders).values({
      id: orderId,
      tenantId,
      code,
      customerId: customer.id,
      orderDate,
      salesUserId: customer.ownerId,
      territoryId: customer.territoryId,
      channelId: customer.channelId,
      status: "CONFIRMED",
      totalAmount,
      promoValue,
      note: first["Ghi chú"]?.trim() || null,
      createdBy: userId,
    });

    await db.insert(orderLines).values(lineValues);
    inserted += 1;
  }

  return {
    inserted,
    updated: 0,
    errors:
      skipped > 0
        ? [{ row: 0, message: `${skipped} đơn đã có sẵn trong hệ thống nên được bỏ qua, không nhập lại.` }]
        : [],
  };
}

/** Sinh mã ngắn từ tên tiếng Việt để tạo nhóm/công nghệ mới khi nhập. */
function slugCode(name: string): string {
  const base = name
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/đ/gi, "d")
    .replace(/[^a-zA-Z0-9]+/g, "")
    .toUpperCase()
    .slice(0, 10);
  return base || `NHOM${Date.now().toString().slice(-5)}`;
}
