/**
 * Robot Manager Import 工具 L1 集成测试（M1 PR-B 出口）
 *
 * 覆盖：详见 docs/modules/robot-manager/14-import-export-tool-prd.md §11.2
 *   - happy: PO 多行 → preview → confirm → 验 PO + Line 创建
 *   - sad: 文件无效 / 1001 行超限 / 必填漏 / FK 不存在 / quantity=0 业务规则 /
 *          cross-org IDOR / 重复 confirm CAS 锁 / confirm 时 errorRows>0
 */
import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { PrismaService } from '@/core/database/prisma/prisma.service';
import { NIL_ORG_ID as NIL_ORG } from '@/common/constants/nil-uuid';
import { createTestApp } from '../../helpers/app.helper';
import { setupIntegrationTest } from '../../helpers/test-setup.helper';
import { buildPoExcel } from './_helpers/build-po-excel';

describe('Robot Manager Import — L1 集成', () => {
  let app: INestApplication;
  let prisma: PrismaService;
  let token: string;
  let userId: string;
  let supplierId: string;
  let supplierCode: string;
  let skuCode: string;
  let modelId: string;

  beforeAll(async () => {
    app = await createTestApp();
    prisma = app.get(PrismaService);
    const ctx = await setupIntegrationTest(app, prisma);
    token = ctx.adminToken;
    userId = ctx.adminUser.id;

    // fixture: 1 supplier + 1 sku
    const ts = Date.now();
    supplierCode = `t_sup_imp_${ts}`;
    const sup = await prisma.supplier.create({
      data: {
        code: supplierCode, name: 'T Imp Supplier', currencyCode: 'USD',
        organizationId: NIL_ORG, createdById: userId,
      },
    });
    supplierId = sup.id;

    const model = await prisma.robotModel.create({
      data: { code: `t_mdl_imp_${ts}`, name: 'T Imp Model', organizationId: NIL_ORG, createdById: userId },
    });
    modelId = model.id;
    skuCode = `t_sku_imp_${ts}`;
    await prisma.robotSku.create({
      data: {
        code: skuCode, name: 'T Imp SKU', modelId, defaultPrice: 30000,
        organizationId: NIL_ORG, createdById: userId,
      },
    });
  });

  // sad 用例创建的 VALIDATED batch 不 confirm → 留下 in-flight 占位，会让下一个用例
  // 撞 `ensureNoOtherInFlight` 抛 IMPORT_CONCURRENT_BATCH。全局 `beforeEach` 清一遍。
  beforeEach(async () => {
    await prisma.importBatch.deleteMany({ where: { createdById: userId } });
  });

  afterAll(async () => {
    // cleanup
    await prisma.importBatch.deleteMany({ where: { createdById: userId } });
    await prisma.purchaseOrder.deleteMany({ where: { poNo: { startsWith: 'PO-IMP-T-' } } });
    await prisma.robotSku.deleteMany({ where: { code: { startsWith: 't_sku_imp_' } } });
    await prisma.robotModel.deleteMany({ where: { code: { startsWith: 't_mdl_imp_' } } });
    await prisma.supplier.deleteMany({ where: { code: { startsWith: 't_sup_imp_' } } });
    await app?.close();
  });

  // helper: 上传文件
  const uploadPreview = (buffer: Buffer, filename = 'test.xlsx') =>
    request(app.getHttpServer())
      .post('/api/v1/robot-manager/import/purchase-order/preview')
      .set('Authorization', `Bearer ${token}`)
      .attach('file', buffer, { filename, contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });

  describe('Happy path', () => {
    it('happy 1: PO 2 行（同 PO） → preview OK → confirm → 1 PO + 2 Line', async () => {
      const poNo = `PO-IMP-T-H1-${Date.now()}`;
      const buffer = buildPoExcel([
        { poNo, supplierCode, lineNo: 1, skuCode, quantity: 5, unitPrice: 30000 },
        { poNo, supplierCode, lineNo: 2, skuCode, quantity: 3, unitPrice: 30000 },
      ]);
      const previewRes = await uploadPreview(buffer);
      expect(previewRes.status).toBe(201);
      const { batchId, summary } = previewRes.body.data;
      expect(summary.totalRows).toBe(2);
      expect(summary.errorRows).toBe(0);
      expect(summary.successRows).toBe(2);

      const confirmRes = await request(app.getHttpServer())
        .post(`/api/v1/robot-manager/import/batches/${batchId}/confirm`)
        .set('Authorization', `Bearer ${token}`);
      expect(confirmRes.status).toBe(201);
      expect(confirmRes.body.data.status).toBe('COMPLETED');

      const po = await prisma.purchaseOrder.findUnique({
        where: { poNo },
        include: { lines: true },
      });
      expect(po).toBeTruthy();
      expect(po!.lines).toHaveLength(2);
      expect(Number(po!.totalAmount)).toBe(5 * 30000 + 3 * 30000);
    });
  });

  describe('Sad path — 解析层', () => {
    it('sad 1: 文件 buffer 不是合法 Excel → 解析层拒绝', async () => {
      const res = await uploadPreview(Buffer.from('not an excel file'));
      expect(res.status).toBe(400);
      // xlsx 库对 garbage buffer 可能 throw（IMPORT_FILE_INVALID）也可能 lenient
      // 解析成空 workbook（IMPORT_SHEET_MISSING / IMPORT_FILE_EMPTY），三者都算"解析层拒绝"
      expect(['IMPORT_FILE_INVALID', 'IMPORT_SHEET_MISSING', 'IMPORT_FILE_EMPTY'])
        .toContain(res.body.error.code);
    });

    it('sad 2: 1001 行 → IMPORT_ROW_LIMIT', async () => {
      const rows = Array.from({ length: 1001 }, (_, i) => ({
        poNo: `PO-IMP-T-LARGE-${i}`, supplierCode, lineNo: 1, skuCode, quantity: 1, unitPrice: 100,
      }));
      const res = await uploadPreview(buildPoExcel(rows));
      expect(res.status).toBe(400);
      expect(res.body.error.code).toBe('IMPORT_ROW_LIMIT');
    });
  });

  describe('Sad path — 引用 / 业务规则', () => {
    it('sad 3: 必填 poNo 空 → entries 含 IMPORT_REQUIRED_MISSING + preview.errorPreview 含明细', async () => {
      const buffer = buildPoExcel([
        { poNo: '', supplierCode, lineNo: 1, skuCode, quantity: 1, unitPrice: 100 },
      ]);
      const res = await uploadPreview(buffer);
      expect(res.status).toBe(201);
      expect(res.body.data.summary.errorRows).toBe(1);
      // backend 一次性返 errorPreview top-50 ERROR 行，FE 不再 GET /batches/:id
      const errorPreview = res.body.data.errorPreview as Array<{ rowNo: number; errorDetail: any[] }>;
      expect(errorPreview).toHaveLength(1);
      expect(errorPreview[0].rowNo).toBe(1);
      expect(errorPreview[0].errorDetail.some(
        (e: any) => e.code === 'IMPORT_REQUIRED_MISSING' && e.field === 'poNo',
      )).toBe(true);
    });

    it('sad 4: supplierCode 不存在 → IMPORT_FK_NOT_FOUND', async () => {
      const buffer = buildPoExcel([
        { poNo: `PO-IMP-T-FK-${Date.now()}`, supplierCode: 'NONEXISTENT_SUP', lineNo: 1, skuCode, quantity: 1, unitPrice: 100 },
      ]);
      const res = await uploadPreview(buffer);
      expect(res.body.data.summary.errorRows).toBe(1);
      const batch = await prisma.importBatch.findUnique({
        where: { id: res.body.data.batchId }, include: { entries: true },
      });
      const errors = batch!.entries[0].errorDetail as any[];
      expect(errors.some((e) => e.code === 'IMPORT_FK_NOT_FOUND' && e.field === 'supplierCode')).toBe(true);
    });

    it('sad 5: quantity = 0 → IMPORT_BUSINESS_RULE_VIOLATION', async () => {
      const buffer = buildPoExcel([
        { poNo: `PO-IMP-T-Q0-${Date.now()}`, supplierCode, lineNo: 1, skuCode, quantity: 0, unitPrice: 100 },
      ]);
      const res = await uploadPreview(buffer);
      expect(res.body.data.summary.errorRows).toBe(1);
      const batch = await prisma.importBatch.findUnique({
        where: { id: res.body.data.batchId }, include: { entries: true },
      });
      const errors = batch!.entries[0].errorDetail as any[];
      expect(errors.some((e) => e.code === 'IMPORT_BUSINESS_RULE_VIOLATION')).toBe(true);
    });
  });

  describe('Sad path — confirm 阶段', () => {
    it('sad 6: confirm 时 errorRows > 0 → IMPORT_BATCH_HAS_ERRORS', async () => {
      const buffer = buildPoExcel([
        { poNo: `PO-IMP-T-CE-${Date.now()}`, supplierCode: 'NONEXISTENT', lineNo: 1, skuCode, quantity: 1, unitPrice: 100 },
      ]);
      const preview = await uploadPreview(buffer);
      const batchId = preview.body.data.batchId;

      const res = await request(app.getHttpServer())
        .post(`/api/v1/robot-manager/import/batches/${batchId}/confirm`)
        .set('Authorization', `Bearer ${token}`);
      expect(res.status).toBe(400);
      expect(res.body.error.code).toBe('IMPORT_BATCH_HAS_ERRORS');
    });

    it('sad 7: 重复 confirm 同 batch → IMPORT_BATCH_ALREADY_CONFIRMED', async () => {
      const poNo = `PO-IMP-T-CF-${Date.now()}`;
      const buffer = buildPoExcel([
        { poNo, supplierCode, lineNo: 1, skuCode, quantity: 1, unitPrice: 100 },
      ]);
      const preview = await uploadPreview(buffer);
      const batchId = preview.body.data.batchId;
      // first confirm OK
      const first = await request(app.getHttpServer())
        .post(`/api/v1/robot-manager/import/batches/${batchId}/confirm`)
        .set('Authorization', `Bearer ${token}`);
      expect(first.status).toBe(201);
      // second confirm 应该 fail（CAS 锁 WHERE status=VALIDATED 不命中）
      const second = await request(app.getHttpServer())
        .post(`/api/v1/robot-manager/import/batches/${batchId}/confirm`)
        .set('Authorization', `Bearer ${token}`);
      expect(second.status).toBe(400);
      expect(second.body.error.code).toBe('IMPORT_BATCH_ALREADY_CONFIRMED');
    });
  });

  describe('IDOR / 跨用户隔离', () => {
    it('sad 8: 非 admin 用 fake batchId GET → 404 不 403（防探测）', async () => {
      const fakeId = '00000000-0000-0000-0000-000000000000';
      const res = await request(app.getHttpServer())
        .get(`/api/v1/robot-manager/import/batches/${fakeId}`)
        .set('Authorization', `Bearer ${token}`);
      expect(res.status).toBe(404);
      expect(res.body.error.code).toBe('IMPORT_BATCH_NOT_FOUND');
    });
  });

  describe('Idempotent / 并发', () => {
    it('happy 2: 同 fileHash 24h 内重复上传 → 返已有 batchId（deduped=true）', async () => {
      const buffer = buildPoExcel([
        { poNo: `PO-IMP-T-IDEM-${Date.now()}`, supplierCode, lineNo: 1, skuCode, quantity: 1, unitPrice: 100 },
      ]);
      const first = await uploadPreview(buffer);
      const second = await uploadPreview(buffer);
      expect(second.body.data.batchId).toBe(first.body.data.batchId);
      expect(second.body.data.summary.deduped).toBe(true);
    });
  });

  describe('Template download', () => {
    it('happy 3: GET /import/purchase-order/template → xlsx + X-Template-Schema-Hash header', async () => {
      const res = await request(app.getHttpServer())
        .get('/api/v1/robot-manager/import/purchase-order/template')
        .set('Authorization', `Bearer ${token}`)
        .buffer(true)
        .parse((response, cb) => {
          const chunks: Buffer[] = [];
          response.on('data', (c: Buffer) => chunks.push(c));
          response.on('end', () => cb(null, Buffer.concat(chunks)));
        });
      expect(res.status).toBe(200);
      expect(res.headers['content-type']).toContain('spreadsheetml');
      expect(res.headers['x-template-schema-hash']).toBeTruthy();
      expect((res.body as Buffer).length).toBeGreaterThan(1000); // buffer 不能空
    });
  });
});
