import type { Prisma } from '@prisma/client';
import { mergePatientPreferences } from '../pipeline/patient-preferences.util';

/**
 * 患者 upsert 数据构造 — 全量(cold-import)与部分更新(push 单表)两种语义。
 *
 * **full(默认,cold-import 存量/增量)**:行为与历史完全一致 —— 每字段显式落值
 *   (缺失 → null / 默认),phone 缺失合成 demo 假号(dev 造数便利,生产 host 给真值自动让位)。
 *
 * **partial(push 形态 A 单表患者主档)**:canonical **未提供**(undefined)的字段不进
 *   update 集合(prisma undefined = 不动该列)——解决 push 单表时跨表依赖缺席的覆盖事故:
 *   宿主只推 customer_basic_info 时,phone 依赖的 contacts lookup 落空,full 语义会把
 *   存量真号覆盖成合成假号;partial 语义下 phone 缺失 → 不动存量。
 *   phone 在 partial 下**永不合成假号**(显式提供才更新)。
 *   注意:显式 null 与 undefined 同义(canonical zod nullable 归一),都视为"未提供"——
 *   push 通道没有"显式清空字段"语义(如需清空走全量通道)。
 */
export function buildPatientUpsertData(
  c: Record<string, unknown>,
  externalId: string,
  opts: { partial: boolean },
): Record<string, unknown> {
  if (!opts.partial) {
    return {
      name: (c.name as string | undefined) ?? null,
      // ⚠️ DEMO 兜底:host 不给 phone(隐私) → 由 externalId hash 合成确定性假号
      phone: (c.phone as string | undefined) ?? synthesizeDemoPhone(externalId),
      gender: (c.gender as string | undefined) ?? null,
      birthDate: c.birthDate ? new Date(c.birthDate as string) : null,
      medicalRecordNumber: (c.medicalRecordNumber as string | undefined) ?? null,
      preferences: mergePatientPreferences(c),
      active: ((c.status as string | undefined) ?? 'active') === 'active',
    };
  }
  // partial:提供(非 null/undefined)才落
  const given = (k: string) => c[k] !== undefined && c[k] !== null;
  return {
    ...(given('name') ? { name: c.name as string } : {}),
    ...(given('phone') ? { phone: c.phone as string } : {}),
    ...(given('gender') ? { gender: c.gender as string } : {}),
    ...(given('birthDate') ? { birthDate: new Date(c.birthDate as string) } : {}),
    ...(given('medicalRecordNumber')
      ? { medicalRecordNumber: c.medicalRecordNumber as string }
      : {}),
    // preferences 本就是"有可写才返回,否则 undefined 不动列"语义,天然 partial-safe
    preferences: mergePatientPreferences(c),
    ...(given('status') ? { active: (c.status as string) === 'active' } : {}),
  };
}

/** 副表(profile)同款双语义。 */
export function buildProfileUpsertData(
  c: Record<string, unknown>,
  opts: { partial: boolean },
): Record<string, unknown> {
  if (!opts.partial) {
    return {
      doNotContact: (c.doNotContact as boolean) ?? false,
      deceased: (c.deceased as boolean) ?? false,
      tags: Array.isArray(c.tags) ? (c.tags as string[]) : [],
      notes: (c.notes as string | undefined) ?? null,
      acquisitionChannel: (c.acquisitionChannel as string | undefined) ?? null,
      acquisitionSub: (c.acquisitionSub as string | undefined) ?? null,
      referralCount: c.referralCount != null ? Math.trunc(Number(c.referralCount)) : null,
      referralAmountCents:
        c.referralAmount != null ? Math.round(Number(c.referralAmount) * 100) : null,
    };
  }
  const given = (k: string) => c[k] !== undefined && c[k] !== null;
  return {
    ...(given('doNotContact') ? { doNotContact: c.doNotContact as boolean } : {}),
    ...(given('deceased') ? { deceased: c.deceased as boolean } : {}),
    ...(Array.isArray(c.tags) && c.tags.length > 0 ? { tags: c.tags as string[] } : {}),
    ...(given('notes') ? { notes: c.notes as string } : {}),
    ...(given('acquisitionChannel')
      ? { acquisitionChannel: c.acquisitionChannel as string }
      : {}),
    ...(given('acquisitionSub') ? { acquisitionSub: c.acquisitionSub as string } : {}),
    ...(given('referralCount') ? { referralCount: Math.trunc(Number(c.referralCount)) } : {}),
    ...(given('referralAmount')
      ? { referralAmountCents: Math.round(Number(c.referralAmount) * 100) }
      : {}),
  };
}

/** dev 造数:externalId hash 合成确定性假号(138 前缀;同 id 恒同号)。生产 host 给真值自动让位。 */
export function synthesizeDemoPhone(externalId: string): string {
  let hash = 0;
  for (let i = 0; i < externalId.length; i++) {
    hash = (hash * 31 + externalId.charCodeAt(i)) >>> 0;
  }
  const last8 = String(hash % 100_000_000).padStart(8, '0');
  return `138${last8}`;
}

export type PatientUpsertData = ReturnType<typeof buildPatientUpsertData> &
  Prisma.InputJsonValue;
