/**
 * L1 集成测试 — 审批人解析失败 → 流程挂起 + 管理员介入恢复（ERR-20260501-004 修复锁定）
 *
 * 锁定：
 *   - suspendInstanceForUnresolvedApprovers activity：
 *     ApprovalInstance.status RUNNING → SUSPENDED + endReason='APPROVER_UNRESOLVED'（不设 endTime）
 *     ApprovalNodeInstance.status → PENDING
 *     向 Administrator 发 IN_APP 通知
 *   - resumeInstanceFromSuspension activity：
 *     ApprovalInstance.status SUSPENDED → RUNNING + endReason 清空
 *   - POST /admin/:instanceId/resume-with-approvers controller：
 *     校验状态（只能从 SUSPENDED 恢复）+ 必须提供 approverIds + 调用 sendSignal('resumeWithApprovers')
 *
 * 不在 scope：完整 Temporal workflow 跑 suspend → wait → resume 链路（这是 worker 层面的事，
 * 留给真正的 E2E）。本套件锁的是"修复后 DB 行为正确 + API 校验正确"。
 */

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 { ApprovalActivities } from '@/engines/approval/temporal/activities/approval.activities';
import { NotificationService } from '@/core/messaging/notification/notification.service';
import { TemporalLoggerService } from '@/core/observability/logging/services/temporal-logger.service';
import { createTestApp } from '../../helpers/app.helper';
import { cleanupByPrefix } from '../../helpers/cleanup.helper';
import { createAdminUser } from '../../helpers/factories/user.factory';

describe('approval engine: suspended on unresolved approvers - L1', () => {
  let app: INestApplication;
  let prisma: PrismaService;
  let activities: ApprovalActivities;
  let notificationService: NotificationService;
  let token: string;
  let adminUserId: string;

  beforeAll(async () => {
    process.env.NODE_ENV = 'test';
    app = await createTestApp();
    prisma = app.get<PrismaService>(PrismaService);
    notificationService = app.get<NotificationService>(NotificationService);
    const temporalLogger = app.get<TemporalLoggerService>(TemporalLoggerService);
    activities = new ApprovalActivities(prisma, notificationService, temporalLogger);
  });

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

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

  afterEach(async () => {
    jest.restoreAllMocks();
    // 清理本套件创建的 approval definition 链 + 其下所有 instance/node 通过 cascade
    await prisma.$transaction(async (tx) => {
      // 先清 form 端 instance（FK 指向 form_definition / approval_instance）
      await tx.formInstance.deleteMany({ where: { businessKey: { startsWith: 't_unr_form_' } } });

      const formDefs = await tx.formDefinition.findMany({
        where: { slug: { startsWith: 't_unr_form_' } },
        select: { id: true },
      });
      const fdIds = formDefs.map((d) => d.id);
      if (fdIds.length > 0) {
        await tx.formVersion.deleteMany({ where: { definitionId: { in: fdIds } } });
        await tx.formDefinition.deleteMany({ where: { id: { in: fdIds } } });
      }

      const apDefs = await tx.approvalDefinition.findMany({
        where: { key: { startsWith: 't_unr_apr_' } },
        select: { id: true },
      });
      const apDefIds = apDefs.map((d) => d.id);
      if (apDefIds.length > 0) {
        const versions = await tx.approvalVersion.findMany({
          where: { definitionId: { in: apDefIds } },
          select: { id: true },
        });
        const verIds = versions.map((v) => v.id);
        if (verIds.length > 0) {
          await tx.approvalInstance.deleteMany({ where: { versionId: { in: verIds } } });
        }
        await tx.approvalDefinition.deleteMany({ where: { id: { in: apDefIds } } });
      }
    });
    await cleanupByPrefix(prisma);
  });

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

  /** 造最小 ApprovalDefinition + Version + Instance + NodeInstance */
  async function setupSuspensionScenario(suffix: string) {
    const def = await prisma.approvalDefinition.create({
      data: {
        key: `t_unr_apr_${suffix}`,
        name: `t_unresolved_${suffix}`,
        category: 'TEST',
        createdBy: adminUserId,
      },
    });
    const version = await prisma.approvalVersion.create({
      data: {
        definitionId: def.id,
        version: 1,
        name: `t_unr_v1_${suffix}`,
        processModel: { nodes: [] } as any,
        isDefault: true,
        status: 'DEPLOYED',
      },
    });
    const instance = await prisma.approvalInstance.create({
      data: {
        versionId: version.id,
        businessType: 'FORM_INSTANCE',
        businessId: `t_biz_${suffix}`,
        businessKey: `t_biz_${suffix}`,
        workflowId: `t_wf_unr_${suffix}`,
        workflowRunId: 't_run',
        initiatorId: adminUserId,
        status: 'RUNNING',
      },
    });
    const node = await prisma.approvalNodeInstance.create({
      data: {
        instanceId: instance.id,
        nodeId: `t_node_unr_${suffix}`,
        nodeName: '部门主管',
        nodeType: 'USER_TASK',
        assignees: [],
        status: 'ACTIVE',
      },
    });
    return { instance, node };
  }

  /** 造一条 FormInstance 关联给定 ApprovalInstance，用于验证 approvalStatus 镜像同步 */
  async function attachFormInstance(approvalInstanceId: string, suffix: string) {
    // 创建最小 form definition + version 让 form_instance 能落库
    const def = await prisma.formDefinition.create({
      data: {
        key: `t_unr_form_${suffix}`,
        slug: `t_unr_form_${suffix}`,
        name: `t_unr_form_${suffix}`,
        category: 'TEST',
        defaultLocale: 'zh-CN',
        supportedLocales: ['zh-CN'],
        latestVersion: 1,
        status: 'PUBLISHED',
        requiresApproval: true,
        createdBy: adminUserId,
      },
    });
    const version = await prisma.formVersion.create({
      data: {
        definitionId: def.id,
        version: 1,
        schema: { type: 'object', properties: {} } as any,
        nameI18n: { 'zh-CN': def.name } as any,
        isDefault: true,
        status: 'PUBLISHED',
        createdBy: adminUserId,
      },
    });
    return prisma.formInstance.create({
      data: {
        formDefinitionId: def.id,
        formVersionId: version.id,
        formKey: def.key,
        formVersion: 1,
        businessKey: `t_unr_form_${suffix}_bk`,
        data: {} as any,
        status: 'PENDING_APPROVAL',
        regionId: 'CN',
        createdBy: adminUserId,
        approvalInstanceId,
        approvalStatus: 'RUNNING',
      },
    });
  }

  describe('suspendInstanceForUnresolvedApprovers activity', () => {
    it('SUSPENDED 状态 + endReason=APPROVER_UNRESOLVED + 不设 endTime + 节点 PENDING + 通知 admin + FormInstance.approvalStatus 同步', async () => {
      const suffix = `${Date.now()}_act`;
      const { instance, node } = await setupSuspensionScenario(suffix);
      const formInst = await attachFormInstance(instance.id, suffix);

      const sendSpy = jest.spyOn(notificationService, 'send').mockResolvedValue(undefined as any);

      await activities.suspendInstanceForUnresolvedApprovers({
        instanceId: instance.id,
        nodeInstanceId: node.id,
        nodeName: node.nodeName,
        initiatorId: adminUserId,
      });

      const ap = await prisma.approvalInstance.findUnique({ where: { id: instance.id } });
      expect(ap!.status).toBe('SUSPENDED');
      expect(ap!.endReason).toBe('APPROVER_UNRESOLVED');
      expect(ap!.endTime).toBeNull(); // 关键：SUSPENDED 不算终态
      expect(ap!.endComment).toContain('部门主管');

      const ni = await prisma.approvalNodeInstance.findUnique({ where: { id: node.id } });
      expect(ni!.status).toBe('PENDING');
      expect((ni!.result as any)?.reason).toBe('APPROVER_UNRESOLVED');

      // FormInstance.approvalStatus 镜像同步（admin 列表过滤依赖此字段）
      const fi = await prisma.formInstance.findUnique({ where: { id: formInst.id } });
      expect(fi!.approvalStatus).toBe('SUSPENDED');

      // 通知至少向一个 admin 发送（adminUserId 本身就是 Administrator）
      expect(sendSpy).toHaveBeenCalled();
      const calledForAdmin = sendSpy.mock.calls.find((c) => (c[0] as any).to === adminUserId);
      expect(calledForAdmin).toBeDefined();
      const payload = calledForAdmin![0] as any;
      expect(payload.channel).toBe('IN_APP');
      expect(payload.subject).toContain('管理员介入');
      expect(payload.variables.instanceId).toBe(instance.id);
    });
  });

  describe('resumeInstanceFromSuspension activity', () => {
    it('SUSPENDED → RUNNING + endReason/endComment 清空 + FormInstance.approvalStatus 回 RUNNING', async () => {
      const suffix = `${Date.now()}_resume`;
      const { instance, node } = await setupSuspensionScenario(suffix);
      const formInst = await attachFormInstance(instance.id, suffix);
      jest.spyOn(notificationService, 'send').mockResolvedValue(undefined as any);

      await activities.suspendInstanceForUnresolvedApprovers({
        instanceId: instance.id,
        nodeInstanceId: node.id,
        nodeName: node.nodeName,
        initiatorId: adminUserId,
      });

      await activities.resumeInstanceFromSuspension({ instanceId: instance.id });

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

      const fi = await prisma.formInstance.findUnique({ where: { id: formInst.id } });
      expect(fi!.approvalStatus).toBe('RUNNING');
    });
  });

  describe('POST /admin/:instanceId/resume-with-approvers controller', () => {
    it('SUSPENDED 实例 + approverIds → 200 + 发 resumeWithApprovers signal', async () => {
      const suffix = `${Date.now()}_api_ok`;
      const { instance, node } = await setupSuspensionScenario(suffix);
      jest.spyOn(notificationService, 'send').mockResolvedValue(undefined as any);
      await activities.suspendInstanceForUnresolvedApprovers({
        instanceId: instance.id,
        nodeInstanceId: node.id,
        nodeName: node.nodeName,
        initiatorId: adminUserId,
      });

      const signalSpy = jest
        .spyOn(app.get(TemporalService), 'sendSignal')
        .mockResolvedValue(undefined as any);

      const res = await request(app.getHttpServer())
        .post(`/api/v1/approval/admin/${instance.id}/resume-with-approvers`)
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({ approverIds: [adminUserId], reason: 't_admin_resume' })
        .expect(200);

      expect(res.body.data.success).toBe(true);
      expect(res.body.data.action).toBe('ADMIN_RESUME');
      expect(res.body.data.status).toBe('RUNNING');

      expect(signalSpy).toHaveBeenCalledTimes(1);
      const [workflowId, signalName, payload] = signalSpy.mock.calls[0];
      expect(workflowId).toBe(instance.workflowId);
      expect(signalName).toBe('resumeWithApprovers');
      expect((payload as any).approverIds).toEqual([adminUserId]);
      expect((payload as any).resolvedBy).toBe(adminUserId);
    });

    it('RUNNING 状态实例 → 400（不允许恢复非 SUSPENDED 状态）', async () => {
      const suffix = `${Date.now()}_api_running`;
      const { instance } = await setupSuspensionScenario(suffix);
      // 不调 suspend，instance 仍是 RUNNING

      jest.spyOn(app.get(TemporalService), 'sendSignal').mockResolvedValue(undefined as any);

      await request(app.getHttpServer())
        .post(`/api/v1/approval/admin/${instance.id}/resume-with-approvers`)
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({ approverIds: [adminUserId], reason: 't_admin_resume' })
        .expect(400);
    });

    it('approverIds 空数组 → 400', async () => {
      const suffix = `${Date.now()}_api_empty`;
      const { instance, node } = await setupSuspensionScenario(suffix);
      jest.spyOn(notificationService, 'send').mockResolvedValue(undefined as any);
      await activities.suspendInstanceForUnresolvedApprovers({
        instanceId: instance.id,
        nodeInstanceId: node.id,
        nodeName: node.nodeName,
        initiatorId: adminUserId,
      });

      jest.spyOn(app.get(TemporalService), 'sendSignal').mockResolvedValue(undefined as any);

      await request(app.getHttpServer())
        .post(`/api/v1/approval/admin/${instance.id}/resume-with-approvers`)
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({ approverIds: [], reason: 't_admin_resume' })
        .expect(400);
    });
  });
});
