import 'dotenv/config';
import { PrismaPg } from '@prisma/adapter-pg';
import { hash } from 'bcrypt';
import prismaClientPackage from '@prisma/client';

const { DictionaryType, DeviceStatus, PrismaClient, Role, TaskStatus } =
  prismaClientPackage;

const connectionString = process.env.DATABASE_URL;
if (!connectionString) {
  throw new Error('DATABASE_URL is required to run seed');
}

const prisma = new PrismaClient({
  adapter: new PrismaPg({ connectionString }),
});

const SEED_PASSWORD_PLAIN = 'Seed@1234';

async function ensureHospital(name) {
  return (
    (await prisma.hospital.findFirst({ where: { name } })) ??
    prisma.hospital.create({ data: { name } })
  );
}

async function ensureDepartment(hospitalId, name) {
  return (
    (await prisma.department.findFirst({
      where: { hospitalId, name },
    })) ??
    prisma.department.create({
      data: { hospitalId, name },
    })
  );
}

async function ensureGroup(departmentId, name) {
  return (
    (await prisma.group.findFirst({
      where: { departmentId, name },
    })) ??
    prisma.group.create({
      data: { departmentId, name },
    })
  );
}

async function upsertUserByScope(data) {
  return prisma.user.upsert({
    where: {
      phone_role_hospitalId: {
        phone: data.phone,
        role: data.role,
        hospitalId: data.hospitalId,
      },
    },
    // 每次重置/补种子时推进失效时间，确保历史 token 无法继续访问。
    update: {
      ...data,
      tokenValidAfter: new Date(),
    },
    create: data,
  });
}

async function ensurePatient({
  hospitalId,
  doctorId,
  creatorId,
  name,
  inpatientNo = null,
  projectName = null,
  phone,
  idCard,
}) {
  const existing = await prisma.patient.findFirst({
    where: {
      hospitalId,
      phone,
      idCard,
    },
  });

  if (existing) {
    if (
      existing.doctorId !== doctorId ||
      existing.creatorId !== creatorId ||
      existing.name !== name ||
      existing.inpatientNo !== inpatientNo ||
      existing.projectName !== projectName
    ) {
      return prisma.patient.update({
        where: { id: existing.id },
        data: { doctorId, creatorId, name, inpatientNo, projectName },
      });
    }
    return existing;
  }

  return prisma.patient.create({
    data: {
      hospitalId,
      doctorId,
      creatorId,
      name,
      inpatientNo,
      projectName,
      phone,
      idCard,
    },
  });
}

async function ensureFamilyMiniAppAccount({
  phone,
  openId = null,
  serviceUid = null,
}) {
  const existing = await prisma.familyMiniAppAccount.findUnique({
    where: { phone },
  });

  if (existing) {
    return prisma.familyMiniAppAccount.update({
      where: { id: existing.id },
      data: {
        openId,
        serviceUid,
        lastLoginAt: new Date(),
      },
    });
  }

  return prisma.familyMiniAppAccount.create({
    data: {
      phone,
      openId,
      serviceUid,
      lastLoginAt: new Date(),
    },
  });
}

async function ensureImplantCatalog({
  modelCode,
  manufacturer,
  name,
  pressureLevels = [],
  isPressureAdjustable = true,
  notes = null,
}) {
  return prisma.implantCatalog.upsert({
    where: { modelCode },
    update: {
      manufacturer,
      name,
      pressureLevels,
      isPressureAdjustable,
      notes,
    },
    create: {
      modelCode,
      manufacturer,
      name,
      pressureLevels,
      isPressureAdjustable,
      notes,
    },
  });
}

async function ensureDictionaryItem({
  type,
  label,
  sortOrder = 0,
  enabled = true,
}) {
  return prisma.dictionaryItem.upsert({
    where: {
      type_label: {
        type,
        label,
      },
    },
    update: {
      sortOrder,
      enabled,
    },
    create: {
      type,
      label,
      sortOrder,
      enabled,
    },
  });
}

async function ensurePatientSurgery({
  patientId,
  surgeryDate,
  surgeryName,
  surgeonName,
  preOpPressure = null,
  primaryDisease,
  hydrocephalusTypes,
  previousShuntSurgeryDate = null,
  preOpMaterials = null,
  notes = null,
}) {
  const normalizedSurgeryDate = new Date(surgeryDate);
  const normalizedPreviousDate = previousShuntSurgeryDate
    ? new Date(previousShuntSurgeryDate)
    : null;

  const existing = await prisma.patientSurgery.findFirst({
    where: {
      patientId,
      surgeryDate: normalizedSurgeryDate,
      surgeryName,
    },
  });

  if (existing) {
    return prisma.patientSurgery.update({
      where: { id: existing.id },
      data: {
        surgeonName,
        preOpPressure,
        primaryDisease,
        hydrocephalusTypes,
        previousShuntSurgeryDate: normalizedPreviousDate,
        preOpMaterials,
        notes,
      },
    });
  }

  return prisma.patientSurgery.create({
    data: {
      patientId,
      surgeryDate: normalizedSurgeryDate,
      surgeryName,
      surgeonName,
      preOpPressure,
      primaryDisease,
      hydrocephalusTypes,
      previousShuntSurgeryDate: normalizedPreviousDate,
      preOpMaterials,
      notes,
    },
  });
}

async function ensureDevice({
  patientId,
  surgeryId,
  implantCatalogId,
  currentPressure,
  status,
  implantModel,
  implantManufacturer,
  implantName,
  isPressureAdjustable,
  isAbandoned,
  shuntMode,
  proximalPunctureAreas,
  valvePlacementSites,
  distalShuntDirection,
  initialPressure,
  implantNotes,
  labelImageUrl,
}) {
  const existing = await prisma.device.findFirst({
    where: {
      patientId,
      surgeryId,
      implantNotes,
    },
  });

  const data = {
    patientId,
    surgeryId,
    implantCatalogId,
    currentPressure,
    status,
    implantModel,
    implantManufacturer,
    implantName,
    isPressureAdjustable,
    isAbandoned,
    shuntMode,
    proximalPunctureAreas,
    valvePlacementSites,
    distalShuntDirection,
    initialPressure,
    implantNotes,
    labelImageUrl,
  };

  if (existing) {
    return prisma.device.update({
      where: { id: existing.id },
      data,
    });
  }

  return prisma.device.create({ data });
}

async function main() {
  const seedPasswordHash = await hash(SEED_PASSWORD_PLAIN, 12);

  const hospitalA = await ensureHospital('Seed Hospital A');
  const hospitalB = await ensureHospital('Seed Hospital B');

  const departmentA1 = await ensureDepartment(hospitalA.id, 'Neurosurgery-A1');
  const departmentA2 = await ensureDepartment(hospitalA.id, 'Cardiology-A2');
  const departmentB1 = await ensureDepartment(hospitalB.id, 'Neurosurgery-B1');

  const groupA1 = await ensureGroup(departmentA1.id, 'Shift-A1');
  const groupA2 = await ensureGroup(departmentA2.id, 'Shift-A2');
  const groupB1 = await ensureGroup(departmentB1.id, 'Shift-B1');

  const systemAdmin = await upsertUserByScope({
    name: 'Seed System Admin',
    phone: '13800001000',
    passwordHash: seedPasswordHash,
    openId: 'seed-system-admin-openid',
    role: Role.SYSTEM_ADMIN,
    hospitalId: null,
    departmentId: null,
    groupId: null,
  });

  const hospitalAdminA = await upsertUserByScope({
    name: 'Seed Hospital Admin A',
    phone: '13800001001',
    passwordHash: seedPasswordHash,
    openId: 'seed-hospital-admin-a-openid',
    role: Role.HOSPITAL_ADMIN,
    hospitalId: hospitalA.id,
    departmentId: null,
    groupId: null,
  });

  await upsertUserByScope({
    name: 'Seed Hospital Admin B',
    phone: '13800001101',
    passwordHash: seedPasswordHash,
    openId: 'seed-hospital-admin-b-openid',
    role: Role.HOSPITAL_ADMIN,
    hospitalId: hospitalB.id,
    departmentId: null,
    groupId: null,
  });

  const directorA = await upsertUserByScope({
    name: 'Seed Director A',
    phone: '13800001002',
    passwordHash: seedPasswordHash,
    openId: 'seed-director-a-openid',
    role: Role.DIRECTOR,
    hospitalId: hospitalA.id,
    departmentId: departmentA1.id,
    groupId: null,
  });

  const leaderA = await upsertUserByScope({
    name: 'Seed Leader A',
    phone: '13800001003',
    passwordHash: seedPasswordHash,
    openId: 'seed-leader-a-openid',
    role: Role.LEADER,
    hospitalId: hospitalA.id,
    departmentId: departmentA1.id,
    groupId: groupA1.id,
  });

  const doctorA = await upsertUserByScope({
    name: 'Seed Doctor A',
    phone: '13800001004',
    passwordHash: seedPasswordHash,
    openId: 'seed-doctor-a-openid',
    role: Role.DOCTOR,
    hospitalId: hospitalA.id,
    departmentId: departmentA1.id,
    groupId: groupA1.id,
  });

  const doctorA2 = await upsertUserByScope({
    name: 'Seed Doctor A2',
    phone: '13800001204',
    passwordHash: seedPasswordHash,
    openId: 'seed-doctor-a2-openid',
    role: Role.DOCTOR,
    hospitalId: hospitalA.id,
    departmentId: departmentA1.id,
    groupId: groupA1.id,
  });

  const doctorA3 = await upsertUserByScope({
    name: 'Seed Doctor A3',
    phone: '13800001304',
    passwordHash: seedPasswordHash,
    openId: 'seed-doctor-a3-openid',
    role: Role.DOCTOR,
    hospitalId: hospitalA.id,
    departmentId: departmentA2.id,
    groupId: groupA2.id,
  });

  const doctorB = await upsertUserByScope({
    name: 'Seed Doctor B',
    phone: '13800001104',
    passwordHash: seedPasswordHash,
    openId: 'seed-doctor-b-openid',
    role: Role.DOCTOR,
    hospitalId: hospitalB.id,
    departmentId: departmentB1.id,
    groupId: groupB1.id,
  });

  const engineerA = await upsertUserByScope({
    name: 'Seed Engineer A',
    phone: '13800001005',
    passwordHash: seedPasswordHash,
    openId: 'seed-engineer-a-openid',
    role: Role.ENGINEER,
    hospitalId: hospitalA.id,
    departmentId: null,
    groupId: null,
  });

  const engineerB = await upsertUserByScope({
    name: 'Seed Engineer B',
    phone: '13800001105',
    passwordHash: seedPasswordHash,
    openId: 'seed-engineer-b-openid',
    role: Role.ENGINEER,
    hospitalId: hospitalB.id,
    departmentId: null,
    groupId: null,
  });

  const dictionarySeeds = {
    [DictionaryType.PRIMARY_DISEASE]: [
      '先天性脑积水',
      '梗阻性脑积水',
      '交通性脑积水',
      '出血后脑积水',
      '肿瘤相关脑积水',
      '外伤后脑积水',
      '感染后脑积水',
      '分流功能障碍',
    ],
    [DictionaryType.HYDROCEPHALUS_TYPE]: [
      '交通性',
      '梗阻性',
      '高压性',
      '正常压力',
      '先天性',
      '继发性',
    ],
    [DictionaryType.SHUNT_MODE]: ['VPS', 'VPLS', 'LPS', '脑室心房分流'],
    [DictionaryType.PROXIMAL_PUNCTURE_AREA]: [
      '额角',
      '枕角',
      '三角区',
      '腰穿',
      '后角',
    ],
    [DictionaryType.VALVE_PLACEMENT_SITE]: [
      '耳后',
      '胸前',
      '锁骨下',
      '腹壁',
      '腰背部',
    ],
    [DictionaryType.DISTAL_SHUNT_DIRECTION]: ['腹腔', '胸腔', '心房', '腰大池'],
  };

  await Promise.all(
    Object.entries(dictionarySeeds).flatMap(([type, labels]) =>
      labels.map((label, index) =>
        ensureDictionaryItem({
          type,
          label,
          sortOrder: index * 10,
        }),
      ),
    ),
  );

  const patientA1 = await ensurePatient({
    hospitalId: hospitalA.id,
    doctorId: doctorA.id,
    creatorId: doctorA.id,
    name: 'Seed Patient A1',
    inpatientNo: 'ZYH-A-0001',
    projectName: '脑积水随访项目-A',
    phone: '13800002001',
    idCard: '110101199001010011',
  });

  const patientA2 = await ensurePatient({
    hospitalId: hospitalA.id,
    doctorId: doctorA2.id,
    creatorId: doctorA2.id,
    name: 'Seed Patient A2',
    inpatientNo: 'ZYH-A-0002',
    projectName: '脑积水随访项目-A',
    phone: '13800002002',
    idCard: '110101199002020022',
  });

  const patientA3 = await ensurePatient({
    hospitalId: hospitalA.id,
    doctorId: doctorA3.id,
    creatorId: doctorA3.id,
    name: 'Seed Patient A3',
    inpatientNo: 'ZYH-A-0003',
    projectName: '脑积水随访项目-A',
    phone: '13800002003',
    idCard: '110101199003030033',
  });

  const patientB1 = await ensurePatient({
    hospitalId: hospitalB.id,
    doctorId: doctorB.id,
    creatorId: doctorB.id,
    name: 'Seed Patient B1',
    inpatientNo: 'ZYH-B-0001',
    projectName: '脑积水随访项目-B',
    phone: '13800002001',
    idCard: '110101199001010011',
  });

  await ensureFamilyMiniAppAccount({
    phone: patientA2.phone,
    openId: 'seed-family-a2-openid',
  });

  const adjustableCatalog = await ensureImplantCatalog({
    modelCode: 'SEED-ADJUSTABLE-VALVE',
    manufacturer: 'Seed MedTech',
    name: 'Seed 可调压分流阀',
    pressureLevels: [80, 100, 120, 140, 160],
    isPressureAdjustable: true,
    notes: 'Seed 全局可调压目录样例',
  });

  const fixedCatalog = await ensureImplantCatalog({
    modelCode: 'SEED-FIXED-VALVE',
    manufacturer: 'Seed MedTech',
    name: 'Seed 固定压分流阀',
    pressureLevels: [],
    isPressureAdjustable: false,
    notes: 'Seed 固定压目录样例',
  });

  const surgeryA1Old = await ensurePatientSurgery({
    patientId: patientA1.id,
    surgeryDate: '2024-06-01T08:00:00.000Z',
    surgeryName: '首次脑室腹腔分流术',
    surgeonName: 'Seed Director A',
    preOpPressure: 24,
    primaryDisease: '先天性脑积水',
    hydrocephalusTypes: ['交通性'],
    notes: '首台手术',
  });

  const surgeryA1New = await ensurePatientSurgery({
    patientId: patientA1.id,
    surgeryDate: '2025-09-10T08:00:00.000Z',
    surgeryName: '分流系统翻修术',
    surgeonName: 'Seed Director A',
    preOpPressure: 18,
    primaryDisease: '分流功能障碍',
    hydrocephalusTypes: ['交通性', '高压性'],
    previousShuntSurgeryDate: '2024-06-01T08:00:00.000Z',
    preOpMaterials: [
      {
        type: 'IMAGE',
        url: 'https://seed.example.com/a1-ct-preop.png',
        name: 'Seed A1 术前 CT',
      },
    ],
    notes: '二次手术，保留原设备历史',
  });

  const surgeryA2 = await ensurePatientSurgery({
    patientId: patientA2.id,
    surgeryDate: '2025-12-15T08:00:00.000Z',
    surgeryName: '脑室腹腔分流术',
    surgeonName: 'Seed Doctor A2',
    preOpPressure: 20,
    primaryDisease: '肿瘤相关脑积水',
    hydrocephalusTypes: ['梗阻性'],
  });

  const surgeryA3 = await ensurePatientSurgery({
    patientId: patientA3.id,
    surgeryDate: '2025-11-20T08:00:00.000Z',
    surgeryName: '脑室腹腔分流术',
    surgeonName: 'Seed Doctor A3',
    preOpPressure: 21,
    primaryDisease: '外伤后脑积水',
    hydrocephalusTypes: ['交通性'],
  });

  const surgeryB1 = await ensurePatientSurgery({
    patientId: patientB1.id,
    surgeryDate: '2025-10-05T08:00:00.000Z',
    surgeryName: '脑室腹腔分流术',
    surgeonName: 'Seed Doctor B',
    preOpPressure: 23,
    primaryDisease: '出血后脑积水',
    hydrocephalusTypes: ['高压性'],
  });

  const deviceA1 = await ensureDevice({
    patientId: patientA1.id,
    surgeryId: surgeryA1New.id,
    implantCatalogId: adjustableCatalog.id,
    currentPressure: 118,
    status: DeviceStatus.ACTIVE,
    implantModel: adjustableCatalog.modelCode,
    implantManufacturer: adjustableCatalog.manufacturer,
    implantName: adjustableCatalog.name,
    isPressureAdjustable: adjustableCatalog.isPressureAdjustable,
    isAbandoned: false,
    shuntMode: 'VPS',
    proximalPunctureAreas: ['额角'],
    valvePlacementSites: ['耳后'],
    distalShuntDirection: '腹腔',
    initialPressure: 118,
    implantNotes: 'Seed A1 当前在用设备',
    labelImageUrl: 'https://seed.example.com/labels/a1-001.jpg',
  });

  const deviceA2 = await ensureDevice({
    patientId: patientA2.id,
    surgeryId: surgeryA2.id,
    implantCatalogId: adjustableCatalog.id,
    currentPressure: 112,
    status: DeviceStatus.ACTIVE,
    implantModel: adjustableCatalog.modelCode,
    implantManufacturer: adjustableCatalog.manufacturer,
    implantName: adjustableCatalog.name,
    isPressureAdjustable: adjustableCatalog.isPressureAdjustable,
    isAbandoned: false,
    shuntMode: 'VPS',
    proximalPunctureAreas: ['枕角'],
    valvePlacementSites: ['胸前'],
    distalShuntDirection: '腹腔',
    initialPressure: 112,
    implantNotes: 'Seed A2 当前在用设备',
    labelImageUrl: 'https://seed.example.com/labels/a2-002.jpg',
  });

  await ensureDevice({
    patientId: patientA3.id,
    surgeryId: surgeryA3.id,
    implantCatalogId: adjustableCatalog.id,
    currentPressure: 109,
    status: DeviceStatus.ACTIVE,
    implantModel: adjustableCatalog.modelCode,
    implantManufacturer: adjustableCatalog.manufacturer,
    implantName: adjustableCatalog.name,
    isPressureAdjustable: adjustableCatalog.isPressureAdjustable,
    isAbandoned: false,
    shuntMode: 'LPS',
    proximalPunctureAreas: ['腰穿'],
    valvePlacementSites: ['腰背部'],
    distalShuntDirection: '腹腔',
    initialPressure: 109,
    implantNotes: 'Seed A3 当前在用设备',
    labelImageUrl: 'https://seed.example.com/labels/a3-003.jpg',
  });

  const deviceB1 = await ensureDevice({
    patientId: patientB1.id,
    surgeryId: surgeryB1.id,
    implantCatalogId: adjustableCatalog.id,
    currentPressure: 121,
    status: DeviceStatus.ACTIVE,
    implantModel: adjustableCatalog.modelCode,
    implantManufacturer: adjustableCatalog.manufacturer,
    implantName: adjustableCatalog.name,
    isPressureAdjustable: adjustableCatalog.isPressureAdjustable,
    isAbandoned: false,
    shuntMode: 'VPS',
    proximalPunctureAreas: ['额角'],
    valvePlacementSites: ['耳后'],
    distalShuntDirection: '腹腔',
    initialPressure: 121,
    implantNotes: 'Seed B1 当前在用设备',
    labelImageUrl: 'https://seed.example.com/labels/b1-001.jpg',
  });

  await ensureDevice({
    patientId: patientA1.id,
    surgeryId: surgeryA1Old.id,
    implantCatalogId: adjustableCatalog.id,
    currentPressure: 130,
    status: DeviceStatus.INACTIVE,
    implantModel: adjustableCatalog.modelCode,
    implantManufacturer: adjustableCatalog.manufacturer,
    implantName: adjustableCatalog.name,
    isPressureAdjustable: adjustableCatalog.isPressureAdjustable,
    isAbandoned: true,
    shuntMode: 'VPS',
    proximalPunctureAreas: ['额角'],
    valvePlacementSites: ['耳后'],
    distalShuntDirection: '腹腔',
    initialPressure: 130,
    implantNotes: 'Seed A1 弃用历史设备',
    labelImageUrl: 'https://seed.example.com/labels/a1-004.jpg',
  });

  // 清理与种子设备关联的历史任务，保证 seed 可重复执行且生命周期夹具稳定。
  const seedTaskItems = await prisma.taskItem.findMany({
    where: {
      deviceId: {
        in: [deviceA1.id, deviceB1.id],
      },
    },
    select: { taskId: true },
  });
  const seedTaskIds = Array.from(
    new Set(seedTaskItems.map((item) => item.taskId)),
  );
  if (seedTaskIds.length > 0) {
    await prisma.task.deleteMany({
      where: {
        id: {
          in: seedTaskIds,
        },
      },
    });
  }

  const lifecycleTaskA = await prisma.task.create({
    data: {
      status: TaskStatus.COMPLETED,
      creatorId: doctorA.id,
      engineerId: engineerA.id,
      hospitalId: hospitalA.id,
      items: {
        create: [
          {
            deviceId: deviceA1.id,
            oldPressure: 118,
            targetPressure: 120,
          },
        ],
      },
    },
    include: { items: true },
  });

  const lifecycleTaskB = await prisma.task.create({
    data: {
      status: TaskStatus.PENDING,
      creatorId: doctorB.id,
      engineerId: engineerB.id,
      hospitalId: hospitalB.id,
      items: {
        create: [
          {
            deviceId: deviceB1.id,
            oldPressure: 121,
            targetPressure: 119,
          },
        ],
      },
    },
    include: { items: true },
  });

  console.log(
    JSON.stringify(
      {
        ok: true,
        seedPasswordPlain: SEED_PASSWORD_PLAIN,
        hospitals: {
          hospitalAId: hospitalA.id,
          hospitalBId: hospitalB.id,
        },
        departments: {
          departmentA1Id: departmentA1.id,
          departmentA2Id: departmentA2.id,
          departmentB1Id: departmentB1.id,
        },
        groups: {
          groupA1Id: groupA1.id,
          groupA2Id: groupA2.id,
          groupB1Id: groupB1.id,
        },
        users: {
          systemAdminId: systemAdmin.id,
          hospitalAdminAId: hospitalAdminA.id,
          directorAId: directorA.id,
          leaderAId: leaderA.id,
          doctorAId: doctorA.id,
          doctorA2Id: doctorA2.id,
          doctorA3Id: doctorA3.id,
          doctorBId: doctorB.id,
          engineerAId: engineerA.id,
          engineerBId: engineerB.id,
        },
        patients: {
          patientA1Id: patientA1.id,
          patientA2Id: patientA2.id,
          patientA3Id: patientA3.id,
          patientB1Id: patientB1.id,
        },
        devices: {
          deviceA1Id: deviceA1.id,
          deviceA2Id: deviceA2.id,
          deviceB1Id: deviceB1.id,
        },
        tasks: {
          lifecycleTaskAId: lifecycleTaskA.id,
          lifecycleTaskBId: lifecycleTaskB.id,
        },
      },
      null,
      2,
    ),
  );
}

main()
  .catch((error) => {
    console.error('Seed failed:', error);
    process.exitCode = 1;
  })
  .finally(async () => {
    await prisma.$disconnect();
  });
