/**
 * Nhập Excel hàng loạt (`src/lib/import.ts`) — đóng test case "Chưa test lại" (dòng 91,
 * sheet "Bộ test case"): các cột tuỳ chọn mới (Dung tích/Trọng lượng, Ghi chú cho sản
 * phẩm; Chức vụ, Phường/Xã, Người nhận hàng, Nhu cầu tư vấn, Nguồn khách cho khách hàng)
 * và quy tắc "nhập lại file cũ không xoá dữ liệu đã có".
 *
 * Dựng file .xlsx THẬT bằng ExcelJS (đúng thư viện production dùng để đọc), không mock
 * `parseSheet` — để bắt đúng lớp lỗi hay gặp với nhập Excel: sai tên cột, ô trống bị hiểu
 * nhầm, kiểu dữ liệu đọc sai.
 *
 * Tự rắc thêm 1 sản phẩm + 1 khách hàng riêng cho file này (không sửa `seedMinimalFixture()`
 * dùng chung) — `scope-leak.test.ts` giả định đúng 2 khách có sẵn trong tenant A, thêm khách
 * vào fixture chung sẽ làm sai số đếm ở đó.
 */
import { beforeAll, describe, expect, it } from "vitest";
import ExcelJS from "exceljs";
import { eq } from "drizzle-orm";
import { db } from "@/db";
import { products, customers } from "@/db/schema";
import { previewImport, runImport } from "@/lib/import";
import { seedMinimalFixture, type Fixture } from "../helpers/db";

let fx: Fixture;
const productCode = "SPCU01";
const customerCode = "KHCU01";

/** Sinh buffer .xlsx thật từ bảng [tiêu đề, ...dòng] — đúng khuôn `parseSheet` mong đợi (tiêu đề ở dòng 1). */
async function buildXlsx(headers: string[], rows: (string | number)[][]): Promise<ArrayBuffer> {
  const wb = new ExcelJS.Workbook();
  const sheet = wb.addWorksheet("Sheet1");
  sheet.addRow(headers);
  for (const row of rows) sheet.addRow(row);
  const buf = Buffer.from(await wb.xlsx.writeBuffer());
  const copy = new ArrayBuffer(buf.byteLength);
  new Uint8Array(copy).set(buf);
  return copy;
}

beforeAll(async () => {
  fx = await seedMinimalFixture();

  await db.insert(products).values({
    tenantId: fx.tenantA,
    code: productCode,
    name: "Kem dưỡng ẩm đang có",
    unitPrice: 180_000,
    volume: "100ml gốc",
    note: "Ghi chú gốc",
  });

  await db.insert(customers).values({
    tenantId: fx.tenantA,
    code: customerCode,
    orgName: "Phòng khám đang có",
    stage: "DEALER",
    ownerId: fx.salesBac,
    territoryId: fx.hanoi,
    channelId: fx.channel,
    contactPosition: "Giám đốc phòng khám",
    ward: "Phường Bến Nghé",
    receiverName: "Anh Bình (kho)",
    leadNeed: "Tư vấn liệu trình A",
    leadSource: "Facebook",
  });
});

describe("Nhập Excel — sản phẩm, cột tuỳ chọn mới", () => {
  it("file hợp lệ: 1 sản phẩm mới đủ cột mới + 1 sản phẩm cũ (cột mới để trống) → preview không lỗi", async () => {
    const buf = await buildXlsx(
      ["Mã SP", "Tên sản phẩm", "Đơn giá", "Dung tích / Trọng lượng", "Ghi chú"],
      [
        ["SPMOI01", "Serum Vitamin C mới", 250_000, "30ml", "Hàng mới về"],
        [productCode, "Kem dưỡng ẩm đang có", 180_000, "", ""], // sản phẩm đã có, cột mới để trống
      ],
    );
    const preview = await previewImport(fx.tenantA, "san-pham", buf);
    expect(preview.errors).toEqual([]);
    expect(preview.validRows).toBe(2);
  });

  it("ghi thật: sản phẩm mới có volume/note đúng theo file", async () => {
    // Chỉ dòng sản phẩm MỚI — không đụng tới productCode ở đây để test sau còn nguyên trạng thái gốc.
    const buf = await buildXlsx(
      ["Mã SP", "Tên sản phẩm", "Đơn giá", "Dung tích / Trọng lượng", "Ghi chú"],
      [["SPMOI01", "Serum Vitamin C mới", 250_000, "30ml", "Hàng mới về"]],
    );
    const result = await runImport(fx.tenantA, fx.director.userId, "san-pham", buf);
    expect(result.errors).toEqual([]);
    expect(result.inserted).toBe(1);
    expect(result.updated).toBe(0);

    const [created] = await db.select().from(products).where(eq(products.code, "SPMOI01"));
    expect(created.volume).toBe("30ml");
    expect(created.note).toBe("Hàng mới về");
  });

  it("PHÁT HIỆN: với sản phẩm, ô để trống trong cột đã khai báo sẽ XOÁ giá trị cũ (khác hành vi khách hàng bên dưới)", async () => {
    // Trước khi nhập: sản phẩm đã có volume/note thật (rắc ở beforeAll của file này).
    const [before] = await db.select().from(products).where(eq(products.code, productCode));
    expect(before.volume).toBe("100ml gốc");
    expect(before.note).toBe("Ghi chú gốc");

    const buf = await buildXlsx(
      ["Mã SP", "Tên sản phẩm", "Đơn giá", "Dung tích / Trọng lượng", "Ghi chú"],
      [[productCode, "Kem dưỡng ẩm đang có", 180_000, "", ""]], // cột có mặt nhưng để trống
    );
    await runImport(fx.tenantA, fx.director.userId, "san-pham", buf);

    const [after] = await db.select().from(products).where(eq(products.code, productCode));
    // Đây là hành vi THẬT của mã nguồn (importProducts không có skipBlank như importCustomers) —
    // ghi lại bằng test để không ai vô tình đổi mà không nhận ra, và để tài liệu mô tả đúng.
    expect(after.volume).toBeNull();
    expect(after.note).toBeNull();
  });
});

describe("Nhập Excel — khách hàng, cột tuỳ chọn mới + không xoá dữ liệu khi cập nhật", () => {
  it("khách ĐÃ CÓ: cột mới để trống → giữ nguyên dữ liệu cũ, không bị xoá", async () => {
    const [before] = await db.select().from(customers).where(eq(customers.code, customerCode));
    expect(before.contactPosition).toBe("Giám đốc phòng khám");
    expect(before.ward).toBe("Phường Bến Nghé");
    expect(before.receiverName).toBe("Anh Bình (kho)");
    expect(before.leadNeed).toBe("Tư vấn liệu trình A");
    expect(before.leadSource).toBe("Facebook");

    const buf = await buildXlsx(
      ["Mã KH", "Tên đơn vị", "Chức vụ", "Phường / Xã", "Người nhận hàng", "Nhu cầu tư vấn", "Nguồn khách"],
      // Tên đơn vị bắt buộc phải có ở MỌI dòng (không riêng khách mới) nên giữ nguyên tên thật;
      // chỉ các cột mở rộng mới để trống — đúng thao tác thật khi người dùng chỉ sửa vài cột.
      [[customerCode, "Phòng khám đang có", "", "", "", "", ""]],
    );
    const preview = await previewImport(fx.tenantA, "khach-hang", buf);
    expect(preview.errors).toEqual([]);

    const result = await runImport(fx.tenantA, fx.director.userId, "khach-hang", buf);
    expect(result.errors).toEqual([]);
    expect(result.updated).toBe(1);
    expect(result.inserted).toBe(0);

    const [after] = await db.select().from(customers).where(eq(customers.code, customerCode));
    expect(after.contactPosition).toBe("Giám đốc phòng khám");
    expect(after.ward).toBe("Phường Bến Nghé");
    expect(after.receiverName).toBe("Anh Bình (kho)");
    expect(after.leadNeed).toBe("Tư vấn liệu trình A");
    expect(after.leadSource).toBe("Facebook");
  });

  it("khách MỚI: cột mới ghi đúng giá trị trong file", async () => {
    const buf = await buildXlsx(
      ["Tên đơn vị", "Kênh", "Khu vực", "Người liên hệ", "Điện thoại", "Chức vụ", "Phường / Xã", "Người nhận hàng", "Nhu cầu tư vấn", "Nguồn khách"],
      [["Spa Mới Tinh", fx.channelName, fx.territoryName, "Chị Lan", "0909111222", "Chủ spa", "Phường 5", "Chị Lan (spa)", "Tư vấn liệu trình B", "Zalo"]],
    );
    const preview = await previewImport(fx.tenantA, "khach-hang", buf);
    expect(preview.errors).toEqual([]);

    const result = await runImport(fx.tenantA, fx.director.userId, "khach-hang", buf);
    expect(result.inserted).toBe(1);

    const [created] = await db.select().from(customers).where(eq(customers.orgName, "Spa Mới Tinh"));
    expect(created.contactPosition).toBe("Chủ spa");
    expect(created.ward).toBe("Phường 5");
    expect(created.receiverName).toBe("Chị Lan (spa)");
    expect(created.leadNeed).toBe("Tư vấn liệu trình B");
    expect(created.leadSource).toBe("Zalo");
  });

  it("nhập lại ĐÚNG FILE CŨ lần 2: không tạo trùng, khách mới ở lần 1 nay thành cập nhật", async () => {
    const buf = await buildXlsx(
      ["Tên đơn vị", "Kênh", "Khu vực", "Người liên hệ", "Điện thoại", "Chức vụ", "Phường / Xã", "Người nhận hàng", "Nhu cầu tư vấn", "Nguồn khách"],
      [["Spa Mới Tinh", fx.channelName, fx.territoryName, "Chị Lan", "0909111222", "Chủ spa", "Phường 5", "Chị Lan (spa)", "Tư vấn liệu trình B", "Zalo"]],
    );
    // Chạy đúng file này lần 2 (mô phỏng người dùng bấm nhầm nhập lại) — không có "Mã KH" trong file
    // nên hệ thống dò trùng qua bước validate số điện thoại, không sinh thêm khách trùng số.
    const preview = await previewImport(fx.tenantA, "khach-hang", buf);
    expect(preview.errors.some((e) => e.message.includes("đã thuộc khách"))).toBe(true);

    const countBefore = await db.select({ id: customers.id }).from(customers).where(eq(customers.orgName, "Spa Mới Tinh"));
    expect(countBefore).toHaveLength(1);
  });
});
