import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { PrismaService } from '@/core/database/prisma/prisma.service';
import { createTestApp } from '../../helpers/app.helper';
import { cleanupDatabase } from '../../helpers/cleanup.helper';
import {
  assignRoleToUser,
  createAdminUser,
  createTestUser,
} from '../../helpers/factories';

/**
 * Meeting-attendance · AuditLogs Controller L1 集成测试
 *
 * 覆盖 2 个 endpoint × HTTP 行为 + 鉴权 + 核心业务规则：
 *   GET  /meeting-attendance/audit-logs         （列审计日志，仅 admin/manager）
 *   GET  /meeting-attendance/audit-logs/stats   （统计数据，仅 admin/manager）
 *
 * 关联工单 #341 · Batch 100%
 */
describe('Meeting-attendance · AuditLogs API', () => {
  let app: INestApplication;
  let prisma: PrismaService;

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

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

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

  // ============================================================
  // helpers
  // ============================================================

  function suffix() {
    return `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
  }

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

  async function ensureRole(code: 'Administrator' | 'MeetingManager' | 'Employee') {
    return prisma.role.upsert({
      where: { code },
      create: { code, name: code, enabled: true, isBuiltIn: true },
      update: { enabled: true },
    });
  }

  async function setupAdmin() {
    const s = suffix();
    const adminUser = await createAdminUser({
      username: `t_adm_al_${s}`,
      email: `t_adm_al_${s}@example.com`,
      password: 'Admin@123',
      displayName: `AuditLogs Admin ${s}`,
    });
    const adminToken = await login(adminUser.username, 'Admin@123');
    return { adminUser, adminToken };
  }

  async function setupManager() {
    const s = suffix();
    const manager = await createTestUser({
      username: `t_mgr_al_${s}`,
      email: `t_mgr_al_${s}@example.com`,
      password: 'Mgr@123',
      displayName: `AuditLogs Manager ${s}`,
    });
    const role = await ensureRole('MeetingManager');
    await assignRoleToUser(manager.id, role.id);
    const mgrToken = await login(manager.username, 'Mgr@123');
    return { manager, mgrToken };
  }

  async function setupEmployee() {
    const s = suffix();
    const employee = await createTestUser({
      username: `t_emp_al_${s}`,
      email: `t_emp_al_${s}@example.com`,
      password: 'Emp@123',
      displayName: `AuditLogs Emp ${s}`,
    });
    const role = await ensureRole('Employee');
    await assignRoleToUser(employee.id, role.id);
    const empToken = await login(employee.username, 'Emp@123');
    return { employee, empToken };
  }

  /** 直接向 DB 插入审计日志（绕过 service，用于测试查询/统计）
   *  userId 必须是 DB 中已存在的 User.id（FK 约束）
   */
  async function seedAuditLog(userId: string, overrides: Record<string, any> = {}) {
    return (prisma as any).meetingAttendanceAuditLog.create({
      data: {
        action: 'TEMPLATE_CREATE',
        resource: 'TEMPLATE',
        statusCode: 201,
        userId,
        userEmail: overrides.userEmail ?? `t_seed_${suffix()}@example.com`,
        userName: overrides.userName ?? 't_seed_user',
        userRole: overrides.userRole ?? 'Administrator',
        method: overrides.method ?? 'POST',
        endpoint: overrides.endpoint ?? '/api/v1/meeting-attendance/templates',
        source: 'API',
        ipAddress: '127.0.0.1',
        userAgent: 'jest/test',
        requestBody: JSON.stringify({}),
        changes: JSON.stringify({}),
        ...overrides,
      },
    });
  }

  // ============================================================
  // GET /meeting-attendance/audit-logs
  // ============================================================

  describe('GET /meeting-attendance/audit-logs', () => {
    it('[1] Administrator 列审计日志 → 200 + logs 数组 + pagination', async () => {
      const { adminToken } = await setupAdmin();

      const resp = await request(app.getHttpServer())
        .get('/api/v1/meeting-attendance/audit-logs')
        .set('Authorization', `Bearer ${adminToken}`)
        .expect(200);

      expect(resp.body).toHaveProperty('logs');
      expect(resp.body).toHaveProperty('pagination');
      expect(Array.isArray(resp.body.logs)).toBe(true);
      expect(resp.body.pagination).toMatchObject({
        page: expect.any(Number),
        pageSize: expect.any(Number),
        total: expect.any(Number),
        totalPages: expect.any(Number),
      });
    });

    it('[2] MeetingManager 列审计日志 → 200', async () => {
      const { mgrToken } = await setupManager();

      const resp = await request(app.getHttpServer())
        .get('/api/v1/meeting-attendance/audit-logs')
        .set('Authorization', `Bearer ${mgrToken}`)
        .expect(200);

      expect(resp.body).toHaveProperty('logs');
    });

    it('[3] 无 Authorization → 401', async () => {
      await request(app.getHttpServer())
        .get('/api/v1/meeting-attendance/audit-logs')
        .expect(401);
    });

    it('[4] Employee 角色（非 admin/manager）→ 403 Insufficient permissions', async () => {
      const { empToken } = await setupEmployee();

      const resp = await request(app.getHttpServer())
        .get('/api/v1/meeting-attendance/audit-logs')
        .set('Authorization', `Bearer ${empToken}`)
        .expect(403);

      expect(resp.body.error).toMatch(/insufficient permissions/i);
    });

    it('[5] 分页参数生效：page=1&pageSize=2 → pagination.pageSize=2', async () => {
      const { adminUser, adminToken } = await setupAdmin();
      // seed 3 条日志
      await seedAuditLog(adminUser.id);
      await seedAuditLog(adminUser.id);
      await seedAuditLog(adminUser.id);

      const resp = await request(app.getHttpServer())
        .get('/api/v1/meeting-attendance/audit-logs?page=1&pageSize=2')
        .set('Authorization', `Bearer ${adminToken}`)
        .expect(200);

      expect(resp.body.pagination.pageSize).toBe(2);
      expect(resp.body.logs.length).toBeLessThanOrEqual(2);
    });

    it('[6] action 过滤：只返回匹配 action 的日志', async () => {
      const { adminUser, adminToken } = await setupAdmin();
      const s = suffix();
      const uniqueEmail = `t_filter_${s}@example.com`;
      await seedAuditLog(adminUser.id, { action: 'TEMPLATE_CREATE', userEmail: uniqueEmail });
      await seedAuditLog(adminUser.id, { action: 'MEETING_CREATE', userEmail: `t_other_${s}@example.com` });

      const resp = await request(app.getHttpServer())
        .get('/api/v1/meeting-attendance/audit-logs?action=TEMPLATE_CREATE')
        .set('Authorization', `Bearer ${adminToken}`)
        .expect(200);

      const actions = (resp.body.logs as any[]).map((l: any) => l.action);
      expect(actions.every((a: string) => a === 'TEMPLATE_CREATE')).toBe(true);
    });

    it('[7] 空库（无匹配记录）→ 200 + logs=[] + total=0', async () => {
      const { adminToken } = await setupAdmin();

      // 用不存在的 userEmail 过滤，确保返回空
      const resp = await request(app.getHttpServer())
        .get('/api/v1/meeting-attendance/audit-logs?userEmail=nobody_nonexistent_xyz@nowhere.example')
        .set('Authorization', `Bearer ${adminToken}`)
        .expect(200);

      expect(resp.body.logs).toEqual([]);
      expect(resp.body.pagination.total).toBe(0);
    });
  });

  // ============================================================
  // GET /meeting-attendance/audit-logs/stats
  // ============================================================

  describe('GET /meeting-attendance/audit-logs/stats', () => {
    it('[8] Administrator 获取统计 → 200 + 必要字段', async () => {
      const { adminToken } = await setupAdmin();

      const resp = await request(app.getHttpServer())
        .get('/api/v1/meeting-attendance/audit-logs/stats')
        .set('Authorization', `Bearer ${adminToken}`)
        .expect(200);

      expect(resp.body).toHaveProperty('actionStats');
      expect(resp.body).toHaveProperty('resourceStats');
      expect(resp.body).toHaveProperty('userStats');
      expect(resp.body).toHaveProperty('sourceStats');
      expect(resp.body).toHaveProperty('errorCount');
      expect(resp.body).toHaveProperty('periodDays');
      expect(Array.isArray(resp.body.actionStats)).toBe(true);
    });

    it('[9] 无 Authorization → 401', async () => {
      await request(app.getHttpServer())
        .get('/api/v1/meeting-attendance/audit-logs/stats')
        .expect(401);
    });

    it('[10] Employee 角色 → 403', async () => {
      const { empToken } = await setupEmployee();

      const resp = await request(app.getHttpServer())
        .get('/api/v1/meeting-attendance/audit-logs/stats')
        .set('Authorization', `Bearer ${empToken}`)
        .expect(403);

      expect(resp.body.error).toMatch(/insufficient permissions/i);
    });

    it('[11] 带日期范围参数 → 200 + periodDays 与传入范围对应', async () => {
      const { adminToken } = await setupAdmin();
      const startDate = '2025-01-01';
      const endDate = '2025-01-07';

      const resp = await request(app.getHttpServer())
        .get(`/api/v1/meeting-attendance/audit-logs/stats?startDate=${startDate}&endDate=${endDate}`)
        .set('Authorization', `Bearer ${adminToken}`)
        .expect(200);

      // service 计算：endDate=2025-01-07T23:59:59.999, startDate=2025-01-01T00:00:00.000
      // diffMs ≈ 6.999... 天，Math.ceil(6.999...) = 7，+1 = 8
      expect(resp.body.periodDays).toBe(8);
    });

    it('[12] seeded 日志的 actionStats 中含对应 action 计数', async () => {
      const { adminUser, adminToken } = await setupAdmin();
      const now = new Date();
      // 种入 2 条 TEMPLATE_DELETE
      await seedAuditLog(adminUser.id, { action: 'TEMPLATE_DELETE', createdAt: now });
      await seedAuditLog(adminUser.id, { action: 'TEMPLATE_DELETE', createdAt: now });

      const resp = await request(app.getHttpServer())
        .get('/api/v1/meeting-attendance/audit-logs/stats')
        .set('Authorization', `Bearer ${adminToken}`)
        .expect(200);

      const deleteEntry = (resp.body.actionStats as any[]).find(
        (s: any) => s.action === 'TEMPLATE_DELETE',
      );
      expect(deleteEntry).toBeTruthy();
      expect(deleteEntry.count).toBeGreaterThanOrEqual(2);
    });
  });
});
