import {
  BadRequestException,
  ConflictException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { Prisma } from '../../generated/prisma/client.js';
import { DeviceStatus, Role } from '../../generated/prisma/enums.js';
import { PrismaService } from '../../prisma.service.js';
import type { ActorContext } from '../../common/actor-context.js';
import { MESSAGES } from '../../common/messages.js';
import { normalizePressureLabel } from '../../common/pressure-level.util.js';
import { CreatePatientDto } from '../dto/create-patient.dto.js';
import { CreatePatientSurgeryDto } from '../dto/create-patient-surgery.dto.js';
import { PatientQueryDto } from '../dto/patient-query.dto.js';
import { UpdatePatientSurgeryDto } from '../dto/update-patient-surgery.dto.js';
import { UpdatePatientDto } from '../dto/update-patient.dto.js';
import {
  buildPatientLifecyclePayload,
  PATIENT_LIFECYCLE_INCLUDE,
} from '../patient-lifecycle.util.js';
import { normalizePatientIdCard } from '../patient-id-card.util.js';

type PrismaExecutor = Prisma.TransactionClient | PrismaService;

const PATIENT_OWNER_ROLES: Role[] = [Role.DOCTOR, Role.DIRECTOR, Role.LEADER];

const IMPLANT_CATALOG_SELECT = {
  id: true,
  modelCode: true,
  manufacturer: true,
  name: true,
  isValve: true,
  pressureLevels: true,
  isPressureAdjustable: true,
  notes: true,
} as const;

const PATIENT_LIST_INCLUDE = {
  hospital: { select: { id: true, name: true } },
  doctor: { select: { id: true, name: true, role: true } },
  creator: { select: { id: true, name: true, role: true } },
  devices: {
    select: {
      id: true,
      status: true,
      currentPressure: true,
      initialPressure: true,
      isAbandoned: true,
      implantModel: true,
      implantManufacturer: true,
      implantName: true,
      isValve: true,
      isPressureAdjustable: true,
    },
    orderBy: { id: 'desc' },
  },
  surgeries: {
    select: {
      id: true,
      surgeryDate: true,
      surgeryName: true,
      primaryDisease: true,
      hydrocephalusTypes: true,
      surgeonId: true,
      surgeonName: true,
    },
    orderBy: { surgeryDate: 'desc' },
    take: 1,
  },
  _count: {
    select: {
      surgeries: true,
    },
  },
} as const;

const PATIENT_DETAIL_INCLUDE = {
  hospital: { select: { id: true, name: true } },
  doctor: {
    select: {
      id: true,
      name: true,
      role: true,
      hospitalId: true,
      departmentId: true,
      groupId: true,
    },
  },
  creator: {
    select: {
      id: true,
      name: true,
      role: true,
    },
  },
  devices: {
    include: {
      implantCatalog: {
        select: IMPLANT_CATALOG_SELECT,
      },
      surgery: {
        select: {
          id: true,
          surgeryDate: true,
          surgeryName: true,
        },
      },
    },
    orderBy: { id: 'desc' },
  },
  surgeries: {
    include: {
      devices: {
        include: {
          implantCatalog: {
            select: IMPLANT_CATALOG_SELECT,
          },
        },
        orderBy: { id: 'desc' },
      },
      surgeon: {
        select: {
          id: true,
          name: true,
          role: true,
        },
      },
    },
    orderBy: { surgeryDate: 'desc' },
  },
} as const;

const PATIENT_SURGERY_DETAIL_INCLUDE = {
  devices: {
    include: {
      implantCatalog: {
        select: IMPLANT_CATALOG_SELECT,
      },
    },
    orderBy: { id: 'desc' },
  },
} as const;

/**
 * B 端患者服务：承载院内可见性隔离、患者 CRUD 与手术档案录入。
 */
@Injectable()
export class BPatientsService {
  constructor(private readonly prisma: PrismaService) {}

  /**
   * 查询当前角色可见患者列表。
   */
  async findVisiblePatients(actor: ActorContext, query: PatientQueryDto = {}) {
    const hospitalId = this.resolveHospitalId(actor, query.hospitalId);
    const where = this.buildVisiblePatientWhere(actor, hospitalId, query);
    const page = query.page && query.page > 0 ? query.page : 1;
    const pageSize =
      query.pageSize && query.pageSize > 0 && query.pageSize <= 100
        ? query.pageSize
        : 20;

    const [total, patients] = await this.prisma.$transaction([
      this.prisma.patient.count({ where }),
      this.prisma.patient.findMany({
        where,
        include: PATIENT_LIST_INCLUDE,
        orderBy: { id: 'desc' },
        skip: (page - 1) * pageSize,
        take: pageSize,
      }),
    ]);

    return {
      total,
      page,
      pageSize,
      list: patients.map((patient) => this.decoratePatientListItem(patient)),
    };
  }

  /**
   * 查询当前角色可见归属人员列表（医生/主任/组长），用于患者表单选择。
   */
  async findVisibleDoctors(actor: ActorContext, requestedHospitalId?: number) {
    const hospitalId = this.resolveHospitalId(actor, requestedHospitalId);
    return this.prisma.user.findMany({
      where: this.buildVisibleDoctorWhere(actor, hospitalId),
      select: {
        id: true,
        name: true,
        phone: true,
        hospitalId: true,
        departmentId: true,
        groupId: true,
        role: true,
      },
      orderBy: { id: 'desc' },
    });
  }

  /**
   * 创建患者，可选一并创建首台手术及植入设备。
   */
  async createPatient(actor: ActorContext, dto: CreatePatientDto) {
    const doctor = await this.resolveWritableDoctor(actor, dto.doctorId);

    return this.prisma.$transaction(async (tx) => {
      const patient = await tx.patient.create({
        data: {
          name: this.normalizeRequiredString(dto.name, 'name'),
          creatorId: actor.id,
          inpatientNo:
            dto.inpatientNo === undefined
              ? undefined
              : this.normalizeNullableString(dto.inpatientNo, 'inpatientNo'),
          projectName:
            dto.projectName === undefined
              ? undefined
              : this.normalizeNullableString(dto.projectName, 'projectName'),
          phone: this.normalizePhone(dto.phone),
          // 身份证统一做轻量标准化后落库，数据库中保存原始证件号而不是哈希。
          idCard: this.normalizeIdCard(dto.idCard),
          hospitalId: doctor.hospitalId!,
          doctorId: doctor.id,
        },
      });

      if (dto.initialSurgery) {
        await this.createPatientSurgeryRecord(
          tx,
          actor,
          patient.id,
          patient.doctorId,
          dto.initialSurgery,
        );
      }

      const detail = await this.loadPatientDetail(tx, patient.id);
      return this.decoratePatientDetail(detail);
    });
  }

  /**
   * 为患者新增一台手术，并支持弃用旧设备。
   */
  async createPatientSurgery(
    actor: ActorContext,
    patientId: number,
    dto: CreatePatientSurgeryDto,
  ) {
    const patient = await this.findPatientWithScope(patientId);
    this.assertPatientScope(actor, patient);

    return this.prisma.$transaction(async (tx) => {
      const createdSurgery = await this.createPatientSurgeryRecord(
        tx,
        actor,
        patient.id,
        patient.doctorId,
        dto,
      );

      const detail = await this.loadPatientDetail(tx, patient.id);
      const decoratedPatient = this.decoratePatientDetail(detail);
      const created = decoratedPatient.surgeries.find(
        (surgery) => surgery.id === createdSurgery.id,
      );

      if (!created) {
        throw new NotFoundException(MESSAGES.PATIENT.SURGERY_NOT_FOUND);
      }

      return created;
    });
  }

  /**
   * 编辑患者既有手术记录，仅允许修改手术主信息与既有设备信息。
   */
  async updatePatientSurgery(
    actor: ActorContext,
    patientId: number,
    surgeryId: number,
    dto: UpdatePatientSurgeryDto,
  ) {
    const patient = await this.findPatientWithScope(patientId);
    this.assertPatientScope(actor, patient);

    return this.prisma.$transaction(async (tx) => {
      const currentSurgery = await this.findPatientSurgeryForUpdate(
        tx,
        patient.id,
        surgeryId,
      );
      const updatedSurgery = await this.updatePatientSurgeryRecord(
        tx,
        actor,
        patient.id,
        patient.doctorId,
        currentSurgery,
        dto,
      );

      const detail = await this.loadPatientDetail(tx, patient.id);
      const decoratedPatient = this.decoratePatientDetail(detail);
      const updated = decoratedPatient.surgeries.find(
        (surgery) => surgery.id === updatedSurgery.id,
      );

      if (!updated) {
        throw new NotFoundException(MESSAGES.PATIENT.SURGERY_NOT_FOUND);
      }

      return updated;
    });
  }

  /**
   * 查询患者详情。
   */
  async findPatientById(actor: ActorContext, id: number) {
    const patient = await this.findPatientWithScope(id);
    this.assertPatientScope(actor, patient);
    return this.decoratePatientDetail(patient);
  }

  async findPatientLifecycle(actor: ActorContext, id: number) {
    const patient = await this.prisma.patient.findUnique({
      where: { id },
      include: PATIENT_LIFECYCLE_INCLUDE,
    });

    if (!patient) {
      throw new NotFoundException(MESSAGES.PATIENT.NOT_FOUND);
    }

    this.assertPatientScope(actor, patient);

    return buildPatientLifecyclePayload(patient);
  }

  /**
   * 更新患者基础信息。
   */
  async updatePatient(actor: ActorContext, id: number, dto: UpdatePatientDto) {
    const patient = await this.findPatientWithScope(id);
    this.assertPatientScope(actor, patient);

    if (dto.initialSurgery !== undefined) {
      throw new BadRequestException(
        MESSAGES.PATIENT.SURGERY_UPDATE_NOT_SUPPORTED,
      );
    }

    const data: Prisma.PatientUpdateInput = {};
    if (dto.name !== undefined) {
      data.name = this.normalizeRequiredString(dto.name, 'name');
    }
    if (dto.inpatientNo !== undefined) {
      data.inpatientNo = this.normalizeNullableString(
        dto.inpatientNo,
        'inpatientNo',
      );
    }
    if (dto.projectName !== undefined) {
      data.projectName = this.normalizeNullableString(
        dto.projectName,
        'projectName',
      );
    }
    if (dto.phone !== undefined) {
      data.phone = this.normalizePhone(dto.phone);
    }
    if (dto.idCard !== undefined) {
      // 更新时沿用同一标准化逻辑，保证查询条件与落库格式一致。
      data.idCard = this.normalizeIdCard(dto.idCard);
    }
    if (dto.doctorId !== undefined) {
      const doctor = await this.resolveWritableDoctor(actor, dto.doctorId);
      data.doctor = { connect: { id: doctor.id } };
      data.hospital = { connect: { id: doctor.hospitalId! } };
    }

    const updated = await this.prisma.patient.update({
      where: { id: patient.id },
      data,
    });

    const detail = await this.loadPatientDetail(this.prisma, updated.id);
    return this.decoratePatientDetail(detail);
  }

  /**
   * 删除患者。
   */
  async removePatient(actor: ActorContext, id: number) {
    const patient = await this.findPatientWithScope(id);
    this.assertPatientScope(actor, patient);

    try {
      const deleted = await this.prisma.patient.delete({
        where: { id: patient.id },
        include: PATIENT_DETAIL_INCLUDE,
      });
      return this.decoratePatientDetail(deleted);
    } catch (error) {
      if (
        error instanceof Prisma.PrismaClientKnownRequestError &&
        error.code === 'P2003'
      ) {
        throw new ConflictException(MESSAGES.PATIENT.DELETE_CONFLICT);
      }
      throw error;
    }
  }

  /**
   * 查询患者并附带医生组织信息，用于权限判定。
   */
  private async findPatientWithScope(id: number) {
    const patientId = Number(id);
    if (!Number.isInteger(patientId)) {
      throw new BadRequestException('id 必须为整数');
    }

    return this.loadPatientDetail(this.prisma, patientId);
  }

  /**
   * 统一加载患者详情。
   */
  private async loadPatientDetail(prisma: PrismaExecutor, patientId: number) {
    const patient = await prisma.patient.findUnique({
      where: { id: patientId },
      include: PATIENT_DETAIL_INCLUDE,
    });

    if (!patient) {
      throw new NotFoundException(MESSAGES.PATIENT.NOT_FOUND);
    }

    return patient;
  }

  private async findPatientSurgeryForUpdate(
    prisma: PrismaExecutor,
    patientId: number,
    surgeryId: number,
  ) {
    const normalizedSurgeryId = Number(surgeryId);
    if (!Number.isInteger(normalizedSurgeryId)) {
      throw new BadRequestException('surgeryId 必须为整数');
    }

    const surgery = await prisma.patientSurgery.findFirst({
      where: {
        id: normalizedSurgeryId,
        patientId,
      },
      include: {
        devices: {
          select: {
            id: true,
            implantCatalogId: true,
            currentPressure: true,
            taskItems: {
              select: { id: true },
              take: 1,
            },
          },
          orderBy: { id: 'desc' },
        },
      },
    });

    if (!surgery) {
      throw new NotFoundException(MESSAGES.PATIENT.SURGERY_NOT_FOUND);
    }

    return surgery;
  }

  /**
   * 校验当前角色是否可操作该患者。
   */
  private assertPatientScope(
    actor: ActorContext,
    patient: {
      hospitalId: number;
      doctorId: number;
      doctor: { departmentId: number | null; groupId: number | null };
    },
  ) {
    switch (actor.role) {
      case Role.SYSTEM_ADMIN:
        return;
      case Role.HOSPITAL_ADMIN:
        if (!actor.hospitalId || actor.hospitalId !== patient.hospitalId) {
          throw new ForbiddenException(MESSAGES.PATIENT.ROLE_FORBIDDEN);
        }
        return;
      case Role.DIRECTOR:
        if (
          !actor.departmentId ||
          patient.doctor.departmentId !== actor.departmentId
        ) {
          throw new ForbiddenException(MESSAGES.PATIENT.ROLE_FORBIDDEN);
        }
        return;
      case Role.LEADER:
        if (!actor.groupId || patient.doctor.groupId !== actor.groupId) {
          throw new ForbiddenException(MESSAGES.PATIENT.ROLE_FORBIDDEN);
        }
        return;
      case Role.DOCTOR:
        if (patient.doctorId !== actor.id) {
          throw new ForbiddenException(MESSAGES.PATIENT.ROLE_FORBIDDEN);
        }
        return;
      default:
        throw new ForbiddenException(MESSAGES.PATIENT.ROLE_FORBIDDEN);
    }
  }

  /**
   * 校验并返回当前角色可写的医生。
   */
  private async resolveWritableDoctor(actor: ActorContext, doctorId: number) {
    const normalizedDoctorId = Number(doctorId);
    if (!Number.isInteger(normalizedDoctorId)) {
      throw new BadRequestException('doctorId 必须为整数');
    }

    const doctor = await this.prisma.user.findUnique({
      where: { id: normalizedDoctorId },
      select: {
        id: true,
        name: true,
        role: true,
        hospitalId: true,
        departmentId: true,
        groupId: true,
      },
    });

    if (!doctor) {
      throw new NotFoundException(MESSAGES.PATIENT.DOCTOR_NOT_FOUND);
    }
    if (!PATIENT_OWNER_ROLES.includes(doctor.role)) {
      throw new BadRequestException(MESSAGES.PATIENT.DOCTOR_ROLE_REQUIRED);
    }
    if (!doctor.hospitalId) {
      throw new BadRequestException(MESSAGES.PATIENT.DOCTOR_NOT_FOUND);
    }

    const visibleDoctor = await this.prisma.user.findFirst({
      where: {
        id: doctor.id,
        ...this.buildVisibleDoctorWhere(actor, doctor.hospitalId),
      },
      select: { id: true },
    });
    if (!visibleDoctor) {
      throw new ForbiddenException(MESSAGES.PATIENT.DOCTOR_SCOPE_FORBIDDEN);
    }

    return doctor;
  }

  /**
   * 按当前角色构造患者归属人员可选范围。
   */
  private buildVisibleDoctorWhere(actor: ActorContext, hospitalId: number) {
    const where: Prisma.UserWhereInput = {
      role: { in: PATIENT_OWNER_ROLES },
      hospitalId,
    };

    switch (actor.role) {
      case Role.SYSTEM_ADMIN:
      case Role.HOSPITAL_ADMIN:
        return where;
      case Role.DIRECTOR:
        if (!actor.departmentId) {
          throw new BadRequestException(MESSAGES.PATIENT.DEPARTMENT_REQUIRED);
        }
        where.departmentId = actor.departmentId;
        return where;
      case Role.LEADER:
        if (!actor.groupId) {
          throw new BadRequestException(MESSAGES.PATIENT.GROUP_REQUIRED);
        }
        where.groupId = actor.groupId;
        return where;
      case Role.DOCTOR:
        if (!actor.departmentId) {
          throw new BadRequestException(MESSAGES.PATIENT.DEPARTMENT_REQUIRED);
        }
        where.departmentId = actor.departmentId;
        return where;
      default:
        throw new ForbiddenException(MESSAGES.PATIENT.ROLE_FORBIDDEN);
    }
  }

  /**
   * 按角色构造患者可见性查询条件。
   */
  private buildVisiblePatientWhere(
    actor: ActorContext,
    hospitalId: number,
    query: PatientQueryDto = {},
  ) {
    const where: Prisma.PatientWhereInput = { hospitalId };
    switch (actor.role) {
      case Role.DOCTOR:
        where.doctorId = actor.id;
        break;
      case Role.LEADER:
        if (!actor.groupId) {
          throw new BadRequestException(MESSAGES.PATIENT.GROUP_REQUIRED);
        }
        where.doctor = {
          groupId: actor.groupId,
          role: { in: PATIENT_OWNER_ROLES },
        };
        break;
      case Role.DIRECTOR:
        if (!actor.departmentId) {
          throw new BadRequestException(MESSAGES.PATIENT.DEPARTMENT_REQUIRED);
        }
        where.doctor = {
          departmentId: actor.departmentId,
          role: { in: PATIENT_OWNER_ROLES },
        };
        break;
      case Role.HOSPITAL_ADMIN:
      case Role.SYSTEM_ADMIN:
        break;
      default:
        throw new ForbiddenException(MESSAGES.PATIENT.ROLE_FORBIDDEN);
    }

    if (query.doctorId != null) {
      const doctorId = this.toInt(query.doctorId, 'doctorId');
      if (actor.role === Role.DOCTOR && doctorId !== actor.id) {
        throw new ForbiddenException(MESSAGES.PATIENT.DOCTOR_SCOPE_FORBIDDEN);
      }
      where.doctorId = doctorId;
    }

    const keyword = query.keyword?.trim();
    if (keyword) {
      where.OR = [
        {
          name: {
            contains: keyword,
            mode: 'insensitive',
          },
        },
        {
          phone: {
            contains: keyword,
            mode: 'insensitive',
          },
        },
        {
          idCard: {
            contains: keyword,
            mode: 'insensitive',
          },
        },
        {
          inpatientNo: {
            contains: keyword,
            mode: 'insensitive',
          },
        },
        {
          doctor: {
            name: {
              contains: keyword,
              mode: 'insensitive',
            },
          },
        },
        {
          hospital: {
            name: {
              contains: keyword,
              mode: 'insensitive',
            },
          },
        },
        {
          surgeries: {
            some: {
              surgeryName: {
                contains: keyword,
                mode: 'insensitive',
              },
            },
          },
        },
      ];
    }

    return where;
  }

  /**
   * 解析 B 端查询 hospitalId：系统管理员必须显式指定医院。
   */
  private resolveHospitalId(
    actor: ActorContext,
    requestedHospitalId?: number,
  ): number {
    if (actor.role === Role.SYSTEM_ADMIN) {
      const normalizedHospitalId = requestedHospitalId;
      if (
        normalizedHospitalId == null ||
        !Number.isInteger(normalizedHospitalId)
      ) {
        throw new BadRequestException(
          MESSAGES.PATIENT.SYSTEM_ADMIN_HOSPITAL_REQUIRED,
        );
      }
      return normalizedHospitalId;
    }

    if (!actor.hospitalId) {
      throw new BadRequestException(MESSAGES.PATIENT.ACTOR_HOSPITAL_REQUIRED);
    }

    return actor.hospitalId;
  }

  /**
   * 创建患者手术记录并写入设备快照。
   */
  private async createPatientSurgeryRecord(
    prisma: PrismaExecutor,
    actor: ActorContext,
    patientId: number,
    patientDoctorId: number,
    dto: CreatePatientSurgeryDto,
  ) {
    if (!Array.isArray(dto.devices) || dto.devices.length === 0) {
      throw new BadRequestException(MESSAGES.PATIENT.SURGERY_ITEMS_REQUIRED);
    }

    const catalogIds = Array.from(
      new Set(
        dto.devices.map((device) =>
          this.toInt(device.implantCatalogId, 'implantCatalogId'),
        ),
      ),
    );
    const abandonedDeviceIds = Array.from(
      new Set(dto.abandonedDeviceIds ?? []),
    );

    const [catalogMap, latestSurgery, surgeon] = await Promise.all([
      this.resolveImplantCatalogMap(prisma, catalogIds),
      prisma.patientSurgery.findFirst({
        where: { patientId },
        orderBy: { surgeryDate: 'desc' },
        select: { surgeryDate: true },
      }),
      this.resolveWritableDoctor(actor, patientDoctorId),
    ]);

    if (abandonedDeviceIds.length > 0) {
      const devices = await prisma.device.findMany({
        where: {
          id: { in: abandonedDeviceIds },
          patientId,
        },
        select: { id: true },
      });

      if (devices.length !== abandonedDeviceIds.length) {
        throw new ForbiddenException(
          MESSAGES.PATIENT.ABANDON_DEVICE_SCOPE_FORBIDDEN,
        );
      }
    }

    const deviceDrafts = dto.devices.map((device) => {
      const catalog = catalogMap.get(device.implantCatalogId);
      if (!catalog) {
        throw new NotFoundException(MESSAGES.PATIENT.IMPLANT_CATALOG_NOT_FOUND);
      }

      const initialPressure =
        !catalog.isValve || device.initialPressure == null
          ? null
          : this.assertPressureLevelAllowed(
              catalog,
              this.normalizePressureLevel(
                device.initialPressure,
                'initialPressure',
              ),
            );
      const fallbackPressureLevel =
        catalog.isValve && catalog.pressureLevels.length > 0
          ? catalog.pressureLevels[0]
          : '0';
      const currentPressure = catalog.isValve
        ? this.assertPressureLevelAllowed(
            catalog,
            initialPressure ?? fallbackPressureLevel,
          )
        : '0';

      return {
        patient: { connect: { id: patientId } },
        implantCatalog: { connect: { id: catalog.id } },
        currentPressure,
        status: DeviceStatus.ACTIVE,
        implantModel: catalog.modelCode,
        implantManufacturer: catalog.manufacturer,
        implantName: catalog.name,
        isValve: catalog.isValve,
        isPressureAdjustable: catalog.isPressureAdjustable,
        isAbandoned: false,
        shuntMode: this.normalizeRequiredString(device.shuntMode, 'shuntMode'),
        proximalPunctureAreas: this.normalizeStringArray(
          device.proximalPunctureAreas,
          'proximalPunctureAreas',
        ),
        valvePlacementSites: catalog.isValve
          ? this.normalizeStringArray(
              device.valvePlacementSites,
              'valvePlacementSites',
            )
          : this.normalizeOptionalStringArray(
              device.valvePlacementSites,
              'valvePlacementSites',
            ),
        distalShuntDirection: this.normalizeRequiredString(
          device.distalShuntDirection,
          'distalShuntDirection',
        ),
        initialPressure,
        implantNotes:
          device.implantNotes === undefined
            ? undefined
            : this.normalizeNullableString(device.implantNotes, 'implantNotes'),
        labelImageUrl:
          device.labelImageUrl === undefined
            ? undefined
            : this.normalizeNullableString(
                device.labelImageUrl,
                'labelImageUrl',
              ),
      };
    });

    const surgery = await prisma.patientSurgery.create({
      data: {
        patientId,
        surgeryDate: this.normalizeIsoDate(dto.surgeryDate, 'surgeryDate'),
        surgeryName: this.normalizeRequiredString(
          dto.surgeryName,
          'surgeryName',
        ),
        surgeonId: surgeon.id,
        surgeonName: surgeon.name,
        preOpPressure:
          dto.preOpPressure == null
            ? null
            : this.normalizeNonNegativeInteger(
                dto.preOpPressure,
                'preOpPressure',
              ),
        primaryDisease: this.normalizeRequiredString(
          dto.primaryDisease,
          'primaryDisease',
        ),
        hydrocephalusTypes: this.normalizeStringArray(
          dto.hydrocephalusTypes,
          'hydrocephalusTypes',
        ),
        previousShuntSurgeryDate: dto.previousShuntSurgeryDate
          ? this.normalizeIsoDate(
              dto.previousShuntSurgeryDate,
              'previousShuntSurgeryDate',
            )
          : (latestSurgery?.surgeryDate ?? null),
        preOpMaterials:
          dto.preOpMaterials == null
            ? undefined
            : this.normalizePreOpMaterials(dto.preOpMaterials),
        notes:
          dto.notes === undefined
            ? undefined
            : this.normalizeNullableString(dto.notes, 'notes'),
        devices: {
          create: deviceDrafts,
        },
      },
      include: PATIENT_SURGERY_DETAIL_INCLUDE,
    });

    if (abandonedDeviceIds.length > 0) {
      await prisma.device.updateMany({
        where: {
          id: { in: abandonedDeviceIds },
          patientId,
        },
        data: {
          isAbandoned: true,
          status: DeviceStatus.INACTIVE,
        },
      });
    }

    return surgery;
  }

  private async updatePatientSurgeryRecord(
    prisma: PrismaExecutor,
    actor: ActorContext,
    patientId: number,
    patientDoctorId: number,
    currentSurgery: Awaited<
      ReturnType<BPatientsService['findPatientSurgeryForUpdate']>
    >,
    dto: UpdatePatientSurgeryDto,
  ) {
    if (
      Array.isArray(dto.abandonedDeviceIds) &&
      dto.abandonedDeviceIds.length > 0
    ) {
      throw new BadRequestException(
        MESSAGES.PATIENT.SURGERY_ABANDON_UPDATE_NOT_SUPPORTED,
      );
    }

    const existingDeviceMap = new Map(
      currentSurgery.devices.map((device) => [device.id, device]),
    );
    const requestedDeviceIds = dto.devices.map((device, index) =>
      this.toInt(device.id, `devices[${index}].id`),
    );
    const uniqueRequestedIds = Array.from(new Set(requestedDeviceIds));

    if (
      requestedDeviceIds.length !== currentSurgery.devices.length ||
      uniqueRequestedIds.length !== currentSurgery.devices.length ||
      uniqueRequestedIds.some((deviceId) => !existingDeviceMap.has(deviceId))
    ) {
      throw new BadRequestException(
        MESSAGES.PATIENT.SURGERY_DEVICE_SET_UPDATE_NOT_SUPPORTED,
      );
    }

    const catalogIds = Array.from(
      new Set(
        dto.devices.map((device) =>
          this.toInt(device.implantCatalogId, 'implantCatalogId'),
        ),
      ),
    );

    const [catalogMap, surgeon] = await Promise.all([
      this.resolveImplantCatalogMap(prisma, catalogIds),
      this.resolveWritableDoctor(actor, patientDoctorId),
    ]);

    await prisma.patientSurgery.update({
      where: { id: currentSurgery.id },
      data: {
        surgeryDate: this.normalizeIsoDate(dto.surgeryDate, 'surgeryDate'),
        surgeryName: this.normalizeRequiredString(
          dto.surgeryName,
          'surgeryName',
        ),
        surgeonId: surgeon.id,
        surgeonName: surgeon.name,
        preOpPressure:
          dto.preOpPressure == null
            ? null
            : this.normalizeNonNegativeInteger(
                dto.preOpPressure,
                'preOpPressure',
              ),
        primaryDisease: this.normalizeRequiredString(
          dto.primaryDisease,
          'primaryDisease',
        ),
        hydrocephalusTypes: this.normalizeStringArray(
          dto.hydrocephalusTypes,
          'hydrocephalusTypes',
        ),
        previousShuntSurgeryDate: dto.previousShuntSurgeryDate
          ? this.normalizeIsoDate(
              dto.previousShuntSurgeryDate,
              'previousShuntSurgeryDate',
            )
          : null,
        preOpMaterials:
          dto.preOpMaterials == null
            ? Prisma.DbNull
            : this.normalizePreOpMaterials(dto.preOpMaterials),
        notes:
          dto.notes === undefined
            ? null
            : this.normalizeNullableString(dto.notes, 'notes'),
      },
    });

    for (const device of dto.devices) {
      const deviceId = this.toInt(device.id, 'devices.id');
      const currentDevice = existingDeviceMap.get(deviceId);
      if (!currentDevice) {
        throw new BadRequestException(
          MESSAGES.PATIENT.SURGERY_DEVICE_SET_UPDATE_NOT_SUPPORTED,
        );
      }

      const catalogId = this.toInt(device.implantCatalogId, 'implantCatalogId');
      const catalog = catalogMap.get(catalogId);
      if (!catalog) {
        throw new NotFoundException(MESSAGES.PATIENT.IMPLANT_CATALOG_NOT_FOUND);
      }

      const catalogChanged = currentDevice.implantCatalogId !== catalog.id;
      if (catalogChanged && currentDevice.taskItems.length > 0) {
        throw new ConflictException(
          MESSAGES.PATIENT.SURGERY_DEVICE_TASK_CONFLICT,
        );
      }

      const initialPressure =
        !catalog.isValve || device.initialPressure == null
          ? null
          : this.assertPressureLevelAllowed(
              catalog,
              this.normalizePressureLevel(
                device.initialPressure,
                'initialPressure',
              ),
            );
      const currentPressure = catalogChanged
        ? this.resolveUpdatedSurgeryDeviceCurrentPressure(
            currentDevice.currentPressure,
            catalog,
            initialPressure,
          )
        : currentDevice.currentPressure;

      await prisma.device.update({
        where: {
          id: currentDevice.id,
        },
        data: {
          patient: { connect: { id: patientId } },
          surgery: { connect: { id: currentSurgery.id } },
          implantCatalog: { connect: { id: catalog.id } },
          currentPressure,
          implantModel: catalog.modelCode,
          implantManufacturer: catalog.manufacturer,
          implantName: catalog.name,
          isValve: catalog.isValve,
          isPressureAdjustable: catalog.isPressureAdjustable,
          shuntMode: this.normalizeRequiredString(
            device.shuntMode,
            'shuntMode',
          ),
          proximalPunctureAreas: this.normalizeStringArray(
            device.proximalPunctureAreas,
            'proximalPunctureAreas',
          ),
          valvePlacementSites: catalog.isValve
            ? this.normalizeStringArray(
                device.valvePlacementSites,
                'valvePlacementSites',
              )
            : this.normalizeOptionalStringArray(
                device.valvePlacementSites,
                'valvePlacementSites',
              ),
          distalShuntDirection: this.normalizeRequiredString(
            device.distalShuntDirection,
            'distalShuntDirection',
          ),
          initialPressure,
          implantNotes:
            device.implantNotes === undefined
              ? null
              : this.normalizeNullableString(
                  device.implantNotes,
                  'implantNotes',
                ),
          labelImageUrl:
            device.labelImageUrl === undefined
              ? null
              : this.normalizeNullableString(
                  device.labelImageUrl,
                  'labelImageUrl',
                ),
        },
      });
    }

    const updatedSurgery = await prisma.patientSurgery.findUnique({
      where: { id: currentSurgery.id },
      include: PATIENT_SURGERY_DETAIL_INCLUDE,
    });

    if (!updatedSurgery) {
      throw new NotFoundException(MESSAGES.PATIENT.SURGERY_NOT_FOUND);
    }

    return updatedSurgery;
  }

  /**
   * 解析并校验植入物型号字典。
   */
  private async resolveImplantCatalogMap(
    prisma: PrismaExecutor,
    implantCatalogIds: number[],
  ) {
    if (implantCatalogIds.length === 0) {
      return new Map<
        number,
        Awaited<ReturnType<typeof prisma.implantCatalog.findFirst>>
      >();
    }

    const catalogs = await prisma.implantCatalog.findMany({
      where: {
        id: { in: implantCatalogIds },
      },
      select: IMPLANT_CATALOG_SELECT,
    });

    if (catalogs.length !== implantCatalogIds.length) {
      throw new NotFoundException(MESSAGES.PATIENT.IMPLANT_CATALOG_NOT_FOUND);
    }

    return new Map(catalogs.map((catalog) => [catalog.id, catalog]));
  }

  /**
   * 可调压植入物若配置了挡位，录入压力时必须命中其中一项。
   */
  private assertPressureLevelAllowed(
    catalog: {
      isValve: boolean;
      isPressureAdjustable: boolean;
      pressureLevels: string[];
    },
    pressure: string,
  ) {
    if (
      catalog.isValve &&
      catalog.isPressureAdjustable &&
      Array.isArray(catalog.pressureLevels) &&
      catalog.pressureLevels.length > 0 &&
      !catalog.pressureLevels.includes(pressure)
    ) {
      throw new BadRequestException(MESSAGES.DEVICE.PRESSURE_LEVEL_INVALID);
    }

    return pressure;
  }

  private resolveUpdatedSurgeryDeviceCurrentPressure(
    currentPressure: string,
    catalog: {
      isValve: boolean;
      isPressureAdjustable: boolean;
      pressureLevels: string[];
    },
    initialPressure: string | null,
  ) {
    if (!catalog.isValve) {
      return '0';
    }

    const fallbackPressureLevel =
      catalog.pressureLevels.length > 0 ? catalog.pressureLevels[0] : '0';
    const normalizedCurrentPressure = this.normalizePressureLevel(
      currentPressure,
      'currentPressure',
    );

    if (
      catalog.isPressureAdjustable &&
      catalog.pressureLevels.length > 0 &&
      catalog.pressureLevels.includes(normalizedCurrentPressure)
    ) {
      return normalizedCurrentPressure;
    }

    if (!catalog.isPressureAdjustable) {
      return (
        normalizedCurrentPressure || initialPressure || fallbackPressureLevel
      );
    }

    return this.assertPressureLevelAllowed(
      catalog,
      initialPressure ?? fallbackPressureLevel,
    );
  }

  /**
   * 将患者详情补全为前端可直接消费的结构。
   */
  private decoratePatientListItem(
    patient: Prisma.PatientGetPayload<{ include: typeof PATIENT_LIST_INCLUDE }>,
  ) {
    const latestSurgery = patient.surgeries[0] ?? null;
    const currentDevice =
      patient.devices.find(
        (device) =>
          device.status === DeviceStatus.ACTIVE && !device.isAbandoned,
      ) ??
      patient.devices.find((device) => !device.isAbandoned) ??
      patient.devices[0] ??
      null;

    return {
      id: patient.id,
      name: patient.name,
      inpatientNo: patient.inpatientNo,
      phone: patient.phone,
      idCard: patient.idCard,
      hospitalId: patient.hospitalId,
      doctorId: patient.doctorId,
      hospital: patient.hospital,
      doctor: patient.doctor,
      devices: patient.devices,
      primaryDisease: latestSurgery?.primaryDisease ?? null,
      hydrocephalusTypes: latestSurgery?.hydrocephalusTypes ?? [],
      surgeryDate: latestSurgery?.surgeryDate ?? null,
      currentPressure: currentDevice?.currentPressure ?? null,
      initialPressure: currentDevice?.initialPressure ?? null,
      shuntSurgeryCount: patient._count.surgeries,
      latestSurgery,
      activeDeviceCount: patient.devices.filter(
        (device) =>
          device.status === DeviceStatus.ACTIVE && !device.isAbandoned,
      ).length,
      abandonedDeviceCount: patient.devices.filter(
        (device) => device.isAbandoned,
      ).length,
    };
  }

  private decoratePatientDetail(
    patient: Awaited<ReturnType<BPatientsService['findPatientWithScope']>>,
  ) {
    const surgeries = this.decorateSurgeries(patient.surgeries);

    return {
      ...patient,
      surgeries,
      shuntSurgeryCount: surgeries.length,
      latestSurgery: surgeries[0] ?? null,
      activeDeviceCount: patient.devices.filter(
        (device) =>
          device.status === DeviceStatus.ACTIVE && !device.isAbandoned,
      ).length,
      abandonedDeviceCount: patient.devices.filter(
        (device) => device.isAbandoned,
      ).length,
    };
  }

  /**
   * 计算每次手术的自动分流手术次数。
   */
  private decorateSurgeries<
    TSurgery extends {
      id: number;
      surgeryDate: Date;
      devices: Array<{
        id: number;
        status: DeviceStatus;
        isAbandoned: boolean;
      }>;
    },
  >(surgeries: TSurgery[]) {
    const sortedAsc = [...surgeries].sort(
      (left, right) =>
        new Date(left.surgeryDate).getTime() -
        new Date(right.surgeryDate).getTime(),
    );
    const sequenceById = new Map(
      sortedAsc.map((surgery, index) => [surgery.id, index + 1] as const),
    );

    return [...surgeries]
      .sort(
        (left, right) =>
          new Date(right.surgeryDate).getTime() -
          new Date(left.surgeryDate).getTime(),
      )
      .map((surgery) => ({
        ...surgery,
        shuntSurgeryCount: sequenceById.get(surgery.id) ?? surgeries.length,
        activeDeviceCount: surgery.devices.filter(
          (device) =>
            device.status === DeviceStatus.ACTIVE && !device.isAbandoned,
        ).length,
        abandonedDeviceCount: surgery.devices.filter(
          (device) => device.isAbandoned,
        ).length,
      }));
  }

  private normalizeRequiredString(value: unknown, fieldName: string) {
    if (typeof value !== 'string') {
      throw new BadRequestException(`${fieldName} 必须是字符串`);
    }
    const trimmed = value.trim();
    if (!trimmed) {
      throw new BadRequestException(`${fieldName} 不能为空`);
    }
    return trimmed;
  }

  private normalizeNullableString(value: unknown, fieldName: string) {
    if (typeof value !== 'string') {
      throw new BadRequestException(`${fieldName} 必须是字符串`);
    }
    const trimmed = value.trim();
    return trimmed || null;
  }

  private normalizePhone(phone: unknown) {
    const normalized = this.normalizeRequiredString(phone, 'phone');
    if (!/^1\d{10}$/.test(normalized)) {
      throw new BadRequestException('phone 必须是合法手机号');
    }
    return normalized;
  }

  /**
   * 统一整理身份证号，避免空格和末尾 x 大小写带来重复数据。
   */
  private normalizeIdCard(value: unknown) {
    const normalized = this.normalizeRequiredString(value, 'idCard');
    return normalizePatientIdCard(normalized);
  }

  private normalizeIsoDate(value: unknown, fieldName: string) {
    const normalized = this.normalizeRequiredString(value, fieldName);
    const parsed = new Date(normalized);
    if (Number.isNaN(parsed.getTime())) {
      throw new BadRequestException(`${fieldName} 必须是合法日期`);
    }
    return parsed;
  }

  private normalizeNonNegativeInteger(value: unknown, fieldName: string) {
    const parsed = Number(value);
    if (!Number.isInteger(parsed) || parsed < 0) {
      throw new BadRequestException(`${fieldName} 必须是大于等于 0 的整数`);
    }
    return parsed;
  }

  private normalizePressureLevel(value: unknown, fieldName: string) {
    return normalizePressureLabel(value, fieldName);
  }

  private normalizeStringArray(value: unknown, fieldName: string) {
    if (!Array.isArray(value) || value.length === 0) {
      throw new BadRequestException(`${fieldName} 必须为非空数组`);
    }

    return Array.from(
      new Set(
        value.map((item) => this.normalizeRequiredString(item, fieldName)),
      ),
    );
  }

  private normalizeOptionalStringArray(value: unknown, fieldName: string) {
    if (value == null) {
      return [];
    }
    if (!Array.isArray(value) || value.length === 0) {
      return [];
    }

    return Array.from(
      new Set(
        value.map((item) => this.normalizeRequiredString(item, fieldName)),
      ),
    );
  }

  private normalizePreOpMaterials(
    materials: CreatePatientSurgeryDto['preOpMaterials'],
  ): Prisma.InputJsonArray {
    if (!Array.isArray(materials)) {
      throw new BadRequestException('preOpMaterials 必须是数组');
    }

    return materials.map((material) => ({
      type: this.normalizeRequiredString(material.type, 'type'),
      url: this.normalizeRequiredString(material.url, 'url'),
      name:
        material.name === undefined
          ? null
          : this.normalizeNullableString(material.name, 'name'),
    })) as Prisma.InputJsonArray;
  }

  private toInt(value: unknown, fieldName: string) {
    const parsed = Number(value);
    if (!Number.isInteger(parsed) || parsed <= 0) {
      throw new BadRequestException(`${fieldName} 必须为正整数`);
    }
    return parsed;
  }
}
