import { randomUUID } from 'crypto';
import { readDataFile, writeDataFile } from './local-store';

export const patientDocumentTypes = ['DNI', 'CE', 'PASAPORTE'] as const;
export type PatientDocumentType = (typeof patientDocumentTypes)[number];

export type StoredPatient = {
  id: string;
  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;
  fechaRegistro: string;
  updatedAt: string;
};

type NullableText = string | null | undefined;

export type CreatePatientInput = {
  tipoDocumento: PatientDocumentType;
  dni: string;
  nombres: string;
  apellidos: string;
  fechaNacimiento?: NullableText;
  email?: NullableText;
  telefono?: NullableText;
  direccion?: NullableText;
  alergias?: NullableText;
  antecedentes?: NullableText;
  razonSocial?: NullableText;
  ruc?: NullableText;
};

export type UpdatePatientInput = {
  tipoDocumento?: PatientDocumentType;
  dni?: NullableText;
  nombres?: NullableText;
  apellidos?: NullableText;
  fechaNacimiento?: NullableText;
  email?: NullableText;
  telefono?: NullableText;
  direccion?: NullableText;
  alergias?: NullableText;
  antecedentes?: NullableText;
  razonSocial?: NullableText;
  ruc?: NullableText;
};

export class DuplicatePatientError extends Error {
  constructor() {
    super('Ya existe un paciente con ese documento.');
  }
}

export class FallbackPatientNotFoundError extends Error {
  constructor() {
    super('Paciente no encontrado.');
  }
}

const fileName = 'patients.json';

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

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

const normalizeDocumentType = (value: unknown): PatientDocumentType =>
  patientDocumentTypes.includes(value as PatientDocumentType) ? (value as PatientDocumentType) : 'DNI';

const buildSeedPatients = (): StoredPatient[] => [
  {
    id: randomUUID(),
    tipoDocumento: 'DNI',
    dni: '72845123',
    nombres: 'Carlos Mendoza',
    apellidos: 'Ruiz',
    fechaNacimiento: '1984-06-12T00:00:00.000Z',
    email: 'cmendoza@email.com',
    telefono: '+51 987 654 321',
    direccion: 'Av. Benavides 1450, Miraflores',
    alergias: 'Penicilina',
    antecedentes: 'Hipertension controlada',
    razonSocial: null,
    ruc: null,
    fechaRegistro: '2026-03-15T09:00:00.000Z',
    updatedAt: '2026-03-15T09:00:00.000Z',
  },
  {
    id: randomUUID(),
    tipoDocumento: 'DNI',
    dni: '70123456',
    nombres: 'Lucia',
    apellidos: 'Fernandez Solis',
    fechaNacimiento: '1991-11-28T00:00:00.000Z',
    email: 'lfernandez@email.com',
    telefono: '+51 912 345 678',
    direccion: 'Calle Alcanfores 210, Miraflores',
    alergias: null,
    antecedentes: 'Bruxismo nocturno',
    razonSocial: null,
    ruc: null,
    fechaRegistro: '2026-02-10T11:30:00.000Z',
    updatedAt: '2026-02-10T11:30:00.000Z',
  },
  {
    id: randomUUID(),
    tipoDocumento: 'DNI',
    dni: '74567891',
    nombres: 'Roberto',
    apellidos: 'Vizcarra',
    fechaNacimiento: '1978-02-05T00:00:00.000Z',
    email: 'rvizcarra@email.com',
    telefono: '+51 999 888 777',
    direccion: 'Av. Larco 450, Miraflores',
    alergias: 'Latex',
    antecedentes: null,
    razonSocial: 'Vizcarra Group SAC',
    ruc: '20604567891',
    fechaRegistro: '2026-01-05T15:45:00.000Z',
    updatedAt: '2026-01-05T15:45:00.000Z',
  },
];

const normalizeStoredPatient = (patient: Partial<StoredPatient>): StoredPatient => ({
  id: patient.id ?? randomUUID(),
  tipoDocumento: normalizeDocumentType(patient.tipoDocumento),
  dni: patient.dni ?? '',
  nombres: patient.nombres ?? '',
  apellidos: patient.apellidos ?? '',
  fechaNacimiento: normalizeNullableText(patient.fechaNacimiento),
  email: normalizeNullableText(patient.email),
  telefono: normalizeNullableText(patient.telefono),
  direccion: normalizeNullableText(patient.direccion),
  alergias: normalizeNullableText(patient.alergias),
  antecedentes: normalizeNullableText(patient.antecedentes),
  razonSocial: normalizeNullableText(patient.razonSocial),
  ruc: normalizeNullableText(patient.ruc),
  fechaRegistro: patient.fechaRegistro ?? new Date().toISOString(),
  updatedAt: patient.updatedAt ?? new Date().toISOString(),
});

const readPatients = async () => {
  const patients = await readDataFile<Array<Partial<StoredPatient>>>(fileName, buildSeedPatients);
  return patients.map(normalizeStoredPatient);
};

const writePatients = async (patients: StoredPatient[]) => {
  await writeDataFile(fileName, patients);
};

export const listFallbackPatients = async () => {
  const patients = await readPatients();

  return patients.sort(
    (left, right) => new Date(right.fechaRegistro).getTime() - new Date(left.fechaRegistro).getTime(),
  );
};

export const getFallbackPatientById = async (patientId: string) => {
  const patients = await readPatients();
  return patients.find((patient) => patient.id === patientId) ?? null;
};

export const createFallbackPatient = async (input: CreatePatientInput) => {
  const patients = await readPatients();

  if (
    patients.some(
      (patient) => patient.tipoDocumento === input.tipoDocumento && patient.dni === input.dni.trim(),
    )
  ) {
    throw new DuplicatePatientError();
  }

  const now = new Date().toISOString();
  const patient: StoredPatient = {
    id: randomUUID(),
    tipoDocumento: input.tipoDocumento,
    dni: input.dni.trim(),
    nombres: input.nombres.trim(),
    apellidos: input.apellidos.trim(),
    fechaNacimiento: normalizeNullableText(input.fechaNacimiento),
    email: normalizeNullableText(input.email),
    telefono: normalizeNullableText(input.telefono),
    direccion: normalizeNullableText(input.direccion),
    alergias: normalizeNullableText(input.alergias),
    antecedentes: normalizeNullableText(input.antecedentes),
    razonSocial: normalizeNullableText(input.razonSocial),
    ruc: normalizeNullableText(input.ruc),
    fechaRegistro: now,
    updatedAt: now,
  };

  patients.unshift(patient);
  await writePatients(patients);

  return patient;
};

export const updateFallbackPatient = async (patientId: string, input: UpdatePatientInput) => {
  const patients = await readPatients();
  const patientIndex = patients.findIndex((patient) => patient.id === patientId);

  if (patientIndex === -1) {
    throw new FallbackPatientNotFoundError();
  }

  const currentPatient = patients[patientIndex];

  if (!currentPatient) {
    throw new FallbackPatientNotFoundError();
  }

  const nextDocumentType = input.tipoDocumento ?? currentPatient.tipoDocumento;
  const nextDni =
    typeof input.dni === 'string' && input.dni.trim() ? input.dni.trim() : currentPatient.dni;

  if (
    patients.some(
      (patient) =>
        patient.id !== patientId &&
        patient.tipoDocumento === nextDocumentType &&
        patient.dni === nextDni,
    )
  ) {
    throw new DuplicatePatientError();
  }

  const updatedPatient: StoredPatient = {
    ...currentPatient,
    tipoDocumento: nextDocumentType,
    dni: nextDni,
    nombres:
      typeof input.nombres === 'string' && input.nombres.trim() ? input.nombres.trim() : currentPatient.nombres,
    apellidos:
      typeof input.apellidos === 'string' && input.apellidos.trim()
        ? input.apellidos.trim()
        : currentPatient.apellidos,
    fechaNacimiento:
      input.fechaNacimiento !== undefined
        ? normalizeNullableText(input.fechaNacimiento)
        : currentPatient.fechaNacimiento,
    email: input.email !== undefined ? normalizeNullableText(input.email) : currentPatient.email,
    telefono: input.telefono !== undefined ? normalizeNullableText(input.telefono) : currentPatient.telefono,
    direccion: input.direccion !== undefined ? normalizeNullableText(input.direccion) : currentPatient.direccion,
    alergias: input.alergias !== undefined ? normalizeNullableText(input.alergias) : currentPatient.alergias,
    antecedentes:
      input.antecedentes !== undefined ? normalizeNullableText(input.antecedentes) : currentPatient.antecedentes,
    razonSocial:
      input.razonSocial !== undefined ? normalizeNullableText(input.razonSocial) : currentPatient.razonSocial,
    ruc: input.ruc !== undefined ? normalizeNullableText(input.ruc) : currentPatient.ruc,
    updatedAt: new Date().toISOString(),
  };

  patients[patientIndex] = updatedPatient;
  await writePatients(patients);

  return updatedPatient;
};
