/**
 * IAM 权限委托集成测试（PR #138 / 规则 §5.3.14）
 *
 * 覆盖：
 * - create / list mine / revoke 流程
 * - 一级链式委托被拒（核心安全规则）
 * - 时间窗校验
 * - mine 不依赖 delegation:read 权限码
 */
import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { PrismaService } from '@/core/database/prisma/prisma.service';
import { cleanupDatabase } from '../../helpers/cleanup.helper';
import { createTestApp } from '../../helpers/app.helper';
import { createTestUser } from '../../helpers/factories/user.factory';
import { setupIntegrationTest } from '../../helpers/test-setup.helper';

describe('IAM Delegations API Integration Tests', () => {
  let app: INestApplication;
  let prisma: PrismaService;
  let adminToken: string;
  let adminUserId: string;

  beforeAll(async () => {
    app = await createTestApp();
    prisma = app.get<PrismaService>(PrismaService);
  });

  beforeEach(async () => {
    const ctx = await setupIntegrationTest(app, prisma);
    adminToken = ctx.adminToken;
    adminUserId = ctx.adminUser.id;
  });

  afterEach(async () => {
    await cleanupDatabase(prisma);
  });

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

  async function loginAs(username: string, password: string) {
    const r = await request(app.getHttpServer())
      .post('/api/v1/auth/login')
      .send({ username, password })
      .expect(200);
    return r.body.data.accessToken as string;
  }

  describe('POST /iam/delegations - 创建委托', () => {
    it('[API-IAM-DEL-001] admin 创建委托成功，audit_log 记录 CREATE', async () => {
      const toUser = await createTestUser({
        username: `dele_to_${Date.now()}`,
        email: `dele_to_${Date.now()}@e.com`,
        password: 'Test@123',
        status: 'ACTIVE',
        source: 'LOCAL',
      });

      const res = await request(app.getHttpServer())
        .post('/api/v1/iam/delegations')
        .set('Authorization', `Bearer ${adminToken}`)
        .send({
          toUserId: toUser.id,
          resource: '*',
          validFrom: new Date(Date.now() - 60_000).toISOString(),
          validTo: new Date(Date.now() + 86_400_000).toISOString(),
          reason: 'integration test',
        })
        .expect(201);

      expect(res.body.data.toUserId).toBe(toUser.id);
      expect(res.body.data.fromUserId).toBe(adminUserId);
      expect(res.body.data.revokedAt).toBeNull();

      const audits = await prisma.iamAuditLog.findMany({
        where: { resource: 'PermissionDelegation', action: 'CREATE' },
      });
      expect(audits.length).toBeGreaterThanOrEqual(1);
    });

    it('[API-IAM-DEL-002] reason 缺失被拒（class-validator + service 双层）', async () => {
      const toUser = await createTestUser({
        username: `dele_no_reason_${Date.now()}`,
        email: `dele_no_reason_${Date.now()}@e.com`,
        password: 'Test@123',
        status: 'ACTIVE',
        source: 'LOCAL',
      });

      await request(app.getHttpServer())
        .post('/api/v1/iam/delegations')
        .set('Authorization', `Bearer ${adminToken}`)
        .send({
          toUserId: toUser.id,
          validFrom: new Date().toISOString(),
          validTo: new Date(Date.now() + 86_400_000).toISOString(),
          // reason 缺失
        })
        .expect(400);
    });

    it('[API-IAM-DEL-003] validFrom > validTo 被拒', async () => {
      const toUser = await createTestUser({
        username: `dele_bad_window_${Date.now()}`,
        email: `dele_bad_window_${Date.now()}@e.com`,
        password: 'Test@123',
        status: 'ACTIVE',
        source: 'LOCAL',
      });

      await request(app.getHttpServer())
        .post('/api/v1/iam/delegations')
        .set('Authorization', `Bearer ${adminToken}`)
        .send({
          toUserId: toUser.id,
          validFrom: new Date(Date.now() + 86_400_000).toISOString(),
          validTo: new Date().toISOString(),
          reason: 'bad window',
        })
        .expect(400);
    });
  });

  describe('GET /iam/delegations/mine - 列表（无需 delegation:read 权限码）', () => {
    it('[API-IAM-DEL-010] 普通员工（无 delegation:read 权限）能看到自己作为受托人的委托', async () => {
      const empUsername = `dele_emp_${Date.now()}`;
      const empPassword = 'Emp@1234';
      const employee = await createTestUser({
        username: empUsername,
        email: `${empUsername}@e.com`,
        password: empPassword,
        status: 'ACTIVE',
        source: 'LOCAL',
      });

      // admin 给 employee 创建一笔委托
      await request(app.getHttpServer())
        .post('/api/v1/iam/delegations')
        .set('Authorization', `Bearer ${adminToken}`)
        .send({
          toUserId: employee.id,
          resource: '*',
          validFrom: new Date(Date.now() - 60_000).toISOString(),
          validTo: new Date(Date.now() + 86_400_000).toISOString(),
          reason: 'mine list test',
        })
        .expect(201);

      // employee 自己看 mine —— 不该被 delegation:read 挡掉
      const empToken = await loginAs(empUsername, empPassword);
      const res = await request(app.getHttpServer())
        .get('/api/v1/iam/delegations/mine')
        .set('Authorization', `Bearer ${empToken}`)
        .expect(200);

      expect(res.body.data.length).toBeGreaterThanOrEqual(1);
      expect(res.body.data.some((d: { toUserId: string }) => d.toUserId === employee.id)).toBe(true);
    });
  });

  describe('链式委托被拒（一级限制）', () => {
    it('[API-IAM-DEL-020] 已收到委托的用户再发起委托被 service 拒（即使有 manage 权限）', async () => {
      // 建一个有 delegation:manage 权限的中转用户 mid
      const midUsername = `dele_mid_${Date.now()}`;
      const midPassword = 'Mid@1234';
      const mid = await createTestUser({
        username: midUsername,
        email: `${midUsername}@e.com`,
        password: midPassword,
        status: 'ACTIVE',
        source: 'LOCAL',
      });

      // 给 mid 授 delegation:manage：临时建一个角色绑给他
      const manageRole = await prisma.role.upsert({
        where: { code: `__test_dele_manage_${Date.now()}` },
        create: {
          code: `__test_dele_manage_${Date.now()}`,
          name: 'TestDelegationManager',
          description: 'integration test',
          isBuiltIn: false,
          enabled: true,
        },
        update: {},
      });
      const perm = await prisma.permission.upsert({
        where: { resource_action: { resource: 'delegation', action: 'manage' } },
        create: {
          resource: 'delegation',
          action: 'manage',
          description: 'manage delegations',
          module: 'IAM',
        },
        update: {},
      });
      await prisma.rolePermission.upsert({
        where: { roleId_permissionId: { roleId: manageRole.id, permissionId: perm.id } },
        create: { roleId: manageRole.id, permissionId: perm.id },
        update: {},
      });
      await prisma.userRole.create({
        data: { userId: mid.id, roleId: manageRole.id },
      });

      // 第三人 final
      const final = await createTestUser({
        username: `dele_final_${Date.now()}`,
        email: `dele_final_${Date.now()}@e.com`,
        password: 'Test@123',
        status: 'ACTIVE',
        source: 'LOCAL',
      });

      // admin → mid 委托
      await request(app.getHttpServer())
        .post('/api/v1/iam/delegations')
        .set('Authorization', `Bearer ${adminToken}`)
        .send({
          toUserId: mid.id,
          resource: '*',
          validFrom: new Date(Date.now() - 60_000).toISOString(),
          validTo: new Date(Date.now() + 86_400_000).toISOString(),
          reason: 'level 1',
        })
        .expect(201);

      // mid → final 链式委托应被 service 拒
      const midToken = await loginAs(midUsername, midPassword);
      const res = await request(app.getHttpServer())
        .post('/api/v1/iam/delegations')
        .set('Authorization', `Bearer ${midToken}`)
        .send({
          toUserId: final.id,
          resource: '*',
          validFrom: new Date(Date.now() - 60_000).toISOString(),
          validTo: new Date(Date.now() + 86_400_000).toISOString(),
          reason: 'should be rejected',
        });

      expect([400, 403]).toContain(res.status);
      expect(JSON.stringify(res.body).toLowerCase()).toMatch(/chain|链/);
    });
  });

  describe('DELETE /iam/delegations/:id - 撤销', () => {
    it('[API-IAM-DEL-030] 撤销后 revokedAt 写入，audit 记录 UPDATE', async () => {
      const toUser = await createTestUser({
        username: `dele_revoke_${Date.now()}`,
        email: `dele_revoke_${Date.now()}@e.com`,
        password: 'Test@123',
        status: 'ACTIVE',
        source: 'LOCAL',
      });

      const created = await request(app.getHttpServer())
        .post('/api/v1/iam/delegations')
        .set('Authorization', `Bearer ${adminToken}`)
        .send({
          toUserId: toUser.id,
          resource: '*',
          validFrom: new Date(Date.now() - 60_000).toISOString(),
          validTo: new Date(Date.now() + 86_400_000).toISOString(),
          reason: 'will revoke',
        })
        .expect(201);

      const id = created.body.data.id;

      await request(app.getHttpServer())
        .delete(`/api/v1/iam/delegations/${id}`)
        .set('Authorization', `Bearer ${adminToken}`)
        .expect(200);

      const after = await prisma.permissionDelegation.findUnique({ where: { id } });
      expect(after?.revokedAt).not.toBeNull();
    });
  });
});
