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 · Templates Controller L1 集成测试
 *
 * 覆盖 5 个 endpoint × HTTP 行为 + 鉴权 + 核心业务规则：
 *   GET    /meeting-attendance/templates              （列模板，需角色权限）
 *   POST   /meeting-attendance/templates              （创建模板）
 *   POST   /meeting-attendance/templates/:id/create-meeting  （从模板建会议）
 *   PUT    /meeting-attendance/templates/:id          （更新模板）
 *   DELETE /meeting-attendance/templates/:id          （删除模板）
 *
 * 关联工单 #341 · Batch 100%
 */
describe('Meeting-attendance · Templates API', () => {
  let app: INestApplication;
  let prisma: PrismaService;

  beforeAll(async () => {
    process.env.NODE_ENV = 'test';
    app = await createTestApp();
    prisma = app.get<PrismaService>(PrismaService);
    // 清前一个 suite 残留的 orphan template（旧测试文件不清 template 表）
    await prisma.meetingTemplate.deleteMany({}).catch(() => undefined);
  });

  afterEach(async () => {
    jest.restoreAllMocks();
    // 先清 meetingTemplate 数据，避免 cleanupDatabase 只清 user 后留下 orphan template
    // → GET 列表 include creator (required relation) 撞孤儿 FK 报 500
    await prisma.meetingTemplate.deleteMany({}).catch(() => undefined);
    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_${s}`,
      email: `t_adm_${s}@example.com`,
      password: 'Admin@123',
      displayName: `Templates 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_${s}`,
      email: `t_mgr_${s}@example.com`,
      password: 'Mgr@123',
      displayName: `Templates 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_${s}`,
      email: `t_emp_${s}@example.com`,
      password: 'Emp@123',
      displayName: `Templates Emp ${s}`,
    });
    const role = await ensureRole('Employee');
    await assignRoleToUser(employee.id, role.id);
    const empToken = await login(employee.username, 'Emp@123');
    return { employee, empToken };
  }

  /** 通过 API 创建一个模板并返回其 id */
  async function createTemplateViaApi(
    token: string,
    overrides: Record<string, any> = {},
  ): Promise<string> {
    const s = suffix();
    const resp = await request(app.getHttpServer())
      .post('/api/v1/meeting-attendance/templates')
      .set('Authorization', `Bearer ${token}`)
      .send({
        name: `t_tpl_${s}`,
        title: `t_meeting_title_${s}`,
        duration: 60,
        location: 'Conference Room A',
        type: 'OFFLINE',
        isPublic: false,
        ...overrides,
      })
      .expect(201);
    return resp.body.template.id as string;
  }

  // ============================================================
  // GET /meeting-attendance/templates
  // ============================================================

  describe('GET /meeting-attendance/templates', () => {
    it('[1] 有角色用户（MeetingManager）→ 200 + 返回 templates 数组', async () => {
      const { mgrToken } = await setupManager();
      const resp = await request(app.getHttpServer())
        .get('/api/v1/meeting-attendance/templates')
        .set('Authorization', `Bearer ${mgrToken}`)
        .expect(200);

      expect(resp.body).toHaveProperty('templates');
      expect(Array.isArray(resp.body.templates)).toBe(true);
    });

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

    it('[3] 有角色用户（Administrator）→ 只能看到自己的模板（非全局）', async () => {
      const { adminUser, adminToken } = await setupAdmin();
      const { mgrToken } = await setupManager();

      // admin 先建一个
      await createTemplateViaApi(adminToken);

      // manager 查看：manager 应该看不到 admin 建的（各用户只能看自己的）
      const resp = await request(app.getHttpServer())
        .get('/api/v1/meeting-attendance/templates')
        .set('Authorization', `Bearer ${mgrToken}`)
        .expect(200);

      const ids = (resp.body.templates as any[]).map((t: any) => t.creatorId);
      expect(ids.every((id) => id !== adminUser.id)).toBe(true);
    });

    it('[4] Employee 无 meeting 角色 → 403 Insufficient permissions', async () => {
      // createTestUser 时只有 Employee 角色，没有 MeetingManager/Administrator
      const s = suffix();
      const noRoleUser = await createTestUser({
        username: `t_norole_${s}`,
        email: `t_norole_${s}@example.com`,
        password: 'No@12345',
        displayName: `NoRole User ${s}`,
      });
      // 不分配任何 meeting 角色
      const token = await login(noRoleUser.username, 'No@12345');

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

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

  // ============================================================
  // POST /meeting-attendance/templates
  // ============================================================

  describe('POST /meeting-attendance/templates', () => {
    it('[5] 创建模板成功：MeetingManager → 201 + 返回 template 对象', async () => {
      const { manager, mgrToken } = await setupManager();
      const s = suffix();

      const resp = await request(app.getHttpServer())
        .post('/api/v1/meeting-attendance/templates')
        .set('Authorization', `Bearer ${mgrToken}`)
        .send({
          name: `t_tpl_${s}`,
          title: `t_title_${s}`,
          duration: 90,
          location: 'Room B',
          type: 'HYBRID',
          isPublic: true,
        })
        .expect(201);

      expect(resp.body).toHaveProperty('template');
      expect(resp.body.template.name).toContain(`t_tpl_${s}`);
      expect(resp.body.template.duration).toBe(90);
      expect(resp.body.template.creatorId).toBe(manager.id);
    });

    it('[6] 无 Authorization → 401', async () => {
      await request(app.getHttpServer())
        .post('/api/v1/meeting-attendance/templates')
        .send({ name: 'x', title: 'y', duration: 30 })
        .expect(401);
    });

    it('[7] 缺少必填字段（name/title/duration 其中一个）→ 400', async () => {
      const { mgrToken } = await setupManager();

      // 缺 duration
      const resp = await request(app.getHttpServer())
        .post('/api/v1/meeting-attendance/templates')
        .set('Authorization', `Bearer ${mgrToken}`)
        .send({ name: 'only-name', title: 'only-title' })
        .expect(400);

      expect(resp.body.error).toMatch(/name.*title.*duration|required/i);
    });

    it('[8] 带 attendeeIds → 201 + 模板 attendee 被写入', async () => {
      const { mgrToken } = await setupManager();
      const { employee } = await setupEmployee();
      const s = suffix();

      const resp = await request(app.getHttpServer())
        .post('/api/v1/meeting-attendance/templates')
        .set('Authorization', `Bearer ${mgrToken}`)
        .send({
          name: `t_tpl_att_${s}`,
          title: `t_title_att_${s}`,
          duration: 45,
          attendeeIds: [employee.id],
        })
        .expect(201);

      const templateId = resp.body.template.id;
      const attendees = await (prisma as any).meetingTemplateAttendee.findMany({
        where: { templateId },
      });
      expect(attendees.length).toBe(1);
      expect(attendees[0].userId).toBe(employee.id);
    });

    it('[9] Employee 角色（无 meeting role）→ 403', async () => {
      const s = suffix();
      const noRoleUser = await createTestUser({
        username: `t_norole2_${s}`,
        email: `t_norole2_${s}@example.com`,
        password: 'No@12345',
        displayName: `NoRole2 ${s}`,
      });
      const token = await login(noRoleUser.username, 'No@12345');

      await request(app.getHttpServer())
        .post('/api/v1/meeting-attendance/templates')
        .set('Authorization', `Bearer ${token}`)
        .send({ name: 'n', title: 't', duration: 30 })
        .expect(403);
    });
  });

  // ============================================================
  // POST /meeting-attendance/templates/:id/create-meeting
  // ============================================================

  describe('POST /meeting-attendance/templates/:id/create-meeting', () => {
    it('[10] 从模板建会议成功 → 201 + 返回 meeting 对象', async () => {
      const { mgrToken } = await setupManager();
      const templateId = await createTemplateViaApi(mgrToken);

      const now = new Date();
      const startTime = new Date(now.getTime() + 60 * 60 * 1000).toISOString();
      const endTime = new Date(now.getTime() + 2 * 60 * 60 * 1000).toISOString();

      const resp = await request(app.getHttpServer())
        .post(`/api/v1/meeting-attendance/templates/${templateId}/create-meeting`)
        .set('Authorization', `Bearer ${mgrToken}`)
        .send({ startTime, endTime })
        .expect(201);

      expect(resp.body).toHaveProperty('meeting');
      expect(resp.body.message).toMatch(/created from template/i);
      expect(resp.body.meeting.id).toBeTruthy();
    });

    it('[11] 无 Authorization → 401', async () => {
      await request(app.getHttpServer())
        .post('/api/v1/meeting-attendance/templates/any-id/create-meeting')
        .send({ startTime: new Date().toISOString(), endTime: new Date().toISOString() })
        .expect(401);
    });

    it('[12] 模板不存在 → 404', async () => {
      const { mgrToken } = await setupManager();
      const now = new Date();
      const startTime = new Date(now.getTime() + 60 * 60 * 1000).toISOString();
      const endTime = new Date(now.getTime() + 2 * 60 * 60 * 1000).toISOString();

      const resp = await request(app.getHttpServer())
        .post('/api/v1/meeting-attendance/templates/non-existent-template-id/create-meeting')
        .set('Authorization', `Bearer ${mgrToken}`)
        .send({ startTime, endTime })
        .expect(404);

      expect(resp.body.error).toMatch(/template does not exist/i);
    });

    it('[13] 缺少 startTime → 400', async () => {
      const { mgrToken } = await setupManager();
      const templateId = await createTemplateViaApi(mgrToken);

      const resp = await request(app.getHttpServer())
        .post(`/api/v1/meeting-attendance/templates/${templateId}/create-meeting`)
        .set('Authorization', `Bearer ${mgrToken}`)
        .send({ endTime: new Date().toISOString() })
        .expect(400);

      expect(resp.body.error).toMatch(/start time.*end time|required/i);
    });

    it('[14] customTitle 覆盖模板 title → meeting.title 使用 customTitle', async () => {
      const { mgrToken } = await setupManager();
      const templateId = await createTemplateViaApi(mgrToken);
      const s = suffix();
      const now = new Date();
      const startTime = new Date(now.getTime() + 60 * 60 * 1000).toISOString();
      const endTime = new Date(now.getTime() + 2 * 60 * 60 * 1000).toISOString();

      const resp = await request(app.getHttpServer())
        .post(`/api/v1/meeting-attendance/templates/${templateId}/create-meeting`)
        .set('Authorization', `Bearer ${mgrToken}`)
        .send({ startTime, endTime, customTitle: `t_custom_${s}` })
        .expect(201);

      expect(resp.body.meeting.title).toContain(`t_custom_${s}`);
    });
  });

  // ============================================================
  // PUT /meeting-attendance/templates/:id
  // ============================================================

  describe('PUT /meeting-attendance/templates/:id', () => {
    it('[15] 创建者更新自己的模板 → 200 + 更新后字段正确', async () => {
      const { mgrToken } = await setupManager();
      const templateId = await createTemplateViaApi(mgrToken);
      const s = suffix();

      const resp = await request(app.getHttpServer())
        .put(`/api/v1/meeting-attendance/templates/${templateId}`)
        .set('Authorization', `Bearer ${mgrToken}`)
        .send({ name: `t_updated_${s}`, duration: 120 })
        .expect(200);

      expect(resp.body.template.name).toContain(`t_updated_${s}`);
      expect(resp.body.template.duration).toBe(120);
    });

    it('[16] 无 Authorization → 401', async () => {
      await request(app.getHttpServer())
        .put('/api/v1/meeting-attendance/templates/any-id')
        .send({ name: 'new' })
        .expect(401);
    });

    it('[17] 模板不存在 → 404', async () => {
      const { mgrToken } = await setupManager();

      const resp = await request(app.getHttpServer())
        .put('/api/v1/meeting-attendance/templates/non-existent-id')
        .set('Authorization', `Bearer ${mgrToken}`)
        .send({ name: 'new-name' })
        .expect(404);

      expect(resp.body.error).toMatch(/template does not exist/i);
    });

    it('[18] 非创建者且非 admin → 403', async () => {
      const { mgrToken } = await setupManager();
      const templateId = await createTemplateViaApi(mgrToken);

      // Employee 有会议角色，但 isMeetingAdminRole('Employee') = false，不是创建者 → 403
      const { empToken } = await setupEmployee();

      const resp = await request(app.getHttpServer())
        .put(`/api/v1/meeting-attendance/templates/${templateId}`)
        .set('Authorization', `Bearer ${empToken}`)
        .send({ name: 'hijack' })
        .expect(403);

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

    it('[19] 传入空 payload（无任何可更新字段）→ 400', async () => {
      const { mgrToken } = await setupManager();
      const templateId = await createTemplateViaApi(mgrToken);

      const resp = await request(app.getHttpServer())
        .put(`/api/v1/meeting-attendance/templates/${templateId}`)
        .set('Authorization', `Bearer ${mgrToken}`)
        .send({})
        .expect(400);

      expect(resp.body.error).toMatch(/payload cannot be empty/i);
    });

    it('[20] duration 为非正数 → 400', async () => {
      const { mgrToken } = await setupManager();
      const templateId = await createTemplateViaApi(mgrToken);

      const resp = await request(app.getHttpServer())
        .put(`/api/v1/meeting-attendance/templates/${templateId}`)
        .set('Authorization', `Bearer ${mgrToken}`)
        .send({ duration: -10 })
        .expect(400);

      expect(resp.body.error).toMatch(/positive number/i);
    });
  });

  // ============================================================
  // DELETE /meeting-attendance/templates/:id
  // ============================================================

  describe('DELETE /meeting-attendance/templates/:id', () => {
    it('[21] 创建者删除自己的模板 → 200 + message', async () => {
      const { mgrToken } = await setupManager();
      const templateId = await createTemplateViaApi(mgrToken);

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

      expect(resp.body.message).toMatch(/deleted successfully/i);

      // 确认 DB 已删除
      const tpl = await (prisma as any).meetingTemplate.findUnique({
        where: { id: templateId },
      });
      expect(tpl).toBeNull();
    });

    it('[22] 无 Authorization → 401', async () => {
      await request(app.getHttpServer())
        .delete('/api/v1/meeting-attendance/templates/any-id')
        .expect(401);
    });

    it('[23] 模板不存在 → 404', async () => {
      const { mgrToken } = await setupManager();

      const resp = await request(app.getHttpServer())
        .delete('/api/v1/meeting-attendance/templates/non-existent-id')
        .set('Authorization', `Bearer ${mgrToken}`)
        .expect(404);

      expect(resp.body.error).toMatch(/template does not exist/i);
    });

    it('[24] 非创建者且非 admin → 403', async () => {
      const { mgrToken } = await setupManager();
      const templateId = await createTemplateViaApi(mgrToken);

      // Employee 有会议角色，但 isMeetingAdminRole('Employee') = false，不是创建者 → 403
      const { empToken } = await setupEmployee();

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

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