import request from 'supertest';
import { Role } from '../../../src/generated/prisma/enums.js';
import {
  closeE2EContext,
  createE2EContext,
  type E2EContext,
} from '../helpers/e2e-context.helper.js';
import { assertRoleMatrix } from '../helpers/e2e-matrix.helper.js';
import {
  expectErrorEnvelope,
  expectSuccessEnvelope,
  uniquePhone,
  uniqueSeedValue,
} from '../helpers/e2e-http.helper.js';

describe('UsersController + BUsersController (e2e)', () => {
  let ctx: E2EContext;

  beforeAll(async () => {
    ctx = await createE2EContext();
  });

  afterAll(async () => {
    await closeE2EContext(ctx);
  });

  async function createDoctorUser(token: string) {
    const response = await request(ctx.app.getHttpServer())
      .post('/users')
      .set('Authorization', `Bearer ${token}`)
      .send({
        name: uniqueSeedValue('用户-医生'),
        phone: uniquePhone(),
        password: 'Seed@1234',
        role: Role.DOCTOR,
        hospitalId: ctx.fixtures.hospitalAId,
        departmentId: ctx.fixtures.departmentA1Id,
        groupId: ctx.fixtures.groupA1Id,
        openId: uniqueSeedValue('users-doctor-openid'),
      });

    expectSuccessEnvelope(response, 201);
    return response.body.data as { id: number; name: string };
  }

  async function createLeaderUser(token: string) {
    const response = await request(ctx.app.getHttpServer())
      .post('/users')
      .set('Authorization', `Bearer ${token}`)
      .send({
        name: uniqueSeedValue('用户-组长'),
        phone: uniquePhone(),
        password: 'Seed@1234',
        role: Role.LEADER,
        hospitalId: ctx.fixtures.hospitalAId,
        departmentId: ctx.fixtures.departmentA1Id,
        groupId: ctx.fixtures.groupA1Id,
        openId: uniqueSeedValue('users-leader-openid'),
      });

    expectSuccessEnvelope(response, 201);
    return response.body.data as { id: number; name: string };
  }

  async function createEngineerUser(token: string) {
    const response = await request(ctx.app.getHttpServer())
      .post('/users')
      .set('Authorization', `Bearer ${token}`)
      .send({
        name: uniqueSeedValue('用户-工程师'),
        phone: uniquePhone(),
        password: 'Seed@1234',
        role: Role.ENGINEER,
        hospitalId: ctx.fixtures.hospitalAId,
        openId: uniqueSeedValue('users-engineer-openid'),
      });

    expectSuccessEnvelope(response, 201);
    return response.body.data as { id: number; name: string };
  }

  describe('POST /users', () => {
    it('成功：SYSTEM_ADMIN 可创建用户', async () => {
      await createDoctorUser(ctx.tokens[Role.SYSTEM_ADMIN]);
    });

    it('成功：HOSPITAL_ADMIN 可创建本院医生', async () => {
      await createDoctorUser(ctx.tokens[Role.HOSPITAL_ADMIN]);
    });

    it('成功：HOSPITAL_ADMIN 可创建本院组长', async () => {
      await createLeaderUser(ctx.tokens[Role.HOSPITAL_ADMIN]);
    });

    it('失败：参数校验失败返回 400', async () => {
      const response = await request(ctx.app.getHttpServer())
        .post('/users')
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
        .send({
          name: 'bad-user',
          phone: '123',
          password: 'short',
          role: Role.DOCTOR,
          hospitalId: ctx.fixtures.hospitalAId,
          departmentId: ctx.fixtures.departmentA1Id,
          groupId: ctx.fixtures.groupA1Id,
        });

      expectErrorEnvelope(response, 400, 'phone 必须是合法手机号');
    });

    it('失败：DIRECTOR 创建用户返回 403', async () => {
      const response = await request(ctx.app.getHttpServer())
        .post('/users')
        .set('Authorization', `Bearer ${ctx.tokens[Role.DIRECTOR]}`)
        .send({
          name: uniqueSeedValue('主任创建用户'),
          phone: uniquePhone(),
          password: 'Seed@1234',
          role: Role.DOCTOR,
          hospitalId: ctx.fixtures.hospitalAId,
          departmentId: ctx.fixtures.departmentA1Id,
          groupId: ctx.fixtures.groupA1Id,
        });

      expectErrorEnvelope(response, 403, '无权限执行当前操作');
    });

    it('角色矩阵：SYSTEM_ADMIN/HOSPITAL_ADMIN 可进入业务，其他角色 403，未登录 401', async () => {
      await assertRoleMatrix({
        name: 'POST /users role matrix',
        tokens: ctx.tokens,
        expectedStatusByRole: {
          [Role.SYSTEM_ADMIN]: 400,
          [Role.HOSPITAL_ADMIN]: 400,
          [Role.DIRECTOR]: 403,
          [Role.LEADER]: 403,
          [Role.DOCTOR]: 403,
          [Role.ENGINEER]: 403,
        },
        sendAsRole: async (_role, token) =>
          request(ctx.app.getHttpServer())
            .post('/users')
            .set('Authorization', `Bearer ${token}`)
            .send({}),
        sendWithoutToken: async () =>
          request(ctx.app.getHttpServer()).post('/users').send({}),
      });
    });
  });

  describe('GET /users', () => {
    it('成功：SYSTEM_ADMIN 可查询用户列表', async () => {
      const response = await request(ctx.app.getHttpServer())
        .get('/users')
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);

      expectSuccessEnvelope(response, 200);
      expect(Array.isArray(response.body.data)).toBe(true);
    });

    it('成功：DIRECTOR 可查看本科室下级列表', async () => {
      const response = await request(ctx.app.getHttpServer())
        .get('/users')
        .set('Authorization', `Bearer ${ctx.tokens[Role.DIRECTOR]}`);

      expectSuccessEnvelope(response, 200);
      expect(Array.isArray(response.body.data)).toBe(true);
      expect(response.body.data.length).toBeGreaterThan(0);
      response.body.data.forEach((item: { role: Role; departmentId: number }) => {
        expect([Role.DOCTOR, Role.LEADER]).toContain(item.role);
        expect(item.departmentId).toBe(ctx.fixtures.departmentA1Id);
      });
    });

    it('成功：LEADER 可查看本组医生列表', async () => {
      const response = await request(ctx.app.getHttpServer())
        .get('/users')
        .set('Authorization', `Bearer ${ctx.tokens[Role.LEADER]}`);

      expectSuccessEnvelope(response, 200);
      expect(Array.isArray(response.body.data)).toBe(true);
      expect(response.body.data.length).toBeGreaterThan(0);
      response.body.data.forEach((item: { role: Role; groupId: number }) => {
        expect(item.role).toBe(Role.DOCTOR);
        expect(item.groupId).toBe(ctx.fixtures.groupA1Id);
      });
    });

    it('失败：未登录返回 401', async () => {
      const response = await request(ctx.app.getHttpServer()).get('/users');
      expectErrorEnvelope(response, 401, '缺少 Bearer Token');
    });

    it('角色矩阵：SYSTEM_ADMIN/HOSPITAL_ADMIN/DIRECTOR/LEADER 可访问，其余角色 403，未登录 401', async () => {
      await assertRoleMatrix({
        name: 'GET /users role matrix',
        tokens: ctx.tokens,
        expectedStatusByRole: {
          [Role.SYSTEM_ADMIN]: 200,
          [Role.HOSPITAL_ADMIN]: 200,
          [Role.DIRECTOR]: 200,
          [Role.LEADER]: 200,
          [Role.DOCTOR]: 403,
          [Role.ENGINEER]: 403,
        },
        sendAsRole: async (_role, token) =>
          request(ctx.app.getHttpServer())
            .get('/users')
            .set('Authorization', `Bearer ${token}`),
        sendWithoutToken: async () =>
          request(ctx.app.getHttpServer()).get('/users'),
      });
    });
  });

  describe('GET /users/:id', () => {
    it('成功：SYSTEM_ADMIN 可查询用户详情', async () => {
      const response = await request(ctx.app.getHttpServer())
        .get(`/users/${ctx.fixtures.users.doctorAId}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);

      expectSuccessEnvelope(response, 200);
      expect(response.body.data.id).toBe(ctx.fixtures.users.doctorAId);
    });

    it('失败：查询不存在用户返回 404', async () => {
      const response = await request(ctx.app.getHttpServer())
        .get('/users/99999999')
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);

      expectErrorEnvelope(response, 404, '用户不存在');
    });

    it('成功：DIRECTOR 可查询本科室医生详情', async () => {
      const response = await request(ctx.app.getHttpServer())
        .get(`/users/${ctx.fixtures.users.doctorAId}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.DIRECTOR]}`);

      expectSuccessEnvelope(response, 200);
      expect(response.body.data.id).toBe(ctx.fixtures.users.doctorAId);
      expect(response.body.data.departmentId).toBe(ctx.fixtures.departmentA1Id);
    });

    it('成功：LEADER 可查询本组医生详情', async () => {
      const response = await request(ctx.app.getHttpServer())
        .get(`/users/${ctx.fixtures.users.doctorAId}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.LEADER]}`);

      expectSuccessEnvelope(response, 200);
      expect(response.body.data.id).toBe(ctx.fixtures.users.doctorAId);
      expect(response.body.data.groupId).toBe(ctx.fixtures.groupA1Id);
    });

    it('失败：DIRECTOR 查询跨科室医生返回 403', async () => {
      const response = await request(ctx.app.getHttpServer())
        .get(`/users/${ctx.fixtures.users.doctorA3Id}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.DIRECTOR]}`);

      expectErrorEnvelope(response, 403, '无权限执行当前操作');
    });

    it('失败：LEADER 查询其他小组医生返回 403', async () => {
      const response = await request(ctx.app.getHttpServer())
        .get(`/users/${ctx.fixtures.users.doctorA3Id}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.LEADER]}`);

      expectErrorEnvelope(response, 403, '无权限执行当前操作');
    });

    it('角色矩阵：SYSTEM_ADMIN/HOSPITAL_ADMIN/DIRECTOR/LEADER 可访问，其他角色 403，未登录 401', async () => {
      await assertRoleMatrix({
        name: 'GET /users/:id role matrix',
        tokens: ctx.tokens,
        expectedStatusByRole: {
          [Role.SYSTEM_ADMIN]: 200,
          [Role.HOSPITAL_ADMIN]: 200,
          [Role.DIRECTOR]: 200,
          [Role.LEADER]: 200,
          [Role.DOCTOR]: 403,
          [Role.ENGINEER]: 403,
        },
        sendAsRole: async (_role, token) =>
          request(ctx.app.getHttpServer())
            .get(`/users/${ctx.fixtures.users.doctorAId}`)
            .set('Authorization', `Bearer ${token}`),
        sendWithoutToken: async () =>
          request(ctx.app.getHttpServer()).get(
            `/users/${ctx.fixtures.users.doctorAId}`,
          ),
      });
    });
  });

  describe('PATCH /users/:id', () => {
    it('成功：SYSTEM_ADMIN 可更新用户姓名', async () => {
      const created = await createDoctorUser(ctx.tokens[Role.SYSTEM_ADMIN]);
      const nextName = uniqueSeedValue('更新后医生名');

      const response = await request(ctx.app.getHttpServer())
        .patch(`/users/${created.id}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
        .send({ name: nextName });

      expectSuccessEnvelope(response, 200);
      expect(response.body.data.name).toBe(nextName);
    });

    it('成功：HOSPITAL_ADMIN 可更新本院医生姓名', async () => {
      const created = await createDoctorUser(ctx.tokens[Role.HOSPITAL_ADMIN]);
      const nextName = uniqueSeedValue('主任更新医生名');

      const response = await request(ctx.app.getHttpServer())
        .patch(`/users/${created.id}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
        .send({ name: nextName });

      expectSuccessEnvelope(response, 200);
      expect(response.body.data.name).toBe(nextName);
    });

    it('成功：HOSPITAL_ADMIN 可将本院医生调整为组长', async () => {
      const created = await createDoctorUser(ctx.tokens[Role.HOSPITAL_ADMIN]);

      const response = await request(ctx.app.getHttpServer())
        .patch(`/users/${created.id}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
        .send({ role: Role.LEADER, groupId: ctx.fixtures.groupA1Id });

      expectSuccessEnvelope(response, 200);
      expect(response.body.data.role).toBe(Role.LEADER);
    });

    it('失败：非医生调整科室/小组返回 400', async () => {
      const response = await request(ctx.app.getHttpServer())
        .patch(`/users/${ctx.fixtures.users.engineerAId}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
        .send({
          departmentId: ctx.fixtures.departmentA1Id,
          groupId: ctx.fixtures.groupA1Id,
        });

      expectErrorEnvelope(
        response,
        400,
        '仅医生/主任/组长允许调整科室/小组归属',
      );
    });

    it('失败：DIRECTOR 更新用户返回 403', async () => {
      const response = await request(ctx.app.getHttpServer())
        .patch(`/users/${ctx.fixtures.users.doctorAId}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.DIRECTOR]}`)
        .send({ name: uniqueSeedValue('主任越权更新') });

      expectErrorEnvelope(response, 403, '无权限执行当前操作');
    });

    it('失败：HOSPITAL_ADMIN 不能把用户改成医院管理员', async () => {
      const response = await request(ctx.app.getHttpServer())
        .patch(`/users/${ctx.fixtures.users.doctorAId}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
        .send({ role: Role.HOSPITAL_ADMIN });

      expectErrorEnvelope(response, 403, '医院管理员仅可操作本院非管理员账号');
    });

    it('角色矩阵：SYSTEM_ADMIN/HOSPITAL_ADMIN 可进入业务，其他角色 403，未登录 401', async () => {
      await assertRoleMatrix({
        name: 'PATCH /users/:id role matrix',
        tokens: ctx.tokens,
        expectedStatusByRole: {
          [Role.SYSTEM_ADMIN]: 404,
          [Role.HOSPITAL_ADMIN]: 404,
          [Role.DIRECTOR]: 403,
          [Role.LEADER]: 403,
          [Role.DOCTOR]: 403,
          [Role.ENGINEER]: 403,
        },
        sendAsRole: async (_role, token) =>
          request(ctx.app.getHttpServer())
            .patch('/users/99999999')
            .set('Authorization', `Bearer ${token}`)
            .send({ name: 'matrix-patch' }),
        sendWithoutToken: async () =>
          request(ctx.app.getHttpServer())
            .patch('/users/99999999')
            .send({ name: 'matrix-patch' }),
      });
    });
  });

  describe('DELETE /users/:id', () => {
    it('成功：SYSTEM_ADMIN 可删除用户', async () => {
      const created = await createEngineerUser(ctx.tokens[Role.SYSTEM_ADMIN]);
      const response = await request(ctx.app.getHttpServer())
        .delete(`/users/${created.id}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);

      expectSuccessEnvelope(response, 200);
      expect(response.body.data.id).toBe(created.id);
    });

    it('成功：HOSPITAL_ADMIN 可删除本院医生', async () => {
      const created = await createDoctorUser(ctx.tokens[Role.HOSPITAL_ADMIN]);
      const response = await request(ctx.app.getHttpServer())
        .delete(`/users/${created.id}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`);

      expectSuccessEnvelope(response, 200);
      expect(response.body.data.id).toBe(created.id);
    });

    it('成功：HOSPITAL_ADMIN 可删除本院组长', async () => {
      const created = await createLeaderUser(ctx.tokens[Role.HOSPITAL_ADMIN]);
      const response = await request(ctx.app.getHttpServer())
        .delete(`/users/${created.id}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`);

      expectSuccessEnvelope(response, 200);
      expect(response.body.data.id).toBe(created.id);
    });

    it('失败：存在关联患者/任务时返回 409', async () => {
      const response = await request(ctx.app.getHttpServer())
        .delete(`/users/${ctx.fixtures.users.doctorAId}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);

      expectErrorEnvelope(response, 409, '用户存在关联患者或任务，无法删除');
    });

    it('失败：DIRECTOR 删除用户返回 403', async () => {
      const response = await request(ctx.app.getHttpServer())
        .delete(`/users/${ctx.fixtures.users.doctorA3Id}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.DIRECTOR]}`);

      expectErrorEnvelope(response, 403, '无权限执行当前操作');
    });

    it('失败：HOSPITAL_ADMIN 无法删除本院管理员', async () => {
      const response = await request(ctx.app.getHttpServer())
        .delete(`/users/${ctx.fixtures.users.hospitalAdminAId}`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`);

      expectErrorEnvelope(response, 403, '医院管理员仅可操作本院非管理员账号');
    });

    it('角色矩阵：SYSTEM_ADMIN/HOSPITAL_ADMIN 可进入业务，其他角色 403，未登录 401', async () => {
      await assertRoleMatrix({
        name: 'DELETE /users/:id role matrix',
        tokens: ctx.tokens,
        expectedStatusByRole: {
          [Role.SYSTEM_ADMIN]: 404,
          [Role.HOSPITAL_ADMIN]: 404,
          [Role.DIRECTOR]: 403,
          [Role.LEADER]: 403,
          [Role.DOCTOR]: 403,
          [Role.ENGINEER]: 403,
        },
        sendAsRole: async (_role, token) =>
          request(ctx.app.getHttpServer())
            .delete('/users/99999999')
            .set('Authorization', `Bearer ${token}`),
        sendWithoutToken: async () =>
          request(ctx.app.getHttpServer()).delete('/users/99999999'),
      });
    });
  });

  describe('PATCH /b/users/:id/assign-engineer-hospital', () => {
    it('成功：SYSTEM_ADMIN 可绑定工程师医院', async () => {
      const response = await request(ctx.app.getHttpServer())
        .patch(
          `/b/users/${ctx.fixtures.users.engineerAId}/assign-engineer-hospital`,
        )
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
        .send({ hospitalId: ctx.fixtures.hospitalAId });

      expectSuccessEnvelope(response, 200);
      expect(response.body.data.hospitalId).toBe(ctx.fixtures.hospitalAId);
      expect(response.body.data.role).toBe(Role.ENGINEER);
    });

    it('失败：目标用户不是工程师返回 400', async () => {
      const response = await request(ctx.app.getHttpServer())
        .patch(
          `/b/users/${ctx.fixtures.users.doctorAId}/assign-engineer-hospital`,
        )
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
        .send({ hospitalId: ctx.fixtures.hospitalAId });

      expectErrorEnvelope(response, 400, '目标用户不是工程师');
    });

    it('角色矩阵：仅 SYSTEM_ADMIN 可进入业务，其他角色 403，未登录 401', async () => {
      await assertRoleMatrix({
        name: 'PATCH /b/users/:id/assign-engineer-hospital role matrix',
        tokens: ctx.tokens,
        expectedStatusByRole: {
          [Role.SYSTEM_ADMIN]: 400,
          [Role.HOSPITAL_ADMIN]: 403,
          [Role.DIRECTOR]: 403,
          [Role.LEADER]: 403,
          [Role.DOCTOR]: 403,
          [Role.ENGINEER]: 403,
        },
        sendAsRole: async (_role, token) =>
          request(ctx.app.getHttpServer())
            .patch(
              `/b/users/${ctx.fixtures.users.engineerAId}/assign-engineer-hospital`,
            )
            .set('Authorization', `Bearer ${token}`)
            .send({}),
        sendWithoutToken: async () =>
          request(ctx.app.getHttpServer())
            .patch(
              `/b/users/${ctx.fixtures.users.engineerAId}/assign-engineer-hospital`,
            )
            .send({}),
      });
    });
  });
});
