/**
 * Robot Manager v3 L1 集成测试
 *
 * 覆盖：
 *   - RobotUnit CRUD（自动建 Snapshot + 第一条 stage_changed event）
 *   - Lifecycle changeStage happy path + 非法转换拒绝
 *   - Hold / Unhold / moveLocation event 投影
 *   - Snapshot 乐观锁 version conflict
 *   - 13 Guard 触发样本（checkConversionValidated）
 *   - L3 业务表 CRUD（PO / SO / Delivery / Payment / ServiceTicket / Rental）
 *   - Records 子表（QualityLabel / Readiness / Inspection / LogisticsLeg / Compliance）+ event 投影
 *
 * 注：这是 v3 重写，旧 v2 test (status / sections / excel / fieldDef) 全部废弃。
 */
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';

describe('Robot Manager v3 — L1 集成', () => {
  let app: INestApplication;
  let prisma: PrismaService;
  let token: string;
  let userId: string;
  let modelId: string;
  let skuId: string;
  let supplierId: string;
  let customerId: string;
  let locationId: string;

  const auth = (path: string) =>
    request(app.getHttpServer()).get(path).set('Authorization', `Bearer ${token}`);
  const authPost = (path: string, body: any) =>
    request(app.getHttpServer()).post(path).set('Authorization', `Bearer ${token}`).send(body);
  const authPut = (path: string, body: any) =>
    request(app.getHttpServer()).put(path).set('Authorization', `Bearer ${token}`).send(body);

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

    // 业务 fixture — 全部 setup helper 之外现场建
    const model = await prisma.robotModel.create({
      data: {
        code: `t_mdl_${Date.now()}`,
        name: 'Test Model',
        brand: 'TestBrand',
        organizationId: NIL_ORG,
        createdById: userId,
      },
    });
    modelId = model.id;
    const sku = await prisma.robotSku.create({
      data: {
        code: `t_sku_${Date.now()}`,
        name: 'Test SKU',
        modelId,
        defaultPrice: 30000,
        organizationId: NIL_ORG,
        createdById: userId,
      },
    });
    skuId = sku.id;
    const sup = await prisma.supplier.create({
      data: {
        code: `t_sup_${Date.now()}`,
        name: 'Test Supplier',
        currencyCode: 'CNY',
        organizationId: NIL_ORG,
        createdById: userId,
      },
    });
    supplierId = sup.id;
    const cust = await prisma.customer.create({
      data: {
        code: `t_cust_${Date.now()}`,
        name: 'Test Customer',
        currencyCode: 'USD',
        organizationId: NIL_ORG,
        createdById: userId,
      },
    });
    customerId = cust.id;
    const loc = await prisma.location.create({
      data: {
        code: `t_loc_${Date.now()}`,
        name: 'Test Location',
        type: 'WAREHOUSE',
        organizationId: NIL_ORG,
        createdById: userId,
      },
    });
    locationId = loc.id;
  });

  afterAll(async () => {
    // 三层批：a) grandchild-of-unit（孙表 FK 指向子表），b) child-of-unit + sibling 业务表 + robot_units，
    // c) 主数据 / model / sku。每层内部互不依赖 → Promise.all 并行。
    const targetUnits = await prisma.robotUnit.findMany({
      where: { ffsn: { startsWith: 'FF-TEST-' } },
      select: { id: true },
    });
    const ids = targetUnits.map((u) => u.id);
    if (ids.length > 0) {
      // a) 孙表：RentalPaymentSchedule（指向 RentalAgreement）+ ServiceTicketActivity（指向 ServiceTicket）
      await Promise.all([
        prisma.rentalPaymentSchedule.deleteMany({
          where: { rentalAgreement: { robotUnitId: { in: ids } } },
        }),
        prisma.serviceTicketActivity.deleteMany({
          where: { ticket: { robotUnitId: { in: ids } } },
        }),
      ]);
      // b) 12 个直接指向 robot_units 的子表 — 全独立可并行
      await Promise.all([
        prisma.rentalAgreement.deleteMany({ where: { robotUnitId: { in: ids } } }),
        prisma.serviceTicket.deleteMany({ where: { robotUnitId: { in: ids } } }),
        prisma.paymentRecord.deleteMany({ where: { robotUnitId: { in: ids } } }),
        prisma.deliveryFulfillment.deleteMany({ where: { robotUnitId: { in: ids } } }),
        prisma.qualityLabelRecord.deleteMany({ where: { robotUnitId: { in: ids } } }),
        prisma.robotPackageReadiness.deleteMany({ where: { robotUnitId: { in: ids } } }),
        prisma.inspectionRecord.deleteMany({ where: { robotUnitId: { in: ids } } }),
        prisma.logisticsLeg.deleteMany({ where: { robotUnitId: { in: ids } } }),
        prisma.complianceCheck.deleteMany({ where: { robotUnitId: { in: ids } } }),
        prisma.robotLifecycleEvent.deleteMany({ where: { robotUnitId: { in: ids } } }),
        prisma.robotUnitSnapshot.deleteMany({ where: { robotUnitId: { in: ids } } }),
        prisma.salesOrderLine.deleteMany({ where: { robotUnitId: { in: ids } } }),
      ]);
    }
    // c) 业务表 head + robot_units + 主数据
    // ⚠️ 严格顺序：robotUnit 先于 PO/SO 删（按 PO 关联反查），否则 PO 已删后 robotUnit 查不到要清理的范围
    // robotUnit 覆盖来源：
    //   - 手动 FF-TEST-* 直接创建
    //   - PO 占位 SN 创建路径，ffsn 形如 {poNo}-LINE-{NNN}，poNo 在测试里有 PO-T-* / PO-PH-* 两种 prefix
    //   - activate-sn 后正式 FFSN 形如 FF-YYYYMM-NNNNN
    // 测试 DB 无 seed，按 ffsn 宽匹配 'FF-' / 'PO-' 安全。
    await prisma.robotUnit.deleteMany({
      where: {
        OR: [
          { ffsn: { startsWith: 'FF-' } },  // 手动 FF-TEST-* + activate 后正式 FFSN
          { ffsn: { startsWith: 'PO-' } },  // PO 占位 SN（PO-T-/PO-PH-* 都覆盖）
        ],
      },
    });
    // ⚠️ deliveryRequest FK 引用 salesOrder，必须先删（否则 salesOrder.deleteMany 触发 FK 拦截）
    await prisma.deliveryRequest.deleteMany({ where: { deliveryNo: { startsWith: 'DR-T-' } } });
    await Promise.all([
      prisma.salesOrder.deleteMany({ where: { soNo: { startsWith: 'SO-T-' } } }),
      prisma.purchaseOrder.deleteMany({
        where: { OR: [{ poNo: { startsWith: 'PO-T-' } }, { poNo: { startsWith: 'PO-PH-' } }] },
      }),
    ]);
    await Promise.all([
      prisma.location.deleteMany({ where: { code: { startsWith: 't_loc_' } } }),
      prisma.customer.deleteMany({ where: { code: { startsWith: 't_cust_' } } }),
      prisma.supplier.deleteMany({ where: { code: { startsWith: 't_sup_' } } }),
      prisma.robotSku.deleteMany({ where: { code: { startsWith: 't_sku_' } } }),
    ]);
    // robotModel 必须在 robotSku 删完之后（FK onDelete: Restrict）
    await prisma.robotModel.deleteMany({ where: { code: { startsWith: 't_mdl_' } } });
    await app?.close();
  });

  // ============================================================
  // RobotUnit CRUD + auto Snapshot/Event
  // ============================================================
  describe('POST /api/v1/robot-manager — 创建机器人', () => {
    it('创建 unit 同步建 Snapshot(currentStage=SUPPLY_PO_CREATED) + 第一条 stage_changed event', async () => {
      const res = await authPost('/api/v1/robot-manager', {
        modelId,
        skuId,
        usageType: 'SALES',
        ffsn: `FF-TEST-${Date.now()}`,
      }).expect(201);
      const unit = res.body.data;
      expect(unit.id).toBeDefined();
      expect(unit.snapshot).toBeDefined();
      expect(unit.snapshot.currentStage).toBe('SUPPLY_PO_CREATED');
      expect(unit.events).toBeInstanceOf(Array);
      expect(unit.events.length).toBeGreaterThan(0);
      expect(unit.events[0].eventType).toBe('stage_changed');
      expect(unit.events[0].fromStage).toBeNull();
      expect(unit.events[0].toStage).toBe('SUPPLY_PO_CREATED');
    });
  });

  // ============================================================
  // Lifecycle changeStage
  // ============================================================
  describe('POST /api/v1/robot-manager/:id/change-stage', () => {
    let unitId: string;

    beforeAll(async () => {
      const res = await authPost('/api/v1/robot-manager', {
        modelId,
        skuId,
        usageType: 'SALES',
        ffsn: `FF-TEST-LC-${Date.now()}`,
      });
      unitId = res.body.data.id;
    });

    it('合法转换 SUPPLY_PO_CREATED → SUPPLY_IN_PRODUCTION', async () => {
      const res = await authPost(`/api/v1/robot-manager/${unitId}/change-stage`, {
        toStage: 'SUPPLY_IN_PRODUCTION',
        reason: '供应商接单',
      }).expect(200);
      expect(res.body.data.snapshot.currentStage).toBe('SUPPLY_IN_PRODUCTION');
    });

    it('非法转换抛 400 (SUPPLY_PO_CREATED → DELIVERY_DELIVERED)', async () => {
      const res = await authPost('/api/v1/robot-manager', {
        modelId,
        skuId,
        usageType: 'SALES',
        ffsn: `FF-TEST-BADX-${Date.now()}`,
      });
      const id = res.body.data.id;
      await authPost(`/api/v1/robot-manager/${id}/change-stage`, {
        toStage: 'DELIVERY_DELIVERED',
      }).expect(400);
    });

    it('GET /:id/allowed-next-stages 返回白名单', async () => {
      const res = await auth(`/api/v1/robot-manager/${unitId}/allowed-next-stages`).expect(200);
      expect(res.body.data).toEqual(
        expect.arrayContaining(['SUPPLY_READY_TO_SHIP', 'CANCELLED']),
      );
    });
  });

  // ============================================================
  // Hold / Unhold / moveLocation
  // ============================================================
  describe('POST /:id/hold | /:id/unhold | /:id/move-location', () => {
    let unitId: string;

    beforeAll(async () => {
      const res = await authPost('/api/v1/robot-manager', {
        modelId,
        skuId,
        usageType: 'SALES',
        ffsn: `FF-TEST-HOLD-${Date.now()}`,
      });
      unitId = res.body.data.id;
    });

    it('hold 写 held event 并刷 snapshot.isHeld=true', async () => {
      await authPost(`/api/v1/robot-manager/${unitId}/hold`, {
        holdReason: 'PDI 卡住',
      }).expect(200);
      const detail = await auth(`/api/v1/robot-manager/${unitId}`).expect(200);
      expect(detail.body.data.snapshot.isHeld).toBe(true);
      expect(detail.body.data.snapshot.holdReason).toBe('PDI 卡住');
    });

    it('unhold 清掉 isHeld', async () => {
      await authPost(`/api/v1/robot-manager/${unitId}/unhold`, {}).expect(200);
      const detail = await auth(`/api/v1/robot-manager/${unitId}`).expect(200);
      expect(detail.body.data.snapshot.isHeld).toBe(false);
    });

    it('move-location 写 location_moved event 并刷 snapshot.currentLocationId', async () => {
      await authPost(`/api/v1/robot-manager/${unitId}/move-location`, {
        toLocationId: locationId,
        reason: '入库',
      }).expect(200);
      const detail = await auth(`/api/v1/robot-manager/${unitId}`).expect(200);
      expect(detail.body.data.snapshot.currentLocationId).toBe(locationId);
    });
  });

  // ============================================================
  // L3：PurchaseOrder
  // ============================================================
  describe('PurchaseOrder CRUD', () => {
    let poId: string;
    it('POST /purchase-orders — 创建', async () => {
      const res = await authPost('/api/v1/robot-manager/purchase-orders', {
        poNo: `PO-T-${Date.now()}`,
        supplierId,
        currencyCode: 'CNY',
        orderedAt: new Date().toISOString(),
        lines: [{ lineNo: 1, skuId, quantity: 5, unitPrice: 22000, currencyCode: 'CNY' }],
      }).expect(201);
      poId = res.body.data.id;
      expect(res.body.data.totalAmount).toBeDefined();
      expect(res.body.data.lines).toHaveLength(1);
    });

    it('GET /purchase-orders/:id', async () => {
      const res = await auth(`/api/v1/robot-manager/purchase-orders/${poId}`).expect(200);
      expect(res.body.data.id).toBe(poId);
    });

    it('PUT /purchase-orders/:id — 改状态', async () => {
      const res = await authPut(`/api/v1/robot-manager/purchase-orders/${poId}`, {
        status: 'ORDERED',
      }).expect(200);
      expect(res.body.data.status).toBe('ORDERED');
    });
  });

  // ============================================================
  // 占位 SN 机制（v3 业务设计 — 01 PO_CREATED 批量预留 → 07 RECEIVED 扫码激活）
  // ============================================================
  describe('占位 SN 机制 — 全链路', () => {
    let testPoId: string;
    let placeholderUnitIds: string[] = [];

    describe('Happy Path', () => {
      it('happy 1：POST PO 自动 createMany N 行占位 RobotUnit + 占位 ffsn 符合 {poNo}-LINE-{NNN}', async () => {
        const poNo = `PO-PH-${Date.now()}`;
        const res = await authPost('/api/v1/robot-manager/purchase-orders', {
          poNo,
          supplierId,
          currencyCode: 'CNY',
          orderedAt: new Date().toISOString(),
          lines: [{ lineNo: 1, skuId, quantity: 3, unitPrice: 22000, currencyCode: 'CNY' }],
        }).expect(201);
        testPoId = res.body.data.id;

        // 拉该 PO 的所有 RobotUnit
        const listRes = await auth(
          `/api/v1/robot-manager?purchaseOrderId=${testPoId}&limit=50`,
        ).expect(200);
        expect(listRes.body.data.items).toHaveLength(3);
        placeholderUnitIds = listRes.body.data.items.map((u: any) => u.id);

        const ffsns = listRes.body.data.items.map((u: any) => u.ffsn).sort();
        expect(ffsns[0]).toBe(`${poNo}-LINE-001`);
        expect(ffsns[2]).toBe(`${poNo}-LINE-003`);
        // 占位字段：placeholderSnOrig 为 null（激活后才填）
        listRes.body.data.items.forEach((u: any) => {
          expect(u.placeholderSnOrig).toBeNull();
          expect(u.snapshot?.currentStage).toBe('SUPPLY_PO_CREATED');
        });
      });

      it('happy 2：POST :id/activate-sn 占位 SN → 正式 FFSN，原值保留 placeholderSnOrig + 写 sn_activated event', async () => {
        const unitId = placeholderUnitIds[0];
        const originalFfsn = `${(await auth(`/api/v1/robot-manager/${unitId}`).expect(200)).body.data.ffsn}`;

        const res = await authPost(`/api/v1/robot-manager/${unitId}/activate-sn`, {
          supplierSn: 'SUP-TEST-001',
        }).expect(200);

        expect(res.body.data.ffsn).not.toBe(originalFfsn);
        // FFSN 格式：FF-YYYYMM-{seqLength 位}。seqLength 来自 RobotSystemConfig.ffsn_rule（seed 配 5）。
        expect(res.body.data.ffsn).toMatch(/^FF-\d{6}-\d{5}$/);
        expect(res.body.data.placeholderSnOrig).toBe(originalFfsn);
        expect(res.body.data.supplierSn).toBe('SUP-TEST-001');

        // 校验 sn_activated event
        const evRes = await auth(`/api/v1/robot-manager/${unitId}/events`).expect(200);
        const snEvent = evRes.body.data.find((e: any) => e.eventType === 'sn_activated');
        expect(snEvent).toBeDefined();
        expect(snEvent.payload.placeholderSn).toBe(originalFfsn);
        expect(snEvent.payload.newFfsn).toBe(res.body.data.ffsn);
      });

      it('happy 3：激活时 advanceTo 同步推进 stage', async () => {
        const unitId = placeholderUnitIds[1];
        // 先把 stage 推到 CUSTOMS_CLEARED（多步链式）
        const stages = [
          'SUPPLY_IN_PRODUCTION',
          'SUPPLY_READY_TO_SHIP',
          'LOGISTICS_IN_TRANSIT',
          'LOGISTICS_BONDED',
          'LOGISTICS_CUSTOMS_CLEARED',
        ];
        for (const s of stages) {
          await authPost(`/api/v1/robot-manager/${unitId}/change-stage`, { toStage: s }).expect(
            200,
          );
        }

        const res = await authPost(`/api/v1/robot-manager/${unitId}/activate-sn`, {
          supplierSn: 'SUP-TEST-002',
          advanceTo: 'WAREHOUSE_RECEIVED',
        }).expect(200);

        expect(res.body.data.snapshot.currentStage).toBe('WAREHOUSE_RECEIVED');
        expect(res.body.data.placeholderSnOrig).toMatch(/-LINE-\d+$/);
      });
    });

    describe('Error Cases', () => {
      it('error 1 — ALREADY_ACTIVATED：已激活的 SN 不能再激活', async () => {
        // happy 2 已激活第一个 unit，再激活应 409
        const unitId = placeholderUnitIds[0];
        const res = await authPost(`/api/v1/robot-manager/${unitId}/activate-sn`, {
          supplierSn: 'SUP-WHATEVER',
        }).expect(409);
        expect(res.body.error?.message ?? '').toMatch(/不是占位|ALREADY_ACTIVATED/);
      });

      it('error 2 — MISSING_SUPPLIER_SN：supplierSn 必填', async () => {
        const unitId = placeholderUnitIds[2];
        await authPost(`/api/v1/robot-manager/${unitId}/activate-sn`, {
          supplierSn: '',
        }).expect(400);
      });

      it('error 3 — STAGE_NOT_AVAILABLE：stage > CUSTOMS_CLEARED 时激活拒绝', async () => {
        // unitIds[1] 已经推到 WAREHOUSE_RECEIVED 且激活了，不算此测试目标
        // 重新创建一个 PO 拿新占位 unit + 推到 WAREHOUSE_RECEIVED 之后激活
        const poRes = await authPost('/api/v1/robot-manager/purchase-orders', {
          poNo: `PO-PH-STAGE-${Date.now()}`,
          supplierId,
          currencyCode: 'CNY',
          orderedAt: new Date().toISOString(),
          lines: [{ lineNo: 1, skuId, quantity: 1, unitPrice: 22000, currencyCode: 'CNY' }],
        }).expect(201);
        const listRes = await auth(
          `/api/v1/robot-manager?purchaseOrderId=${poRes.body.data.id}&limit=10`,
        ).expect(200);
        const unitId = listRes.body.data.items[0].id;
        // 先激活推到 RECEIVED
        await authPost(`/api/v1/robot-manager/${unitId}/activate-sn`, {
          supplierSn: 'SUP-S3-A',
          advanceTo: 'WAREHOUSE_RECEIVED',
        }).expect(200);
        // 再激活：因为 stage 已 RECEIVED + 不再是占位（双重拒绝）
        // 实际是先撞 ALREADY_ACTIVATED（因为已激活）
        const res = await authPost(`/api/v1/robot-manager/${unitId}/activate-sn`, {
          supplierSn: 'SUP-S3-B',
        }).expect(409);
        expect(res.body.error?.message ?? '').toMatch(
          /ALREADY_ACTIVATED|STAGE_NOT_AVAILABLE|不是占位/,
        );
      });

      it('error 4 — SN_CONFLICT：手动指定的正式 FFSN 已被其他 unit 占用', async () => {
        // 拿到 happy 2 激活后的正式 FFSN，再创建新占位 + activate 时指定同 ffsn → 冲突
        const conflictedFfsn = (
          await auth(`/api/v1/robot-manager/${placeholderUnitIds[0]}`).expect(200)
        ).body.data.ffsn;

        // 新创建一个占位 unit
        const poRes = await authPost('/api/v1/robot-manager/purchase-orders', {
          poNo: `PO-PH-CONFLICT-${Date.now()}`,
          supplierId,
          currencyCode: 'CNY',
          orderedAt: new Date().toISOString(),
          lines: [{ lineNo: 1, skuId, quantity: 1, unitPrice: 22000, currencyCode: 'CNY' }],
        }).expect(201);
        const listRes = await auth(
          `/api/v1/robot-manager?purchaseOrderId=${poRes.body.data.id}&limit=10`,
        ).expect(200);
        const unitId = listRes.body.data.items[0].id;

        const res = await authPost(`/api/v1/robot-manager/${unitId}/activate-sn`, {
          supplierSn: 'SUP-CONFLICT-001',
          ffsn: conflictedFfsn,
        }).expect(409);
        expect(res.body.error?.message ?? '').toMatch(/SN_CONFLICT|已被.*占用/);
      });

      it('error 5 — PLACEHOLDER_NOT_ACTIVATED：推进到 WAREHOUSE_RECEIVED 但占位 SN 未激活', async () => {
        // 新建占位 unit + 推到 CUSTOMS_CLEARED + 不激活直接推 RECEIVED → guard 拒绝
        const poRes = await authPost('/api/v1/robot-manager/purchase-orders', {
          poNo: `PO-PH-GATE-${Date.now()}`,
          supplierId,
          currencyCode: 'CNY',
          orderedAt: new Date().toISOString(),
          lines: [{ lineNo: 1, skuId, quantity: 1, unitPrice: 22000, currencyCode: 'CNY' }],
        }).expect(201);
        const listRes = await auth(
          `/api/v1/robot-manager?purchaseOrderId=${poRes.body.data.id}&limit=10`,
        ).expect(200);
        const unitId = listRes.body.data.items[0].id;

        // 推到 CUSTOMS_CLEARED
        for (const s of [
          'SUPPLY_IN_PRODUCTION',
          'SUPPLY_READY_TO_SHIP',
          'LOGISTICS_IN_TRANSIT',
          'LOGISTICS_BONDED',
          'LOGISTICS_CUSTOMS_CLEARED',
        ]) {
          await authPost(`/api/v1/robot-manager/${unitId}/change-stage`, { toStage: s }).expect(
            200,
          );
        }

        // 直接推 RECEIVED 不激活 → guard 失败
        const res = await authPost(`/api/v1/robot-manager/${unitId}/change-stage`, {
          toStage: 'WAREHOUSE_RECEIVED',
        }).expect(400);
        expect(JSON.stringify(res.body)).toMatch(/PLACEHOLDER_NOT_ACTIVATED|占位 SN/);
      });
    });
  });

  // ============================================================
  // L3：SalesOrder
  // ============================================================
  describe('SalesOrder CRUD', () => {
    let soId: string;
    it('POST /sales-orders — 创建', async () => {
      const res = await authPost('/api/v1/robot-manager/sales-orders', {
        soNo: `SO-T-${Date.now()}`,
        customerId,
        currencyCode: 'USD',
        contractStatus: 'SIGNED',
        lines: [
          { lineNo: 1, lineType: 'HARDWARE', unitPrice: 35000, currencyCode: 'USD' },
        ],
      }).expect(201);
      soId = res.body.data.id;
      expect(res.body.data.lines).toHaveLength(1);
    });

    it('GET 查询包含 lines', async () => {
      const res = await auth(`/api/v1/robot-manager/sales-orders/${soId}`).expect(200);
      expect(res.body.data.lines).toBeDefined();
    });
  });

  // ============================================================
  // L3：Payment CRUD + mark-paid 状态突变
  // ============================================================
  describe('Payment CRUD + mark-paid', () => {
    let soIdLocal: string;
    let paymentId: string;
    beforeAll(async () => {
      const so = await authPost('/api/v1/robot-manager/sales-orders', {
        soNo: `SO-T-PAY-${Date.now()}`,
        customerId,
        currencyCode: 'USD',
        contractStatus: 'SIGNED',
        lines: [{ lineNo: 1, lineType: 'HARDWARE', unitPrice: 35000, currencyCode: 'USD' }],
      });
      soIdLocal = so.body.data.id;
    });

    it('POST /payments — INBOUND 创建 (SalesOrder 关联)', async () => {
      const res = await authPost('/api/v1/robot-manager/payments', {
        paymentNo: `PAY-T-${Date.now()}`,
        relatedType: 'SALES_ORDER',
        relatedId: soIdLocal,
        direction: 'INBOUND',
        amount: 35000,
        currencyCode: 'USD',
        paymentMethod: 'WIRE_TRANSFER',
        paymentStatus: 'NOT_PAID_YET',
      }).expect(201);
      paymentId = res.body.data.id;
      expect(res.body.data.paymentNo).toBeDefined();
      expect(res.body.data.direction).toBe('INBOUND');
      expect(res.body.data.paymentStatus).toBe('NOT_PAID_YET');
    });

    it('POST /payments/:id/mark-paid — 状态突变 PAID + paidAt 设置', async () => {
      const paidAt = '2026-05-18T10:00:00Z';
      const res = await authPost(`/api/v1/robot-manager/payments/${paymentId}/mark-paid`, {
        paidAt,
      }).expect(200);
      expect(res.body.data.paymentStatus).toBe('PAID');
      expect(res.body.data.paidAt).toBeDefined();
    });
  });

  // ============================================================
  // L3：Delivery Request + Fulfillment — 状态突变 + Snapshot 投影
  // ============================================================
  describe('Delivery — request + fulfillment 同事务写 event + 刷 Snapshot', () => {
    let robotIdLocal: string;
    let soIdLocal: string;
    let drId: string;

    beforeAll(async () => {
      const so = await authPost('/api/v1/robot-manager/sales-orders', {
        soNo: `SO-T-DEL-${Date.now()}`,
        customerId,
        currencyCode: 'USD',
        contractStatus: 'SIGNED',
        lines: [{ lineNo: 1, lineType: 'HARDWARE', unitPrice: 30000, currencyCode: 'USD' }],
      });
      soIdLocal = so.body.data.id;

      const r = await authPost('/api/v1/robot-manager', {
        modelId,
        skuId,
        usageType: 'SALES',
        ffsn: `FF-TEST-DEL-${Date.now()}`,
      });
      robotIdLocal = r.body.data.id;
    });

    it('POST /delivery-requests — 创建', async () => {
      const res = await authPost('/api/v1/robot-manager/delivery-requests', {
        deliveryNo: `DR-T-${Date.now()}`,
        salesOrderId: soIdLocal,
        customerId,
        requestType: 'WILL_CALL',
        expectedDate: '2026-06-01',
      }).expect(201);
      drId = res.body.data.id;
      expect(res.body.data.deliveryNo).toBeDefined();
    });

    it('POST /delivery-fulfillments — 同事务写 delivery_signed event + 刷 Snapshot.warrantyStatus=ACTIVE + currentCustomerId', async () => {
      const res = await authPost('/api/v1/robot-manager/delivery-fulfillments', {
        deliveryRequestId: drId,
        robotUnitId: robotIdLocal,
        deliveredAt: '2026-05-18T10:00:00Z',
        signedFormStatus: 'SIGNED',
      }).expect(201);
      expect(res.body.data.id).toBeDefined();

      // 验 delivery_signed event 被同事务写
      const events = await auth(`/api/v1/robot-manager/${robotIdLocal}/events`).expect(200);
      expect(
        (events.body.data as any[]).some((e: any) => e.eventType === 'delivery_signed'),
      ).toBe(true);

      // 验 Snapshot 被 projector 投影：warrantyStatus=ACTIVE + currentCustomerId
      const unit = await auth(`/api/v1/robot-manager/${robotIdLocal}`).expect(200);
      expect(unit.body.data.snapshot.warrantyStatus).toBe('ACTIVE');
      expect(unit.body.data.snapshot.currentCustomerId).toBe(customerId);
    });
  });

  // ============================================================
  // L3：ServiceTicket open/close 自动写 event
  // ============================================================
  describe('ServiceTicket open/close 自动写 event', () => {
    let robotId: string;
    let ticketId: string;
    beforeAll(async () => {
      const r = await authPost('/api/v1/robot-manager', {
        modelId,
        skuId,
        usageType: 'SALES',
        ffsn: `FF-TEST-ST-${Date.now()}`,
      });
      robotId = r.body.data.id;
    });

    it('POST /service-tickets — 创建工单 + 写 service_opened event', async () => {
      const res = await authPost('/api/v1/robot-manager/service-tickets', {
        ticketNo: `TKT-T-${Date.now()}`,
        robotUnitId: robotId,
        issueTypeCode: 'OTHER',
        severity: 'P2',
      }).expect(201);
      ticketId = res.body.data.id;
      const events = await auth(`/api/v1/robot-manager/${robotId}/events`).expect(200);
      expect(
        (events.body.data as any[]).some((e: any) => e.eventType === 'service_opened'),
      ).toBe(true);
    });

    it('PUT /service-tickets/:id — close → service_closed event', async () => {
      await authPut(`/api/v1/robot-manager/service-tickets/${ticketId}`, {
        status: 'CLOSED',
      }).expect(200);
      const events = await auth(`/api/v1/robot-manager/${robotId}/events`).expect(200);
      expect(
        (events.body.data as any[]).some((e: any) => e.eventType === 'service_closed'),
      ).toBe(true);
    });
  });

  // ============================================================
  // L3：Rental — 自动生成 schedule
  // ============================================================
  describe('RentalAgreement 自动生成 PaymentSchedule', () => {
    let robotId: string;
    beforeAll(async () => {
      const r = await authPost('/api/v1/robot-manager', {
        modelId,
        skuId,
        usageType: 'SALES',
        ffsn: `FF-TEST-RN-${Date.now()}`,
      });
      robotId = r.body.data.id;
    });

    it('POST /rentals 12 月期 → 12 条 schedule', async () => {
      const start = new Date('2026-06-01').toISOString();
      const end = new Date('2027-06-01').toISOString();
      const res = await authPost('/api/v1/robot-manager/rentals', {
        robotUnitId: robotId,
        customerId,
        startAt: start,
        endAt: end,
        monthlyRate: 5000,
        currencyCode: 'USD',
      }).expect(201);
      expect(res.body.data.periodMonths).toBe(12);

      const detail = await auth(`/api/v1/robot-manager/rentals/${res.body.data.id}`).expect(200);
      expect(detail.body.data.schedules).toHaveLength(12);
    });
  });

  // ============================================================
  // Records: QualityLabel + Readiness + Inspection + LogisticsLeg + Compliance
  // ============================================================
  describe('Robot Records 子表 + LifecycleEvent 投影', () => {
    let robotId: string;
    beforeAll(async () => {
      const r = await authPost('/api/v1/robot-manager', {
        modelId,
        skuId,
        usageType: 'SALES',
        ffsn: `FF-TEST-REC-${Date.now()}`,
      });
      robotId = r.body.data.id;
    });

    it('PUT /:id/quality-labels APPLIED 写 label_applied event', async () => {
      await authPut(`/api/v1/robot-manager/${robotId}/quality-labels`, {
        labelTypeCode: 'BODY_FCC',
        status: 'APPLIED',
      }).expect(200);
      const events = await auth(`/api/v1/robot-manager/${robotId}/events`).expect(200);
      expect(
        (events.body.data as any[]).some((e: any) => e.eventType === 'label_applied'),
      ).toBe(true);
    });

    it('POST /:id/inspections 写 inspection_logged event', async () => {
      await authPost(`/api/v1/robot-manager/${robotId}/inspections`, {
        inspectionNo: 1,
        issue: '电池接触不良',
        issueTagCode: 'BATTERY_ISSUE',
      }).expect(201);
      const events = await auth(`/api/v1/robot-manager/${robotId}/events`).expect(200);
      expect(
        (events.body.data as any[]).some((e: any) => e.eventType === 'inspection_logged'),
      ).toBe(true);
    });

    it('POST /:id/logistics-legs', async () => {
      await authPost(`/api/v1/robot-manager/${robotId}/logistics-legs`, {
        legNo: 1,
        logisticsStatus: 'IN_TRANSIT',
        fromLocationId: locationId,
        toLocationId: locationId,
      }).expect(201);
      const legs = await auth(`/api/v1/robot-manager/${robotId}/logistics-legs`).expect(200);
      expect(legs.body.data).toHaveLength(1);
    });

    it('PUT /:id/readiness 10 配件齐齐 → readiness_completed event', async () => {
      await authPut(`/api/v1/robot-manager/${robotId}/readiness`, {
        hasRobot: true,
        hasBattery: true,
        hasRemote: true,
        hasUsaPowerCable: true,
        hasCnCableWithAdapter: true,
        hasPowerSupply: true,
        hasChargingDock: true,
        hasFootPads: true,
        hasToolsKit: true,
        hasPaperwork: true,
        specialistId: userId,
      }).expect(200);
      const events = await auth(`/api/v1/robot-manager/${robotId}/events`).expect(200);
      expect(
        (events.body.data as any[]).some((e: any) => e.eventType === 'readiness_completed'),
      ).toBe(true);
    });

    it('PUT /:id/compliance upsert', async () => {
      await authPut(`/api/v1/robot-manager/${robotId}/compliance`, {
        stickerStatus: 'COMPLETE',
        fccStatus: 'CLEARED',
        complianceNotes: '已清关',
      }).expect(200);
      const cc = await auth(`/api/v1/robot-manager/${robotId}/compliance`).expect(200);
      expect(cc.body.data.stickerStatus).toBe('COMPLETE');
    });
  });
});
