import sharp from 'sharp';
import request from 'supertest';
import { DeviceStatus, 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';
import { buildMiniAppMockPayload } from '../helpers/e2e-miniapp-auth.helper.js';

function uniqueIdCard() {
  const suffix = `${Date.now()}${Math.floor(Math.random() * 1000)}`
    .replace(/\D/g, '')
    .slice(-4);
  return `11010119990101${suffix.padStart(4, '0')}`;
}

describe('Patients Controllers (e2e)', () => {
  let ctx: E2EContext;
  let samplePngBuffer: Buffer;

  beforeAll(async () => {
    ctx = await createE2EContext();
    samplePngBuffer = await sharp({
      create: {
        width: 24,
        height: 24,
        channels: 3,
        background: { r: 20, g: 60, b: 120 },
      },
    })
      .png()
      .toBuffer();
  });

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

  async function uploadEngineerProof() {
    const response = await request(ctx.app.getHttpServer())
      .post('/b/uploads')
      .set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`)
      .attach('file', samplePngBuffer, {
        filename: 'patient-task-proof.png',
        contentType: 'image/png',
      });

    expectSuccessEnvelope(response, 201);
    return response.body.data as {
      id: number;
      type: 'IMAGE' | 'VIDEO';
      url: string;
      originalName?: string;
      fileName?: string;
    };
  }

  async function loginPatientByPhone(phone: string, openId?: string) {
    const response = await request(ctx.app.getHttpServer())
      .post('/auth/miniapp/c/phone-login')
      .send(
        buildMiniAppMockPayload(
          phone,
          openId ?? uniqueSeedValue('patient-openid'),
        ),
      );

    expectSuccessEnvelope(response, 201);
    return response.body.data.accessToken as string;
  }

  describe('GET /b/patients', () => {
    it('成功：按角色返回正确可见性范围', async () => {
      const systemAdminResponse = await request(ctx.app.getHttpServer())
        .get('/b/patients')
        .query({ hospitalId: ctx.fixtures.hospitalAId })
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
      expectSuccessEnvelope(systemAdminResponse, 200);
      const systemPatientIds = (
        systemAdminResponse.body.data as Array<{ id: number }>
      ).map((item) => item.id);
      expect(systemPatientIds).toEqual(
        expect.arrayContaining([
          ctx.fixtures.patients.patientA1Id,
          ctx.fixtures.patients.patientA2Id,
          ctx.fixtures.patients.patientA3Id,
        ]),
      );

      const patientA1 = (
        systemAdminResponse.body.data as Array<{
          id: number;
          primaryDisease: string | null;
          hydrocephalusTypes: string[];
          surgeryDate: string | null;
          currentPressure: string | null;
          initialPressure: string | null;
        }>
      ).find((item) => item.id === ctx.fixtures.patients.patientA1Id);
      expect(patientA1).toBeDefined();
      expect(patientA1?.primaryDisease).toBeTruthy();
      expect(Array.isArray(patientA1?.hydrocephalusTypes)).toBe(true);
      expect(patientA1?.surgeryDate).toBeTruthy();
      expect(patientA1?.currentPressure).toBeTruthy();
      expect(patientA1?.initialPressure).toBeTruthy();

      const hospitalAdminResponse = await request(ctx.app.getHttpServer())
        .get('/b/patients')
        .set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`);
      expectSuccessEnvelope(hospitalAdminResponse, 200);
      const hospitalPatientIds = (
        hospitalAdminResponse.body.data as Array<{ id: number }>
      ).map((item) => item.id);
      expect(hospitalPatientIds).toEqual(
        expect.arrayContaining([
          ctx.fixtures.patients.patientA1Id,
          ctx.fixtures.patients.patientA2Id,
          ctx.fixtures.patients.patientA3Id,
        ]),
      );

      const directorResponse = await request(ctx.app.getHttpServer())
        .get('/b/patients')
        .set('Authorization', `Bearer ${ctx.tokens[Role.DIRECTOR]}`);
      expectSuccessEnvelope(directorResponse, 200);
      const directorPatientIds = (
        directorResponse.body.data as Array<{ id: number }>
      ).map((item) => item.id);
      expect(directorPatientIds).toEqual(
        expect.arrayContaining([
          ctx.fixtures.patients.patientA1Id,
          ctx.fixtures.patients.patientA2Id,
        ]),
      );
      expect(directorPatientIds).not.toContain(
        ctx.fixtures.patients.patientA3Id,
      );

      const leaderResponse = await request(ctx.app.getHttpServer())
        .get('/b/patients')
        .set('Authorization', `Bearer ${ctx.tokens[Role.LEADER]}`);
      expectSuccessEnvelope(leaderResponse, 200);
      const leaderPatientIds = (
        leaderResponse.body.data as Array<{ id: number }>
      ).map((item) => item.id);
      expect(leaderPatientIds).toEqual(
        expect.arrayContaining([
          ctx.fixtures.patients.patientA1Id,
          ctx.fixtures.patients.patientA2Id,
        ]),
      );
      expect(leaderPatientIds).not.toContain(ctx.fixtures.patients.patientA3Id);

      const doctorResponse = await request(ctx.app.getHttpServer())
        .get('/b/patients')
        .set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`);
      expectSuccessEnvelope(doctorResponse, 200);
      const doctorPatientIds = (
        doctorResponse.body.data as Array<{ id: number }>
      ).map((item) => item.id);
      expect(doctorPatientIds).toContain(ctx.fixtures.patients.patientA1Id);
      expect(doctorPatientIds).not.toContain(ctx.fixtures.patients.patientA2Id);
      expect(doctorPatientIds).not.toContain(ctx.fixtures.patients.patientA3Id);

      const engineerResponse = await request(ctx.app.getHttpServer())
        .get('/b/patients')
        .set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`);
      expectErrorEnvelope(engineerResponse, 403, '无权限执行当前操作');
    });

    it('失败：SYSTEM_ADMIN 不传 hospitalId 返回 400', async () => {
      const response = await request(ctx.app.getHttpServer())
        .get('/b/patients')
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);

      expectErrorEnvelope(
        response,
        400,
        '系统管理员查询必须显式传入 hospitalId',
      );
    });

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

          if (role === Role.SYSTEM_ADMIN) {
            req.query({ hospitalId: ctx.fixtures.hospitalAId });
          }

          return req;
        },
        sendWithoutToken: async () =>
          request(ctx.app.getHttpServer()).get('/b/patients'),
      });
    });
  });

  describe('GET /c/patients/my-lifecycle', () => {
    it('成功：患者小程序登录后可按手机号查询自己的生命周期', async () => {
      const patientToken = await loginPatientByPhone(
        '13800002002',
        'seed-family-a2-openid',
      );
      const response = await request(ctx.app.getHttpServer())
        .get('/c/patients/my-lifecycle')
        .set('Authorization', `Bearer ${patientToken}`);

      expectSuccessEnvelope(response, 200);
      expect(response.body.data.phone).toBe('13800002002');
      expect(response.body.data).toHaveProperty('patient');
      expect(response.body.data).not.toHaveProperty('patientCount');
      expect(Array.isArray(response.body.data.lifecycle)).toBe(true);
      const firstEvent = response.body.data.lifecycle[0] as
        | Record<string, unknown>
        | undefined;
      if (firstEvent) {
        expect(firstEvent).not.toHaveProperty('patient');
      }
    });

    it('失败：未登录返回 401', async () => {
      const response = await request(ctx.app.getHttpServer()).get(
        '/c/patients/my-lifecycle',
      );

      expectErrorEnvelope(response, 401, '缺少 Bearer Token');
    });

    it('成功：已存在 C 端账号再次登录后仍可查询', async () => {
      const patientToken = await loginPatientByPhone(
        '13800002002',
        'seed-family-a2-openid',
      );
      const response = await request(ctx.app.getHttpServer())
        .get('/c/patients/my-lifecycle')
        .set('Authorization', `Bearer ${patientToken}`);

      expectSuccessEnvelope(response, 200);
      expect(response.body.data.phone).toBe('13800002002');
    });
  });

  describe('GET /b/patients/:id/lifecycle', () => {
    it('成功：返回顶层 patient 与 lifecycle 结构', async () => {
      const response = await request(ctx.app.getHttpServer())
        .get(`/b/patients/${ctx.fixtures.patients.patientA1Id}/lifecycle`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`);

      expectSuccessEnvelope(response, 200);
      expect(response.body.data).toHaveProperty('patient');
      expect(response.body.data.patient.id).toBe(
        ctx.fixtures.patients.patientA1Id,
      );
      expect(response.body.data).not.toHaveProperty('patientId');
      expect(response.body.data).not.toHaveProperty('patientCount');
      expect(Array.isArray(response.body.data.lifecycle)).toBe(true);
      const firstEvent = response.body.data.lifecycle[0] as
        | Record<string, unknown>
        | undefined;
      if (firstEvent) {
        expect(firstEvent).not.toHaveProperty('patient');
      }
    });

    it('失败：ENGINEER 无权限访问返回 403', async () => {
      const response = await request(ctx.app.getHttpServer())
        .get(`/b/patients/${ctx.fixtures.patients.patientA1Id}/lifecycle`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`);

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

  describe('患者手术录入', () => {
    it('成功：DOCTOR 可创建患者并附带首台手术和植入设备', async () => {
      const response = await request(ctx.app.getHttpServer())
        .post('/b/patients')
        .set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`)
        .send({
          name: '首术患者',
          inpatientNo: uniqueSeedValue('zyh'),
          projectName: '脑积水手术项目',
          phone: uniquePhone(),
          idCard: uniqueIdCard(),
          doctorId: ctx.fixtures.users.doctorAId,
          initialSurgery: {
            surgeryDate: '2026-03-19T08:00:00.000Z',
            surgeryName: '脑室腹腔分流术',
            preOpPressure: 20,
            primaryDisease: '梗阻性脑积水',
            hydrocephalusTypes: ['交通性'],
            devices: [
              {
                implantCatalogId: ctx.fixtures.catalogs.adjustableValveId,
                shuntMode: 'VPS',
                proximalPunctureAreas: ['额角'],
                valvePlacementSites: ['耳后'],
                distalShuntDirection: '腹腔',
                initialPressure: '1',
                implantNotes: '首术植入',
                labelImageUrl:
                  'https://seed.example.com/tests/first-surgery.jpg',
              },
            ],
          },
        });

      expectSuccessEnvelope(response, 201);
      expect(response.body.data.creatorId).toBe(ctx.fixtures.users.doctorAId);
      expect(response.body.data.creator).toEqual(
        expect.objectContaining({
          id: ctx.fixtures.users.doctorAId,
        }),
      );
      expect(response.body.data.createdAt).toBeTruthy();
      expect(response.body.data.shuntSurgeryCount).toBe(1);
      expect(response.body.data.surgeries).toHaveLength(1);
      expect(response.body.data.surgeries[0].surgeonId).toBe(
        ctx.fixtures.users.doctorAId,
      );
      expect(response.body.data.surgeries[0].devices).toHaveLength(1);
      expect(response.body.data.surgeries[0].devices[0].implantModel).toBe(
        'SEED-ADJUSTABLE-VALVE',
      );
    });

    it('成功：新增二次手术时可弃用旧设备且保留旧设备调压历史', async () => {
      const createPatientResponse = await request(ctx.app.getHttpServer())
        .post('/b/patients')
        .set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`)
        .send({
          name: '二次手术患者',
          inpatientNo: uniqueSeedValue('zyh'),
          projectName: '二次手术项目',
          phone: uniquePhone(),
          idCard: uniqueIdCard(),
          doctorId: ctx.fixtures.users.doctorAId,
          initialSurgery: {
            surgeryDate: '2026-03-01T08:00:00.000Z',
            surgeryName: '首次分流术',
            preOpPressure: 18,
            primaryDisease: '出血后脑积水',
            hydrocephalusTypes: ['高压性'],
            devices: [
              {
                implantCatalogId: ctx.fixtures.catalogs.adjustableValveId,
                shuntMode: 'VPS',
                proximalPunctureAreas: ['额角'],
                valvePlacementSites: ['耳后'],
                distalShuntDirection: '腹腔',
                initialPressure: '1',
                implantNotes: '首术设备',
                labelImageUrl:
                  'https://seed.example.com/tests/initial-device.jpg',
              },
            ],
          },
        });
      expectSuccessEnvelope(createPatientResponse, 201);

      const patient = createPatientResponse.body.data as {
        id: number;
        devices: Array<{ id: number }>;
      };
      const oldDeviceId = patient.devices[0].id;

      const publishResponse = await request(ctx.app.getHttpServer())
        .post('/b/tasks/publish')
        .set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`)
        .send({
          items: [
            {
              deviceId: oldDeviceId,
              targetPressure: '1.5',
            },
          ],
        });
      expectSuccessEnvelope(publishResponse, 201);

      const acceptResponse = await request(ctx.app.getHttpServer())
        .post('/b/tasks/accept')
        .set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`)
        .send({ taskId: publishResponse.body.data.id });
      expectSuccessEnvelope(acceptResponse, 201);
      const proof = await uploadEngineerProof();

      const completeResponse = await request(ctx.app.getHttpServer())
        .post('/b/tasks/complete')
        .set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`)
        .send({
          taskId: publishResponse.body.data.id,
          completionMaterials: [
            {
              assetId: proof.id,
              type: proof.type,
              url: proof.url,
              name: proof.originalName || proof.fileName || '调压完成照片',
            },
          ],
        });
      expectSuccessEnvelope(completeResponse, 201);

      const surgeryResponse = await request(ctx.app.getHttpServer())
        .post(`/b/patients/${patient.id}/surgeries`)
        .set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`)
        .send({
          surgeryDate: '2026-03-18T08:00:00.000Z',
          surgeryName: '二次翻修术',
          preOpPressure: 16,
          primaryDisease: '分流功能障碍',
          hydrocephalusTypes: ['交通性', '高压性'],
          abandonedDeviceIds: [oldDeviceId],
          devices: [
            {
              implantCatalogId: ctx.fixtures.catalogs.adjustableValveId,
              shuntMode: 'VPS',
              proximalPunctureAreas: ['枕角'],
              valvePlacementSites: ['耳后'],
              distalShuntDirection: '腹腔',
              initialPressure: '1',
              implantNotes: '二次手术新设备-1',
              labelImageUrl: 'https://seed.example.com/tests/revision-1.jpg',
            },
            {
              implantCatalogId: ctx.fixtures.catalogs.adjustableValveId,
              shuntMode: 'VPS',
              proximalPunctureAreas: ['额角'],
              valvePlacementSites: ['胸前'],
              distalShuntDirection: '胸腔',
              initialPressure: '1.5',
              implantNotes: '二次手术新设备-2',
              labelImageUrl: 'https://seed.example.com/tests/revision-2.jpg',
            },
          ],
        });

      expectSuccessEnvelope(surgeryResponse, 201);
      expect(surgeryResponse.body.data.devices).toHaveLength(2);
      expect(surgeryResponse.body.data.shuntSurgeryCount).toBe(2);

      const oldDevice = await ctx.prisma.device.findUnique({
        where: { id: oldDeviceId },
        include: { taskItems: true },
      });

      expect(oldDevice?.isAbandoned).toBe(true);
      expect(oldDevice?.status).toBe(DeviceStatus.INACTIVE);
      expect(oldDevice?.taskItems).toHaveLength(1);
    });

    it('失败：手术录入设备不允许手工传 currentPressure', async () => {
      const response = await request(ctx.app.getHttpServer())
        .post('/b/patients')
        .set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`)
        .send({
          name: '非法当前压力患者',
          inpatientNo: uniqueSeedValue('zyh'),
          projectName: '非法字段校验',
          phone: uniquePhone(),
          idCard: uniqueIdCard(),
          doctorId: ctx.fixtures.users.doctorAId,
          initialSurgery: {
            surgeryDate: '2026-03-19T08:00:00.000Z',
            surgeryName: '脑室腹腔分流术',
            primaryDisease: '梗阻性脑积水',
            hydrocephalusTypes: ['交通性'],
            devices: [
              {
                implantCatalogId: ctx.fixtures.catalogs.adjustableValveId,
                shuntMode: 'VPS',
                proximalPunctureAreas: ['额角'],
                valvePlacementSites: ['耳后'],
                distalShuntDirection: '腹腔',
                initialPressure: '1',
                currentPressure: '1',
              },
            ],
          },
        });

      expectErrorEnvelope(response, 400, '请求参数不合法');
    });
  });
});
