import { randomUUID } from 'crypto';
import { EstadoPieza, Prisma } from '@prisma/client';
import type { Request, Response } from 'express';
import prisma from '../config/db';
import { listFallbackAppointments } from '../services/appointment.store';
import { listFallbackBudgets } from '../services/budget.store';
import { listFallbackTransactions } from '../services/finance.store';
import {
  listFallbackOdontogram,
  updateFallbackOdontogramPiece,
} from '../services/odontogram.store';
import {
  CreateFallbackTreatmentInput,
  FallbackTreatmentNotFoundError,
  TreatmentStatus,
  createFallbackTreatment,
  listFallbackTreatments,
  treatmentStatuses,
  updateFallbackTreatment,
} from '../services/treatment.store';
import {
  CreatePatientInput,
  DuplicatePatientError,
  FallbackPatientNotFoundError,
  PatientDocumentType,
  UpdatePatientInput,
  createFallbackPatient,
  getFallbackPatientById,
  listFallbackPatients,
  patientDocumentTypes,
  updateFallbackPatient,
} from '../services/patient.store';
import { listFallbackRecipes } from '../services/recipe.store';

const toothNumbers = [
  18, 17, 16, 15, 14, 13, 12, 11, 21, 22, 23, 24, 25, 26, 27, 28, 48, 47, 46, 45, 44, 43, 42, 41,
  31, 32, 33, 34, 35, 36, 37, 38,
] as const;

const normalizeNullableText = (value: unknown) => {
  if (typeof value !== 'string') {
    return null;
  }

  const trimmedValue = value.trim();
  return trimmedValue ? trimmedValue : null;
};

const normalizeDateInput = (value: unknown) => {
  if (typeof value !== 'string' || !value.trim()) {
    return null;
  }

  const directDate = new Date(value);

  if (!Number.isNaN(directDate.getTime())) {
    return directDate.toISOString();
  }

  const assumedDate = new Date(`${value}T00:00:00`);

  if (!Number.isNaN(assumedDate.getTime())) {
    return assumedDate.toISOString();
  }

  return null;
};

const calculateAge = (dateValue: string | null) => {
  if (!dateValue) {
    return null;
  }

  const birthDate = new Date(dateValue);

  if (Number.isNaN(birthDate.getTime())) {
    return null;
  }

  const today = new Date();
  let age = today.getFullYear() - birthDate.getFullYear();
  const monthDelta = today.getMonth() - birthDate.getMonth();

  if (monthDelta < 0 || (monthDelta === 0 && today.getDate() < birthDate.getDate())) {
    age -= 1;
  }

  return age;
};

const normalizeFaces = (value: unknown) =>
  Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];

type PatientRequestPayload = {
  tipoDocumento?: PatientDocumentType;
  dni?: string;
  nombres?: string;
  apellidos?: string;
  fechaNacimiento?: string | null;
  email?: string | null;
  telefono?: string | null;
  direccion?: string | null;
  alergias?: string | null;
  antecedentes?: string | null;
  razonSocial?: string | null;
  ruc?: string | null;
};

type TreatmentRequestPayload = {
  nombre?: string;
  descripcion?: string | null;
  costoBase?: number;
  estado?: TreatmentStatus;
  piezaDental?: string | null;
  fechaInicio?: string | null;
  fechaFin?: string | null;
  notasClinicas?: string | null;
  activo?: boolean;
};

type DatabaseTreatmentRecord = {
  id: string;
  nombre: string;
  descripcion: string | null;
  costoBase: number;
  estado: string;
  piezaDental: string | null;
  fechaInicio: Date | null;
  fechaFin: Date | null;
  notasClinicas: string | null;
  activo: boolean;
  createdAt: Date;
  updatedAt: Date;
  pacienteId: string | null;
};

const resolveRouteParam = (value: string | string[] | undefined) => (Array.isArray(value) ? value[0] : value);

const getPatientIdFromRequest = (req: Request, res: Response) => {
  const patientId = resolveRouteParam(req.params.id);

  if (!patientId) {
    res.status(400).json({ error: 'Paciente invalido' });
    return null;
  }

  return patientId;
};

const getTreatmentIdFromRequest = (req: Request, res: Response) => {
  const treatmentId = resolveRouteParam(req.params.treatmentId);

  if (!treatmentId) {
    res.status(400).json({ error: 'Tratamiento invalido' });
    return null;
  }

  return treatmentId;
};

const normalizeNumberInput = (value: unknown) => {
  if (typeof value === 'number' && Number.isFinite(value)) {
    return value;
  }

  if (typeof value === 'string' && value.trim()) {
    const parsedValue = Number(value);
    return Number.isFinite(parsedValue) ? parsedValue : null;
  }

  return null;
};

const buildPatientCreateInput = (payload: PatientRequestPayload): CreatePatientInput | null => {
  if (!payload.dni || !payload.nombres || !payload.apellidos) {
    return null;
  }

  return {
    tipoDocumento: payload.tipoDocumento ?? 'DNI',
    dni: payload.dni,
    nombres: payload.nombres,
    apellidos: payload.apellidos,
    ...(payload.fechaNacimiento !== undefined ? { fechaNacimiento: payload.fechaNacimiento } : {}),
    ...(payload.email !== undefined ? { email: payload.email } : {}),
    ...(payload.telefono !== undefined ? { telefono: payload.telefono } : {}),
    ...(payload.direccion !== undefined ? { direccion: payload.direccion } : {}),
    ...(payload.alergias !== undefined ? { alergias: payload.alergias } : {}),
    ...(payload.antecedentes !== undefined ? { antecedentes: payload.antecedentes } : {}),
    ...(payload.razonSocial !== undefined ? { razonSocial: payload.razonSocial } : {}),
    ...(payload.ruc !== undefined ? { ruc: payload.ruc } : {}),
  };
};

const buildPatientCreateData = (payload: CreatePatientInput): Prisma.PacienteCreateInput => ({
  tipoDocumento: payload.tipoDocumento,
  dni: payload.dni,
  nombres: payload.nombres,
  apellidos: payload.apellidos,
  ...(payload.fechaNacimiento !== undefined
    ? { fechaNacimiento: payload.fechaNacimiento ? new Date(payload.fechaNacimiento) : null }
    : {}),
  ...(payload.email !== undefined ? { email: payload.email } : {}),
  ...(payload.telefono !== undefined ? { telefono: payload.telefono } : {}),
  ...(payload.direccion !== undefined ? { direccion: payload.direccion } : {}),
  ...(payload.alergias !== undefined ? { alergias: payload.alergias } : {}),
  ...(payload.antecedentes !== undefined ? { antecedentes: payload.antecedentes } : {}),
  ...(payload.razonSocial !== undefined ? { razonSocial: payload.razonSocial } : {}),
  ...(payload.ruc !== undefined ? { ruc: payload.ruc } : {}),
});

const buildPatientUpdateData = (payload: PatientRequestPayload): Prisma.PacienteUpdateInput => ({
  ...(payload.tipoDocumento !== undefined ? { tipoDocumento: payload.tipoDocumento } : {}),
  ...(payload.dni !== undefined ? { dni: payload.dni } : {}),
  ...(payload.nombres !== undefined ? { nombres: payload.nombres } : {}),
  ...(payload.apellidos !== undefined ? { apellidos: payload.apellidos } : {}),
  ...(payload.fechaNacimiento !== undefined
    ? { fechaNacimiento: payload.fechaNacimiento ? new Date(payload.fechaNacimiento) : null }
    : {}),
  ...(payload.email !== undefined ? { email: payload.email } : {}),
  ...(payload.telefono !== undefined ? { telefono: payload.telefono } : {}),
  ...(payload.direccion !== undefined ? { direccion: payload.direccion } : {}),
  ...(payload.alergias !== undefined ? { alergias: payload.alergias } : {}),
  ...(payload.antecedentes !== undefined ? { antecedentes: payload.antecedentes } : {}),
  ...(payload.razonSocial !== undefined ? { razonSocial: payload.razonSocial } : {}),
  ...(payload.ruc !== undefined ? { ruc: payload.ruc } : {}),
});

const buildTreatmentCreateInput = (
  payload: TreatmentRequestPayload,
): CreateFallbackTreatmentInput | null => {
  if (!payload.nombre || payload.costoBase === undefined || payload.costoBase <= 0) {
    return null;
  }

  return {
    nombre: payload.nombre,
    costoBase: Number(payload.costoBase.toFixed(2)),
    ...(payload.descripcion !== undefined ? { descripcion: payload.descripcion } : {}),
    ...(payload.estado !== undefined ? { estado: payload.estado } : {}),
    ...(payload.piezaDental !== undefined ? { piezaDental: payload.piezaDental } : {}),
    ...(payload.fechaInicio !== undefined ? { fechaInicio: payload.fechaInicio } : {}),
    ...(payload.fechaFin !== undefined ? { fechaFin: payload.fechaFin } : {}),
    ...(payload.notasClinicas !== undefined ? { notasClinicas: payload.notasClinicas } : {}),
    ...(payload.activo !== undefined ? { activo: payload.activo } : {}),
  };
};


const serializePatientSummary = (patient: {
  id: string;
  tipoDocumento?: PatientDocumentType;
  dni: string;
  nombres: string;
  apellidos: string;
  fechaNacimiento?: Date | string | null;
  email: string | null;
  telefono: string | null;
  direccion?: string | null;
  alergias?: string | null;
  antecedentes?: string | null;
  razonSocial?: string | null;
  ruc?: string | null;
  fechaRegistro: Date | string;
  updatedAt: Date | string;
}) => ({
  id: patient.id,
  tipoDocumento: patient.tipoDocumento ?? 'DNI',
  dni: patient.dni,
  nombres: patient.nombres,
  apellidos: patient.apellidos,
  nombreCompleto: `${patient.nombres} ${patient.apellidos}`.trim(),
  fechaNacimiento:
    patient.fechaNacimiento instanceof Date
      ? patient.fechaNacimiento.toISOString()
      : patient.fechaNacimiento ?? null,
  edad: calculateAge(
    patient.fechaNacimiento instanceof Date
      ? patient.fechaNacimiento.toISOString()
      : patient.fechaNacimiento ?? null,
  ),
  email: patient.email,
  telefono: patient.telefono,
  direccion: patient.direccion ?? null,
  alergias: patient.alergias ?? null,
  antecedentes: patient.antecedentes ?? null,
  razonSocial: patient.razonSocial ?? null,
  ruc: patient.ruc ?? null,
  fechaRegistro:
    patient.fechaRegistro instanceof Date ? patient.fechaRegistro.toISOString() : patient.fechaRegistro,
  updatedAt: patient.updatedAt instanceof Date ? patient.updatedAt.toISOString() : patient.updatedAt,
});

const buildCompleteOdontogram = (
  patientId: string,
  pieces: Array<{
    id: string;
    numero: number;
    estado: EstadoPieza | string;
    caras: unknown;
    observacion: string | null;
    updatedAt: Date | string;
  }>,
) =>
  toothNumbers.map((toothNumber) => {
    const piece = pieces.find((currentPiece) => currentPiece.numero === toothNumber);

    return {
      id: piece?.id ?? `virtual-${patientId}-${toothNumber}`,
      numero: toothNumber,
      estado: (piece?.estado as EstadoPieza | undefined) ?? EstadoPieza.SANO,
      caras: normalizeFaces(piece?.caras),
      observacion: piece?.observacion ?? null,
      updatedAt:
        piece?.updatedAt instanceof Date
          ? piece.updatedAt.toISOString()
          : piece?.updatedAt ?? new Date().toISOString(),
    };
  });

const buildOdontogramSummary = (pieces: Array<{ estado: string }>) => ({
  affectedTeethCount: pieces.filter((piece) => piece.estado !== 'SANO').length,
  cariesCount: pieces.filter((piece) => piece.estado === 'CARIES').length,
  restoredCount: pieces.filter((piece) => ['RESTAURADO', 'CORONA', 'IMPLANTE'].includes(piece.estado)).length,
  extractedCount: pieces.filter((piece) => piece.estado === 'EXTRAIDO').length,
});

const buildTimeline = (
  patientName: string,
  input: {
    appointments: Array<{ id: string; fecha: string; motivo: string; estado: string }>;
    budgets: Array<{ id: string; fecha: string; estado: string; concepto: string }>;
    recipes: Array<{ id: string; fecha: string; diagnostico: string; firmada: boolean }>;
    treatments: Array<{ id: string; updatedAt: string; nombre: string; estado: string; piezaDental: string | null }>;
  },
) =>
  [
    ...input.appointments.map((appointment) => ({
      id: `appt-${appointment.id}`,
      type: 'appointment',
      date: appointment.fecha,
      title: appointment.motivo,
      description: `Cita ${appointment.estado.toLowerCase()} para ${patientName}.`,
      badge: appointment.estado,
    })),
    ...input.budgets.map((budget) => ({
      id: `budget-${budget.id}`,
      type: 'budget',
      date: budget.fecha,
      title: budget.concepto,
      description: `Presupuesto ${budget.estado.toLowerCase()}.`,
      badge: budget.estado,
    })),
    ...input.recipes.map((recipe) => ({
      id: `recipe-${recipe.id}`,
      type: 'recipe',
      date: recipe.fecha,
      title: recipe.diagnostico,
      description: recipe.firmada ? 'Receta firmada y vigente.' : 'Receta pendiente de firma.',
      badge: recipe.firmada ? 'Firmada' : 'Pendiente',
    })),
    ...input.treatments.map((treatment) => ({
      id: `treatment-${treatment.id}`,
      type: 'treatment',
      date: treatment.updatedAt,
      title: treatment.nombre,
      description: `Tratamiento ${treatment.estado.toLowerCase()}${treatment.piezaDental ? ` en pieza ${treatment.piezaDental}` : ''} para ${patientName}.`,
      badge: treatment.estado,
    })),
  ]
    .sort((left, right) => new Date(right.date).getTime() - new Date(left.date).getTime())
    .slice(0, 8);

const buildMetrics = (input: {
  appointments: Array<{ fecha: string }>;
  budgets: Array<{ estado: string; total: number }>;
  transactions: Array<{ tipo: string; monto: number }>;
  treatments: Array<{ estado: string }>;
  odontogramSummary: ReturnType<typeof buildOdontogramSummary>;
}) => {
  const now = new Date();
  const upcomingAppointments = input.appointments.filter(
    (appointment) => new Date(appointment.fecha).getTime() >= now.getTime(),
  ).length;
  const totalPaid = input.transactions
    .filter((transaction) => transaction.tipo === 'INGRESO')
    .reduce((sum, transaction) => sum + transaction.monto, 0);
  const pendingBudgetTotal = input.budgets
    .filter((budget) => budget.estado === 'PENDIENTE' || budget.estado === 'APROBADO')
    .reduce((sum, budget) => sum + budget.total, 0);
  const activeTreatmentCount = input.treatments.filter((treatment) =>
    ['PLANIFICADO', 'EN_PROCESO', 'PAUSADO'].includes(treatment.estado),
  ).length;

  return {
    upcomingAppointments,
    totalPaid,
    pendingBudgetTotal,
    activeTreatmentCount: activeTreatmentCount || input.odontogramSummary.affectedTeethCount,
  };
};

const serializeAppointment = (appointment: {
  id: string;
  fecha: Date | string;
  motivo: string;
  estado: string;
  notas?: string | null;
  dentista?: { name?: string | null } | null;
  dentistaNombre?: string;
}) => ({
  id: appointment.id,
  fecha: appointment.fecha instanceof Date ? appointment.fecha.toISOString() : appointment.fecha,
  motivo: appointment.motivo,
  estado: appointment.estado,
  notas: appointment.notas ?? null,
  dentistaNombre: appointment.dentista?.name ?? appointment.dentistaNombre ?? 'Agenda del Sistema',
});

const serializeBudget = (budget: {
  id: string;
  folio?: string;
  fecha: Date | string;
  total: number;
  estado: string;
  concepto?: string;
  items?: Array<{ tratamiento?: { nombre?: string | null } | null }>;
}) => ({
  id: budget.id,
  folio: budget.folio ?? budget.id,
  fecha: budget.fecha instanceof Date ? budget.fecha.toISOString() : budget.fecha,
  total: budget.total,
  estado: budget.estado,
  concepto: budget.concepto ?? budget.items?.[0]?.tratamiento?.nombre ?? 'Plan de tratamiento',
});

const serializeRecipe = (recipe: {
  id: string;
  fecha: Date | string;
  diagnostico: string;
  indicaciones?: string;
  firmada: boolean;
  medico?: { name?: string | null } | null;
  medicoNombre?: string;
}) => ({
  id: recipe.id,
  fecha: recipe.fecha instanceof Date ? recipe.fecha.toISOString() : recipe.fecha,
  diagnostico: recipe.diagnostico,
  indicaciones: recipe.indicaciones ?? '',
  firmada: recipe.firmada,
  medicoNombre: recipe.medico?.name ?? recipe.medicoNombre ?? 'Dr. Demo DentaFlow',
});

const serializeTransaction = (transaction: {
  id: string;
  fecha: Date | string;
  monto: number;
  tipo: string;
  descripcion?: string | null;
}) => ({
  id: transaction.id,
  fecha: transaction.fecha instanceof Date ? transaction.fecha.toISOString() : transaction.fecha,
  monto: transaction.monto,
  tipo: transaction.tipo,
  descripcion:
    transaction.descripcion ?? (transaction.tipo === 'INGRESO' ? 'Pago registrado' : 'Movimiento de caja'),
});

const serializeTreatment = (treatment: {
  id: string;
  nombre: string;
  descripcion?: string | null;
  costoBase: number;
  estado: string;
  piezaDental?: string | null;
  fechaInicio?: Date | string | null;
  fechaFin?: Date | string | null;
  notasClinicas?: string | null;
  activo: boolean;
  createdAt?: Date | string;
  updatedAt: Date | string;
}) => ({
  id: treatment.id,
  nombre: treatment.nombre,
  descripcion: treatment.descripcion ?? null,
  costoBase: treatment.costoBase,
  estado: treatment.estado,
  piezaDental: treatment.piezaDental ?? null,
  fechaInicio:
    treatment.fechaInicio instanceof Date
      ? treatment.fechaInicio.toISOString()
      : treatment.fechaInicio ?? null,
  fechaFin: treatment.fechaFin instanceof Date ? treatment.fechaFin.toISOString() : treatment.fechaFin ?? null,
  notasClinicas: treatment.notasClinicas ?? null,
  activo: treatment.activo,
  createdAt:
    treatment.createdAt instanceof Date ? treatment.createdAt.toISOString() : treatment.createdAt ?? null,
  updatedAt: treatment.updatedAt instanceof Date ? treatment.updatedAt.toISOString() : treatment.updatedAt,
});

const selectTreatmentColumns = Prisma.sql`
  "id",
  "nombre",
  "descripcion",
  "costoBase",
  "estado"::text AS "estado",
  "piezaDental",
  "fechaInicio",
  "fechaFin",
  "notasClinicas",
  "activo",
  "createdAt",
  "updatedAt",
  "pacienteId"
`;

const listDatabaseTreatments = async (patientId: string) =>
  prisma.$queryRaw<DatabaseTreatmentRecord[]>(Prisma.sql`
    SELECT ${selectTreatmentColumns}
    FROM "Tratamiento"
    WHERE "pacienteId" = ${patientId}
    ORDER BY "updatedAt" DESC, "createdAt" DESC
  `);

const findDatabaseTreatment = async (patientId: string, treatmentId: string) => {
  const treatments = await prisma.$queryRaw<DatabaseTreatmentRecord[]>(Prisma.sql`
    SELECT ${selectTreatmentColumns}
    FROM "Tratamiento"
    WHERE "id" = ${treatmentId} AND "pacienteId" = ${patientId}
    LIMIT 1
  `);

  return treatments[0] ?? null;
};

const createDatabaseTreatment = async (patientId: string, payload: CreateFallbackTreatmentInput) => {
  const treatmentId = randomUUID();
  const now = new Date();
  const rows = await prisma.$queryRaw<DatabaseTreatmentRecord[]>(Prisma.sql`
    INSERT INTO "Tratamiento" (
      "id",
      "nombre",
      "descripcion",
      "costoBase",
      "activo",
      "estado",
      "piezaDental",
      "fechaInicio",
      "fechaFin",
      "notasClinicas",
      "createdAt",
      "updatedAt",
      "pacienteId"
    )
    VALUES (
      ${treatmentId},
      ${payload.nombre},
      ${payload.descripcion ?? null},
      ${payload.costoBase},
      ${payload.activo ?? true},
      CAST(${payload.estado ?? 'PLANIFICADO'} AS "EstadoTratamiento"),
      ${payload.piezaDental ?? null},
      ${payload.fechaInicio ? new Date(payload.fechaInicio) : null},
      ${payload.fechaFin ? new Date(payload.fechaFin) : null},
      ${payload.notasClinicas ?? null},
      ${now},
      ${now},
      ${patientId}
    )
    RETURNING ${selectTreatmentColumns}
  `);

  return rows[0] ?? null;
};

const updateDatabaseTreatment = async (
  patientId: string,
  treatmentId: string,
  currentTreatment: DatabaseTreatmentRecord,
  payload: TreatmentRequestPayload,
) => {
  const nextTreatment = {
    nombre: payload.nombre ?? currentTreatment.nombre,
    descripcion: payload.descripcion !== undefined ? payload.descripcion : currentTreatment.descripcion,
    costoBase: payload.costoBase !== undefined ? Number(payload.costoBase.toFixed(2)) : currentTreatment.costoBase,
    estado: payload.estado ?? (currentTreatment.estado as TreatmentStatus),
    piezaDental: payload.piezaDental !== undefined ? payload.piezaDental : currentTreatment.piezaDental,
    fechaInicio: payload.fechaInicio !== undefined ? payload.fechaInicio : currentTreatment.fechaInicio?.toISOString() ?? null,
    fechaFin: payload.fechaFin !== undefined ? payload.fechaFin : currentTreatment.fechaFin?.toISOString() ?? null,
    notasClinicas:
      payload.notasClinicas !== undefined ? payload.notasClinicas : currentTreatment.notasClinicas,
    activo: payload.activo !== undefined ? payload.activo : currentTreatment.activo,
  };
  const updatedAt = new Date();

  const rows = await prisma.$queryRaw<DatabaseTreatmentRecord[]>(Prisma.sql`
    UPDATE "Tratamiento"
    SET
      "nombre" = ${nextTreatment.nombre},
      "descripcion" = ${nextTreatment.descripcion},
      "costoBase" = ${nextTreatment.costoBase},
      "activo" = ${nextTreatment.activo},
      "estado" = CAST(${nextTreatment.estado} AS "EstadoTratamiento"),
      "piezaDental" = ${nextTreatment.piezaDental},
      "fechaInicio" = ${nextTreatment.fechaInicio ? new Date(nextTreatment.fechaInicio) : null},
      "fechaFin" = ${nextTreatment.fechaFin ? new Date(nextTreatment.fechaFin) : null},
      "notasClinicas" = ${nextTreatment.notasClinicas},
      "updatedAt" = ${updatedAt}
    WHERE "id" = ${treatmentId} AND "pacienteId" = ${patientId}
    RETURNING ${selectTreatmentColumns}
  `);

  return rows[0] ?? null;
};

const buildClinicalHistorySummary = (
  treatments: Array<{ estado: string; updatedAt: string }>,
) => ({
  totalTreatments: treatments.length,
  plannedCount: treatments.filter((treatment) => treatment.estado === 'PLANIFICADO').length,
  inProgressCount: treatments.filter((treatment) => treatment.estado === 'EN_PROCESO').length,
  completedCount: treatments.filter((treatment) => treatment.estado === 'COMPLETADO').length,
  lastClinicalUpdate: treatments[0]?.updatedAt ?? null,
});

export const buildDatabasePatientProfile = async (patientId: string) => {
  const patient = await prisma.paciente.findUnique({
    where: { id: patientId },
    include: {
      citas: {
        include: { dentista: true },
        orderBy: { fecha: 'desc' },
      },
      presupuestos: {
        include: {
          creadoPor: true,
          items: {
            include: {
              tratamiento: true,
            },
          },
        },
        orderBy: { fecha: 'desc' },
      },
      recetas: {
        include: { medico: true },
        orderBy: { fecha: 'desc' },
      },
      transacciones: {
        orderBy: { fecha: 'desc' },
      },
      odontogramas: {
        orderBy: { numero: 'asc' },
      },
    },
  });

  if (!patient) {
    return null;
  }

  const databaseTreatments = await listDatabaseTreatments(patientId);
  const serializedPatient = serializePatientSummary(patient);
  const appointments = patient.citas.map(serializeAppointment);
  const budgets = patient.presupuestos.map(serializeBudget);
  const recipes = patient.recetas.map(serializeRecipe);
  const transactions = patient.transacciones.map(serializeTransaction);
  const treatments = databaseTreatments.map(serializeTreatment);
  const odontogramPieces = buildCompleteOdontogram(patient.id, patient.odontogramas);
  const odontogramSummary = buildOdontogramSummary(odontogramPieces);
  const clinicalHistory = buildClinicalHistorySummary(treatments);

  return {
    patient: serializedPatient,
    metrics: buildMetrics({
      appointments,
      budgets,
      transactions,
      treatments,
      odontogramSummary,
    }),
    timeline: buildTimeline(serializedPatient.nombreCompleto, {
      appointments,
      budgets,
      recipes,
      treatments,
    }),
    appointments,
    budgets,
    recipes,
    transactions,
    treatments,
    clinicalHistory,
    odontogram: {
      summary: odontogramSummary,
      pieces: odontogramPieces,
    },
  };
};

export const buildFallbackPatientProfile = async (patientId: string) => {
  const patient = await getFallbackPatientById(patientId);

  if (!patient) {
    return null;
  }

  const [appointmentsData, budgetsData, recipesData, transactionsData, odontogramPieces, treatmentsData] = await Promise.all([
    listFallbackAppointments(),
    listFallbackBudgets(),
    listFallbackRecipes(),
    listFallbackTransactions(),
    listFallbackOdontogram(patientId),
    listFallbackTreatments(patientId),
  ]);

  const serializedPatient = serializePatientSummary(patient);
  const appointments = appointmentsData
    .filter((appointment) => appointment.pacienteId === patientId)
    .map((appointment) =>
      serializeAppointment({
        id: appointment.id,
        fecha: appointment.fecha,
        motivo: appointment.motivo,
        estado: appointment.estado,
        notas: appointment.notas,
        dentistaNombre: appointment.dentistaNombre,
      }),
    );
  const budgets = budgetsData
    .filter((budget) => budget.pacienteId === patientId)
    .map((budget) =>
      serializeBudget({
        id: budget.id,
        folio: budget.folio,
        fecha: budget.fecha,
        total: budget.total,
        estado: budget.estado,
        concepto: budget.concepto,
      }),
    );
  const recipes = recipesData
    .filter((recipe) => recipe.pacienteId === patientId)
    .map((recipe) =>
      serializeRecipe({
        id: recipe.id,
        fecha: recipe.fecha,
        diagnostico: recipe.diagnostico,
        indicaciones: recipe.indicaciones,
        firmada: recipe.firmada,
        medicoNombre: recipe.medicoNombre,
      }),
    );
  const transactions = transactionsData
    .filter((transaction) => transaction.pacienteId === patientId)
    .map((transaction) =>
      serializeTransaction({
        id: transaction.id,
        fecha: transaction.fecha,
        monto: transaction.monto,
        tipo: transaction.tipo,
        descripcion: transaction.descripcion,
      }),
    );
  const treatments = treatmentsData.map((treatment) =>
    serializeTreatment({
      id: treatment.id,
      nombre: treatment.nombre,
      descripcion: treatment.descripcion,
      costoBase: treatment.costoBase,
      estado: treatment.estado,
      piezaDental: treatment.piezaDental,
      fechaInicio: treatment.fechaInicio,
      fechaFin: treatment.fechaFin,
      notasClinicas: treatment.notasClinicas,
      activo: treatment.activo,
      createdAt: treatment.createdAt,
      updatedAt: treatment.updatedAt,
    }),
  );
  const fullOdontogram = buildCompleteOdontogram(patient.id, odontogramPieces);
  const odontogramSummary = buildOdontogramSummary(fullOdontogram);
  const clinicalHistory = buildClinicalHistorySummary(treatments);

  return {
    patient: serializedPatient,
    metrics: buildMetrics({
      appointments,
      budgets,
      transactions,
      treatments,
      odontogramSummary,
    }),
    timeline: buildTimeline(serializedPatient.nombreCompleto, {
      appointments,
      budgets,
      recipes,
      treatments,
    }),
    appointments,
    budgets,
    recipes,
    transactions,
    treatments,
    clinicalHistory,
    odontogram: {
      summary: odontogramSummary,
      pieces: fullOdontogram,
    },
  };
};

const buildPatientPayloadFromRequest = (body: Request['body']): PatientRequestPayload => {
  const payload: PatientRequestPayload = {};

  if (typeof body.tipoDocumento === 'string' && patientDocumentTypes.includes(body.tipoDocumento as PatientDocumentType)) {
    payload.tipoDocumento = body.tipoDocumento as PatientDocumentType;
  }
  if (typeof body.dni === 'string') payload.dni = body.dni.trim();
  if (typeof body.nombres === 'string') payload.nombres = body.nombres.trim();
  if (typeof body.apellidos === 'string') payload.apellidos = body.apellidos.trim();
  if (body.fechaNacimiento !== undefined) payload.fechaNacimiento = normalizeDateInput(body.fechaNacimiento);
  if (body.email !== undefined) payload.email = normalizeNullableText(body.email);
  if (body.telefono !== undefined) payload.telefono = normalizeNullableText(body.telefono);
  if (body.direccion !== undefined) payload.direccion = normalizeNullableText(body.direccion);
  if (body.alergias !== undefined) payload.alergias = normalizeNullableText(body.alergias);
  if (body.antecedentes !== undefined) payload.antecedentes = normalizeNullableText(body.antecedentes);
  if (body.razonSocial !== undefined) payload.razonSocial = normalizeNullableText(body.razonSocial);
  if (body.ruc !== undefined) payload.ruc = normalizeNullableText(body.ruc);

  return payload;
};

const buildTreatmentPayloadFromRequest = (body: Request['body']): TreatmentRequestPayload => {
  const payload: TreatmentRequestPayload = {};

  if (typeof body.nombre === 'string') payload.nombre = body.nombre.trim();
  if (body.descripcion !== undefined) payload.descripcion = normalizeNullableText(body.descripcion);

  const normalizedCost = normalizeNumberInput(body.costoBase);
  if (normalizedCost !== null) payload.costoBase = normalizedCost;

  if (
    typeof body.estado === 'string' &&
    treatmentStatuses.includes(body.estado as TreatmentStatus)
  ) {
    payload.estado = body.estado as TreatmentStatus;
  }

  if (body.piezaDental !== undefined) payload.piezaDental = normalizeNullableText(body.piezaDental);
  if (body.fechaInicio !== undefined) payload.fechaInicio = normalizeDateInput(body.fechaInicio);
  if (body.fechaFin !== undefined) payload.fechaFin = normalizeDateInput(body.fechaFin);
  if (body.notasClinicas !== undefined) payload.notasClinicas = normalizeNullableText(body.notasClinicas);
  if (typeof body.activo === 'boolean') payload.activo = body.activo;

  return payload;
};

export const getPatients = async (_req: Request, res: Response) => {
  try {
    const patients = await prisma.paciente.findMany({
      orderBy: { fechaRegistro: 'desc' },
    });
    res.setHeader('X-Data-Source', 'database');
    res.json(patients);
  } catch (error) {
    console.warn('Database unavailable, using local patient fallback store.', error);

    try {
      const patients = await listFallbackPatients();
      res.setHeader('X-Data-Source', 'local-fallback');
      res.json(patients);
    } catch (fallbackError) {
      console.error('Error fetching patients:', fallbackError);
    res.status(500).json({ error: 'Error interno del servidor' });
    }
  }
};

export const getPatientProfile = async (req: Request, res: Response) => {
  const patientId = getPatientIdFromRequest(req, res);

  if (!patientId) {
    return;
  }

  try {
    const profile = await buildDatabasePatientProfile(patientId);

    if (!profile) {
      return res.status(404).json({ error: 'Paciente no encontrado' });
    }

    res.setHeader('X-Data-Source', 'database');
    return res.json(profile);
  } catch (error) {
    console.warn('Database unavailable, using local patient profile fallback store.', error);

    try {
      const profile = await buildFallbackPatientProfile(patientId);

      if (!profile) {
        return res.status(404).json({ error: 'Paciente no encontrado' });
      }

      res.setHeader('X-Data-Source', 'local-fallback');
      return res.json(profile);
    } catch (fallbackError) {
      console.error('Error fetching patient profile:', fallbackError);
      return res.status(500).json({ error: 'Error interno del servidor' });
    }
  }
};

export const createPatient = async (req: Request, res: Response) => {
  try {
    const payload = buildPatientCreateInput(buildPatientPayloadFromRequest(req.body));

    if (!payload) {
      return res.status(400).json({ error: 'Numero de documento, nombres y apellidos son obligatorios' });
    }

    const newPatient = await prisma.paciente.create({
      data: buildPatientCreateData(payload),
    });

    res.setHeader('X-Data-Source', 'database');
    res.status(201).json(newPatient);
  } catch (error) {
    if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
      return res.status(409).json({ error: 'Ya existe un paciente con ese documento' });
    }

    try {
      const payload = buildPatientCreateInput(buildPatientPayloadFromRequest(req.body));

      if (!payload) {
        return res.status(400).json({ error: 'Numero de documento, nombres y apellidos son obligatorios' });
      }

      const newPatient = await createFallbackPatient(payload);

      res.setHeader('X-Data-Source', 'local-fallback');
      return res.status(201).json(newPatient);
    } catch (fallbackError) {
      if (fallbackError instanceof DuplicatePatientError) {
        return res.status(409).json({ error: fallbackError.message });
      }

      console.error('Error creating patient:', fallbackError);
      return res.status(500).json({ error: 'Error interno del servidor' });
    }
  }
};

export const updatePatient = async (req: Request, res: Response) => {
  const patientId = getPatientIdFromRequest(req, res);

  if (!patientId) {
    return;
  }

  const payload = buildPatientPayloadFromRequest(req.body);

  if (payload.tipoDocumento !== undefined && !patientDocumentTypes.includes(payload.tipoDocumento)) {
    return res.status(400).json({ error: 'Selecciona un tipo de documento valido' });
  }

  if (payload.dni !== undefined && !payload.dni) {
    return res.status(400).json({ error: 'El numero de documento no puede quedar vacio' });
  }

  if (payload.nombres !== undefined && !payload.nombres) {
    return res.status(400).json({ error: 'Los nombres no pueden quedar vacios' });
  }

  if (payload.apellidos !== undefined && !payload.apellidos) {
    return res.status(400).json({ error: 'Los apellidos no pueden quedar vacios' });
  }

  try {
    const updatedPatient = await prisma.paciente.update({
      where: { id: patientId },
      data: buildPatientUpdateData(payload),
    });

    res.setHeader('X-Data-Source', 'database');
    return res.json(updatedPatient);
  } catch (error) {
    if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2025') {
      return res.status(404).json({ error: 'Paciente no encontrado' });
    }

    if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
      return res.status(409).json({ error: 'Ya existe un paciente con ese documento' });
    }

    try {
      const updatedPatient = await updateFallbackPatient(patientId, payload as UpdatePatientInput);
      res.setHeader('X-Data-Source', 'local-fallback');
      return res.json(updatedPatient);
    } catch (fallbackError) {
      if (fallbackError instanceof FallbackPatientNotFoundError) {
        return res.status(404).json({ error: fallbackError.message });
      }

      if (fallbackError instanceof DuplicatePatientError) {
        return res.status(409).json({ error: fallbackError.message });
      }

      console.error('Error updating patient:', fallbackError);
      return res.status(500).json({ error: 'Error interno del servidor' });
    }
  }
};

export const createPatientTreatment = async (req: Request, res: Response) => {
  const patientId = getPatientIdFromRequest(req, res);

  if (!patientId) {
    return;
  }

  const payload = buildTreatmentCreateInput(buildTreatmentPayloadFromRequest(req.body));

  if (!payload) {
    return res.status(400).json({ error: 'Nombre y costo base del tratamiento son obligatorios' });
  }

  try {
    const patient = await prisma.paciente.findUnique({
      where: { id: patientId },
    });

    if (!patient) {
      return res.status(404).json({ error: 'Paciente no encontrado' });
    }

    const treatment = await createDatabaseTreatment(patientId, payload);

    if (!treatment) {
      throw new Error('No se pudo registrar el tratamiento.');
    }

    res.setHeader('X-Data-Source', 'database');
    return res.status(201).json(serializeTreatment(treatment));
  } catch (error) {
    console.warn('Database unavailable while creating treatment, using local fallback store.', error);

    try {
      const treatment = await createFallbackTreatment(patientId, payload);
      res.setHeader('X-Data-Source', 'local-fallback');
      return res.status(201).json(serializeTreatment(treatment));
    } catch (fallbackError) {
      const message = fallbackError instanceof Error ? fallbackError.message : 'Error interno del servidor';
      return res.status(message === 'Paciente no encontrado.' ? 404 : 500).json({ error: message });
    }
  }
};

export const updatePatientTreatment = async (req: Request, res: Response) => {
  const patientId = getPatientIdFromRequest(req, res);

  if (!patientId) {
    return;
  }

  const treatmentId = getTreatmentIdFromRequest(req, res);

  if (!treatmentId) {
    return;
  }

  const payload = buildTreatmentPayloadFromRequest(req.body);

  if (payload.nombre !== undefined && !payload.nombre) {
    return res.status(400).json({ error: 'El nombre del tratamiento no puede quedar vacio' });
  }

  if (payload.costoBase !== undefined && payload.costoBase <= 0) {
    return res.status(400).json({ error: 'El costo base debe ser mayor a cero' });
  }

  try {
    const treatment = await findDatabaseTreatment(patientId, treatmentId);

    if (!treatment) {
      return res.status(404).json({ error: 'Tratamiento no encontrado' });
    }

    const updatedTreatment = await updateDatabaseTreatment(patientId, treatmentId, treatment, payload);

    if (!updatedTreatment) {
      return res.status(404).json({ error: 'Tratamiento no encontrado' });
    }

    res.setHeader('X-Data-Source', 'database');
    return res.json(serializeTreatment(updatedTreatment));
  } catch (error) {
    console.warn('Database unavailable while updating treatment, using local fallback store.', error);

    try {
      const updatedTreatment = await updateFallbackTreatment(patientId, treatmentId, payload);
      res.setHeader('X-Data-Source', 'local-fallback');
      return res.json(serializeTreatment(updatedTreatment));
    } catch (fallbackError) {
      if (fallbackError instanceof FallbackTreatmentNotFoundError) {
        return res.status(404).json({ error: fallbackError.message });
      }

      console.error('Error updating treatment:', fallbackError);
      return res.status(500).json({ error: 'Error interno del servidor' });
    }
  }
};

export const updateOdontogramPiece = async (req: Request, res: Response) => {
  const patientId = getPatientIdFromRequest(req, res);

  if (!patientId) {
    return;
  }

  const toothNumber = Number(resolveRouteParam(req.params.toothNumber));

  if (!Number.isInteger(toothNumber)) {
    return res.status(400).json({ error: 'Numero de pieza invalido' });
  }

  const normalizedStatus =
    typeof req.body.estado === 'string' && Object.values(EstadoPieza).includes(req.body.estado as EstadoPieza)
      ? (req.body.estado as EstadoPieza)
      : undefined;
  const normalizedFaces = Array.isArray(req.body.caras)
    ? req.body.caras.filter((face: unknown): face is string => typeof face === 'string')
    : undefined;
  const normalizedObservation =
    req.body.observacion !== undefined ? normalizeNullableText(req.body.observacion) : undefined;

  try {
    const patient = await prisma.paciente.findUnique({
      where: { id: patientId },
    });

    if (!patient) {
      return res.status(404).json({ error: 'Paciente no encontrado' });
    }

    const existingPiece = await prisma.odontogramaPieza.findFirst({
      where: {
        pacienteId: patientId,
        numero: toothNumber,
      },
      orderBy: { updatedAt: 'desc' },
    });

    const piece = existingPiece
      ? await prisma.odontogramaPieza.update({
          where: { id: existingPiece.id },
          data: {
            ...(normalizedStatus ? { estado: normalizedStatus } : {}),
            ...(normalizedFaces ? { caras: normalizedFaces } : {}),
            ...(normalizedObservation !== undefined ? { observacion: normalizedObservation } : {}),
          },
        })
      : await prisma.odontogramaPieza.create({
          data: {
            pacienteId: patientId,
            numero: toothNumber,
            estado: normalizedStatus ?? EstadoPieza.SANO,
            caras: normalizedFaces ?? [],
            observacion: normalizedObservation ?? null,
          },
        });

    res.setHeader('X-Data-Source', 'database');
    return res.json({
      id: piece.id,
      numero: piece.numero,
      estado: piece.estado,
      caras: normalizeFaces(piece.caras),
      observacion: piece.observacion,
      updatedAt: piece.updatedAt.toISOString(),
    });
  } catch (error) {
    console.warn('Database unavailable while updating odontogram, using local fallback store.', error);

    try {
      const piece = await updateFallbackOdontogramPiece(patientId, toothNumber, {
        ...(normalizedStatus ? { estado: normalizedStatus } : {}),
        ...(normalizedFaces ? { caras: normalizedFaces } : {}),
        ...(normalizedObservation !== undefined ? { observacion: normalizedObservation } : {}),
      });

      res.setHeader('X-Data-Source', 'local-fallback');
      return res.json(piece);
    } catch (fallbackError) {
      const message = fallbackError instanceof Error ? fallbackError.message : 'Error interno del servidor';
      return res.status(message === 'Paciente no encontrado' ? 404 : 500).json({ error: message });
    }
  }
};
