/**
 * L1 集成测试 — form-management 真实审批链路（DB 端到端，Temporal 端 mock 在 seam）
 *
 * 覆盖：
 *   - submit (requiresApproval=true) → DB 端到端：FormInstance DRAFT→PENDING_APPROVAL，
 *     ApprovalInstance 行落库（含 businessType/businessId/initiatorId/businessKey），
 *     FormInstance.approvalInstanceId 写回，approvalStatus=RUNNING。
 *   - withdraw → DB 端到端：FormInstance + ApprovalInstance 双向 WITHDRAWN，
 *     approvalEndTime 写入；temporalService.sendSignal('withdraw', ...) 收到正确参数。
 *   - submit/withdraw 调 temporal.startApprovalWorkflow / sendSignal 时 args 正确。
 *
 * 设计取舍（对照 docs/modules/approval-form-todos/B-l1-integration-baseline.md「剩余」段）：
 *   原计划"真实 Temporal + 测试 namespace + 独立 worker"。实际调研后取舍为**在 Temporal SDK 边界
 *   spy/mock，覆盖所有 DB 状态机**：
 *     - 价值：本测试要验的是业务状态机和 FK 关联，不是 Temporal 自身可靠性
 *     - 成本：起 worker 涉及 NativeConnection + workflowsPath + activities 注入 + 跨测试生命周期
 *       管理 + flaky race，性价比低
 *     - 真正的 workflow 端到端属于 L2/L3 范畴（启 backend + worker + 浏览器/MCP）
 *   workflow 实际跑通的覆盖留给 A bucket（L2 MCP）。
 */

import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { PrismaService } from '@/core/database/prisma/prisma.service';
import { TemporalService } from '@/engines/approval/temporal/temporal.service';
import { createTestApp } from '../../helpers/app.helper';
import { cleanupByPrefix } from '../../helpers/cleanup.helper';
import { createAdminUser } from '../../helpers/factories/user.factory';
import {
  cleanupFormManagementTestData,
  createTestFormDefinition,
  TestFormDefinition,
} from './_helpers';

describe('form-management instance with real approval link - L1 (DB-only)', () => {
  let app: INestApplication;
  let prisma: PrismaService;
  let temporalService: TemporalService;

  let token: string;
  let userId: string;
  let definition: TestFormDefinition;
  let approvalDefKey: string;

  beforeAll(async () => {
    process.env.NODE_ENV = 'test';
    app = await createTestApp();
    prisma = app.get<PrismaService>(PrismaService);
    temporalService = app.get<TemporalService>(TemporalService);
  });

  beforeEach(async () => {
    const suffix = `${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
    const user = await createAdminUser({
      username: `t_fmwa_${suffix}`,
      email: `t_fmwa_${suffix}@example.com`,
      password: 'Admin@123',
      displayName: 't_FmWa User',
    });
    userId = user.id;

    const login = await request(app.getHttpServer())
      .post('/api/v1/auth/login')
      .send({ username: user.username, password: 'Admin@123' })
      .expect(200);
    token = login.body.data.accessToken as string;

    // 造审批流程定义 + ACTIVE 版本（不要 isDefault: true 避免 unique 冲突)
    approvalDefKey = `t_apr_def_${suffix}`;
    const apDef = await prisma.approvalDefinition.create({
      data: {
        key: approvalDefKey,
        name: `t_apr_${suffix}`,
        category: 'TEST',
        createdBy: userId,
      },
    });
    await prisma.approvalVersion.create({
      data: {
        definitionId: apDef.id,
        version: 1,
        name: `t_apr_v1_${suffix}`,
        processModel: { nodes: [] } as any,
        status: 'DEPLOYED',
        deployedAt: new Date(),
        // approval.service.startApproval 用 versions.where: { isDefault: true } 查版本
        isDefault: true,
      },
    });

    definition = await createTestFormDefinition(prisma, {
      prefix: 't_fmwa',
      createdBy: userId,
      requiresApproval: true,
      approvalProcessKey: approvalDefKey,
    });
  });

  afterEach(async () => {
    jest.restoreAllMocks();
    await cleanupFormManagementTestData(prisma);
    await cleanupByPrefix(prisma);
  });

  afterAll(async () => {
    await app.close();
  });

  describe('submit → 启动审批工作流', () => {
    it('FormInstance DRAFT → PENDING_APPROVAL；ApprovalInstance 落库；FormInstance.approvalInstanceId 写回', async () => {
      // mock TemporalService.startApprovalWorkflow 返回固定 runId
      const startSpy = jest
        .spyOn(temporalService, 'startApprovalWorkflow')
        .mockResolvedValue({ runId: 't_run_id_xyz' } as any);

      // 1. 创建草稿
      const createRes = await request(app.getHttpServer())
        .post('/api/v1/form-management/instances')
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({
          formDefinitionId: definition.definitionId,
          formData: { amount: 1000, reason: 't_submit' },
        })
        .expect(201);
      const instanceId = createRes.body.data.formInstance.id as string;

      // 2. submit
      const submitRes = await request(app.getHttpServer())
        .post(`/api/v1/form-management/instances/${instanceId}/submit`)
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({})
        .expect(201);
      expect(submitRes.body.data.status).toBe('PENDING_APPROVAL');
      expect(submitRes.body.data.approvalProcessId).toBeTruthy();

      // 3. DB 端验证
      const after = await prisma.formInstance.findUnique({ where: { id: instanceId } });
      expect(after!.status).toBe('PENDING_APPROVAL');
      expect(after!.approvalInstanceId).toBeTruthy();
      expect(after!.approvalStatus).toBe('RUNNING');
      expect(after!.approvalStartTime).not.toBeNull();
      expect(after!.submittedBy).toBe(userId);
      expect(after!.submittedAt).not.toBeNull();

      const ap = await prisma.approvalInstance.findUnique({
        where: { id: after!.approvalInstanceId! },
      });
      expect(ap).not.toBeNull();
      expect(ap!.businessType).toBe('FORM_INSTANCE');
      expect(ap!.businessId).toBe(instanceId);
      // approval.startApproval 当前用 businessId 作为 businessKey 写入（见 approval.service.ts），
      // 不是 form 端的业务单号。锁定该现状，避免回归。
      expect(ap!.businessKey).toBe(instanceId);
      expect(ap!.initiatorId).toBe(userId);

      // 4. Temporal 边界：startApprovalWorkflow 用对了参数
      expect(startSpy).toHaveBeenCalledTimes(1);
      const callArg = startSpy.mock.calls[0][0];
      expect(callArg.businessType).toBe('FORM_INSTANCE');
      expect(callArg.businessId).toBe(instanceId);
      expect(callArg.initiatorId).toBe(userId);
      // ⚠️ TD-6: variables 不应携带 formData 副本（FormInstance.data 是唯一数据源）
      expect(callArg.variables.formData).toBeUndefined();
      expect(callArg.variables.formKey).toBe(definition.formKey);
    });
  });

  describe('withdraw 真实链路 → 双向 WITHDRAWN + Temporal signal', () => {
    it('PENDING_APPROVAL → withdraw → form/approval 双 WITHDRAWN，sendSignal(workflowId, withdraw, ...) 调过', async () => {
      // mock startApprovalWorkflow 让 submit 成功
      jest
        .spyOn(temporalService, 'startApprovalWorkflow')
        .mockResolvedValue({ runId: 't_run_id_w' } as any);
      // mock sendSignal 让 withdraw 成功（不真发给 Temporal worker）
      const signalSpy = jest
        .spyOn(temporalService, 'sendSignal')
        .mockResolvedValue(undefined as any);

      const createRes = await request(app.getHttpServer())
        .post('/api/v1/form-management/instances')
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({
          formDefinitionId: definition.definitionId,
          formData: { amount: 5000, reason: 't_withdraw' },
        })
        .expect(201);
      const instanceId = createRes.body.data.formInstance.id as string;

      await request(app.getHttpServer())
        .post(`/api/v1/form-management/instances/${instanceId}/submit`)
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({})
        .expect(201);

      const submitted = await prisma.formInstance.findUnique({ where: { id: instanceId } });
      const approvalInstanceId = submitted!.approvalInstanceId!;
      const ap = await prisma.approvalInstance.findUnique({ where: { id: approvalInstanceId } });
      const workflowId = ap!.workflowId;

      const withdrawRes = await request(app.getHttpServer())
        .post(`/api/v1/form-management/instances/${instanceId}/withdraw`)
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({ reason: 't_withdraw_reason' });
      expect([200, 201]).toContain(withdrawRes.status);

      // form 端 WITHDRAWN（PR #208 锁定：不是 CANCELLED）
      const after = await prisma.formInstance.findUnique({ where: { id: instanceId } });
      expect(after!.status).toBe('WITHDRAWN');
      expect(after!.approvalStatus).toBe('WITHDRAWN');
      expect(after!.approvalEndTime).not.toBeNull();

      // approval 端 sendSignal('withdraw', ...) 被 workflowId 命中
      expect(signalSpy).toHaveBeenCalled();
      const withdrawCall = signalSpy.mock.calls.find(
        (c) => c[0] === workflowId && c[1] === 'withdraw',
      );
      expect(withdrawCall).toBeDefined();
      expect((withdrawCall![2] as any).reason).toBe('t_withdraw_reason');
    });
  });

  // ============================================
  // 重提（ERR-20260501-003 修复锁定）
  // ============================================
  describe('resubmit (REJECTED → submit) → 复用 ApprovalInstance ID 重启 workflow', () => {
    it('REJECTED form → submit again → 同一 ApprovalInstance ID 状态重置 RUNNING；不撞 unique 约束', async () => {
      const startSpy = jest
        .spyOn(temporalService, 'startApprovalWorkflow')
        .mockResolvedValue({ runId: 't_run_initial' } as any);

      // 1. 首次 submit
      const createRes = await request(app.getHttpServer())
        .post('/api/v1/form-management/instances')
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({
          formDefinitionId: definition.definitionId,
          formData: { amount: 1000, reason: 't_resubmit_initial' },
        })
        .expect(201);
      const instanceId = createRes.body.data.formInstance.id as string;

      await request(app.getHttpServer())
        .post(`/api/v1/form-management/instances/${instanceId}/submit`)
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({})
        .expect(201);

      const afterFirstSubmit = await prisma.formInstance.findUnique({
        where: { id: instanceId },
      });
      const originalApprovalInstanceId = afterFirstSubmit!.approvalInstanceId!;
      expect(originalApprovalInstanceId).toBeTruthy();

      // 2. 模拟 reject：直接把 form + approval 都标 REJECTED（绕过 Temporal callback）
      await prisma.$transaction(async (tx) => {
        await tx.formInstance.update({
          where: { id: instanceId },
          data: { status: 'REJECTED', approvalStatus: 'REJECTED' },
        });
        await tx.approvalInstance.update({
          where: { id: originalApprovalInstanceId },
          data: {
            status: 'REJECTED',
            endTime: new Date(),
            endReason: 'REJECTED',
          },
        });
      });

      // 3. 重提：调用 submit
      startSpy.mockResolvedValue({ runId: 't_run_resubmit' } as any);
      await request(app.getHttpServer())
        .post(`/api/v1/form-management/instances/${instanceId}/submit`)
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({})
        .expect(201);

      // 4. 验证：ApprovalInstance ID **未变**（复用），但状态已重置
      const afterResubmit = await prisma.formInstance.findUnique({
        where: { id: instanceId },
      });
      expect(afterResubmit!.status).toBe('PENDING_APPROVAL');
      expect(afterResubmit!.approvalInstanceId).toBe(originalApprovalInstanceId);

      const ap = await prisma.approvalInstance.findUnique({
        where: { id: originalApprovalInstanceId },
      });
      expect(ap!.status).toBe('RUNNING'); // ← 关键：从 REJECTED 重置回 RUNNING
      expect(ap!.endTime).toBeNull();
      expect(ap!.endReason).toBeNull();
      // workflowId 应当是新的（含新时间戳），避免 Temporal workflow 重名
      expect(ap!.workflowId).not.toBe(`approval-FORM_INSTANCE-${instanceId}-`);
      expect(ap!.workflowId).toMatch(/^approval-FORM_INSTANCE-/);

      // 5. Temporal startApprovalWorkflow 被调过两次（首提 + 重提）
      expect(startSpy).toHaveBeenCalledTimes(2);
      // 第二次调用是重提，workflowId 不同于第一次
      const firstCallWorkflowId = startSpy.mock.calls[0][0].workflowId;
      const secondCallWorkflowId = startSpy.mock.calls[1][0].workflowId;
      expect(firstCallWorkflowId).not.toBe(secondCallWorkflowId);
    });

    it('WITHDRAWN form → submit again → 同样复用 ApprovalInstance ID', async () => {
      jest
        .spyOn(temporalService, 'startApprovalWorkflow')
        .mockResolvedValue({ runId: 't_run' } as any);
      jest.spyOn(temporalService, 'sendSignal').mockResolvedValue(undefined as any);

      const createRes = await request(app.getHttpServer())
        .post('/api/v1/form-management/instances')
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({
          formDefinitionId: definition.definitionId,
          formData: { amount: 100, reason: 't_resubmit_withdrawn' },
        })
        .expect(201);
      const instanceId = createRes.body.data.formInstance.id as string;

      await request(app.getHttpServer())
        .post(`/api/v1/form-management/instances/${instanceId}/submit`)
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({})
        .expect(201);

      const after1 = await prisma.formInstance.findUnique({ where: { id: instanceId } });
      const originalApId = after1!.approvalInstanceId!;

      // withdraw → form WITHDRAWN + approval WITHDRAWN
      await request(app.getHttpServer())
        .post(`/api/v1/form-management/instances/${instanceId}/withdraw`)
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({ reason: 't_withdraw_for_resubmit' });
      await prisma.approvalInstance.update({
        where: { id: originalApId },
        data: { status: 'WITHDRAWN', endTime: new Date(), endReason: 'WITHDRAWN' },
      });

      // resubmit
      await request(app.getHttpServer())
        .post(`/api/v1/form-management/instances/${instanceId}/submit`)
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({})
        .expect(201);

      const after2 = await prisma.formInstance.findUnique({ where: { id: instanceId } });
      expect(after2!.status).toBe('PENDING_APPROVAL');
      expect(after2!.approvalInstanceId).toBe(originalApId); // 复用

      const ap = await prisma.approvalInstance.findUnique({ where: { id: originalApId } });
      expect(ap!.status).toBe('RUNNING');
    });
  });
});
