import { EstadoCita } from '@prisma/client';
import { randomUUID } from 'crypto';
import prisma from '../config/db';
import { listFallbackAppointments } from './appointment.store';
import { recordAppointmentReminderCommunications } from './communication.store';
import { readDataFile, writeDataFile } from './local-store';

type ReminderStage = 'DAY_BEFORE' | 'TWO_HOURS_BEFORE';
type ReminderRecipientType = 'DENTIST' | 'PATIENT';

type ReminderDeliveryRecord = {
  id: string;
  appointmentId: string;
  startsAt: string;
  stage: ReminderStage;
  recipientType: ReminderRecipientType;
  recipientId: string;
  sentAt: string;
};

type StoredReminderDeliveryRecord = Partial<ReminderDeliveryRecord> & {
  dentistId?: string;
};

type ReminderCandidate = {
  id: string;
  fecha: string;
  motivo: string;
  estado: string;
  paciente: {
    id: string;
    nombres: string;
    apellidos: string;
    email: string | null;
    telefono: string | null;
  };
  dentista: {
    id: string;
    nombre: string;
    email: string | null;
  };
  sourceMode: 'database' | 'local-fallback';
};

type ReminderWindow = {
  stage: ReminderStage;
  minMs: number;
  maxMs: number;
};

const reminderDeliveriesFileName = 'appointment-reminder-deliveries.json';
const pollIntervalMs = 60_000;
const cleanupRetentionMs = 1000 * 60 * 60 * 24 * 14;
const reminderWindows: ReminderWindow[] = [
  {
    stage: 'DAY_BEFORE',
    minMs: 1000 * 60 * 60 * 23,
    maxMs: 1000 * 60 * 60 * 25,
  },
  {
    stage: 'TWO_HOURS_BEFORE',
    minMs: 1000 * 60 * 90,
    maxMs: 1000 * 60 * 150,
  },
];

let schedulerStarted = false;
let schedulerTimer: NodeJS.Timeout | null = null;
let schedulerInFlight = false;

const defaultReminderDeliveries = () => [] as ReminderDeliveryRecord[];

const isReminderEligibleStatus = (status: string) =>
  status === EstadoCita.PROGRAMADA || status === EstadoCita.CONFIRMADA || status === EstadoCita.EN_SALA;

const normalizeReminderStage = (value: unknown): ReminderStage =>
  value === 'TWO_HOURS_BEFORE' ? 'TWO_HOURS_BEFORE' : 'DAY_BEFORE';

const normalizeReminderRecipientType = (value: unknown): ReminderRecipientType =>
  value === 'PATIENT' ? 'PATIENT' : 'DENTIST';

const normalizeReminderDeliveryRecord = (record: StoredReminderDeliveryRecord): ReminderDeliveryRecord | null => {
  const appointmentId = typeof record.appointmentId === 'string' ? record.appointmentId : '';
  const startsAt = typeof record.startsAt === 'string' ? record.startsAt : '';
  const recipientId =
    typeof record.recipientId === 'string' && record.recipientId
      ? record.recipientId
      : typeof record.dentistId === 'string' && record.dentistId
        ? record.dentistId
        : '';

  if (!appointmentId || !startsAt || !recipientId) {
    return null;
  }

  return {
    id: typeof record.id === 'string' && record.id ? record.id : randomUUID(),
    appointmentId,
    startsAt,
    stage: normalizeReminderStage(record.stage),
    recipientType: normalizeReminderRecipientType(record.recipientType),
    recipientId,
    sentAt: typeof record.sentAt === 'string' ? record.sentAt : new Date().toISOString(),
  };
};

const buildReminderKey = (
  appointmentId: string,
  startsAt: string,
  stage: ReminderStage,
  recipientType: ReminderRecipientType,
  recipientId: string,
) => `${appointmentId}:${startsAt}:${stage}:${recipientType}:${recipientId}`;

const readReminderDeliveries = async () => {
  const storedRecords = await readDataFile<StoredReminderDeliveryRecord[]>(
    reminderDeliveriesFileName,
    defaultReminderDeliveries,
  );

  return storedRecords
    .map(normalizeReminderDeliveryRecord)
    .filter((record): record is ReminderDeliveryRecord => Boolean(record));
};

const writeReminderDeliveries = async (records: ReminderDeliveryRecord[]) =>
  writeDataFile(reminderDeliveriesFileName, records);

const listDatabaseReminderCandidates = async (windowEnd: Date): Promise<ReminderCandidate[]> => {
  const appointments = await prisma.cita.findMany({
    where: {
      fecha: {
        lte: windowEnd,
      },
      estado: {
        in: [EstadoCita.PROGRAMADA, EstadoCita.CONFIRMADA, EstadoCita.EN_SALA],
      },
    },
    include: {
      paciente: true,
      dentista: true,
    },
    orderBy: {
      fecha: 'asc',
    },
  });

  return appointments.map((appointment) => ({
    id: appointment.id,
    fecha: appointment.fecha.toISOString(),
    motivo: appointment.motivo,
    estado: appointment.estado,
    paciente: {
      id: appointment.paciente.id,
      nombres: appointment.paciente.nombres,
      apellidos: appointment.paciente.apellidos,
      email: appointment.paciente.email,
      telefono: appointment.paciente.telefono,
    },
    dentista: {
      id: appointment.dentista.id,
      nombre: appointment.dentista.name,
      email: appointment.dentista.email,
    },
    sourceMode: 'database',
  }));
};

const listFallbackReminderCandidates = async (): Promise<ReminderCandidate[]> => {
  const appointments = await listFallbackAppointments();

  return appointments
    .filter((appointment) => isReminderEligibleStatus(appointment.estado))
    .map((appointment) => ({
      id: appointment.id,
      fecha: appointment.fecha,
      motivo: appointment.motivo,
      estado: appointment.estado,
      paciente: {
        id: appointment.pacienteId,
        nombres: appointment.pacienteNombres,
        apellidos: appointment.pacienteApellidos,
        email: appointment.pacienteEmail,
        telefono: appointment.pacienteTelefono,
      },
      dentista: {
        id: appointment.dentistaId,
        nombre: appointment.dentistaNombre,
        email: null,
      },
      sourceMode: 'local-fallback',
    }));
};

const listReminderCandidates = async (windowEnd: Date): Promise<ReminderCandidate[]> => {
  try {
    return await listDatabaseReminderCandidates(windowEnd);
  } catch (error) {
    console.warn('Reminder scheduler is using local fallback appointments.', error);
    return listFallbackReminderCandidates();
  }
};

const runReminderCycle = async () => {
  if (schedulerInFlight) {
    return;
  }

  schedulerInFlight = true;

  try {
    const now = new Date();
    const maxWindowMs = Math.max(...reminderWindows.map((window) => window.maxMs));
    const windowEnd = new Date(now.getTime() + maxWindowMs);
    const [appointments, deliveries] = await Promise.all([
      listReminderCandidates(windowEnd),
      readReminderDeliveries(),
    ]);

    const deliveryKeys = new Set(
      deliveries.map((delivery) =>
        buildReminderKey(
          delivery.appointmentId,
          delivery.startsAt,
          delivery.stage,
          delivery.recipientType,
          delivery.recipientId,
        ),
      ),
    );
    const nextDeliveries = deliveries.filter(
      (delivery) => now.getTime() - new Date(delivery.sentAt).getTime() <= cleanupRetentionMs,
    );

    for (const appointment of appointments) {
      const startsAt = new Date(appointment.fecha);

      if (Number.isNaN(startsAt.getTime()) || !isReminderEligibleStatus(appointment.estado)) {
        continue;
      }

      const msUntilStart = startsAt.getTime() - now.getTime();

      for (const reminderWindow of reminderWindows) {
        if (msUntilStart < reminderWindow.minMs || msUntilStart > reminderWindow.maxMs) {
          continue;
        }

        const dentistDeliveryKey = buildReminderKey(
          appointment.id,
          startsAt.toISOString(),
          reminderWindow.stage,
          'DENTIST',
          appointment.dentista.id,
        );

        if (deliveryKeys.has(dentistDeliveryKey)) {
          continue;
        }

        const sentAt = new Date().toISOString();
        nextDeliveries.unshift({
          id: randomUUID(),
          appointmentId: appointment.id,
          startsAt: startsAt.toISOString(),
          recipientType: 'DENTIST',
          recipientId: appointment.dentista.id,
          stage: reminderWindow.stage,
          sentAt,
        });
        deliveryKeys.add(dentistDeliveryKey);

        try {
          await recordAppointmentReminderCommunications({
            appointmentId: appointment.id,
            startsAt: startsAt.toISOString(),
            patient: {
              id: appointment.paciente.id,
              name: `${appointment.paciente.nombres} ${appointment.paciente.apellidos}`.trim(),
              email: appointment.paciente.email,
              phone: appointment.paciente.telefono,
            },
            dentist: {
              id: appointment.dentista.id,
              name: appointment.dentista.nombre,
              email: appointment.dentista.email,
            },
            motivo: appointment.motivo,
            status: appointment.estado,
            stage: reminderWindow.stage,
            sourceMode: appointment.sourceMode,
          });
        } catch (communicationError) {
          console.error('Error while emitting appointment reminder communications:', communicationError);
        }
      }
    }

    await writeReminderDeliveries(nextDeliveries.slice(0, 500));
  } catch (error) {
    console.error('Error while processing appointment reminders:', error);
  } finally {
    schedulerInFlight = false;
  }
};

export const startAppointmentReminderScheduler = () => {
  if (schedulerStarted) {
    return;
  }

  schedulerStarted = true;
  void runReminderCycle();
  schedulerTimer = setInterval(() => {
    void runReminderCycle();
  }, pollIntervalMs);

  if (typeof schedulerTimer.unref === 'function') {
    schedulerTimer.unref();
  }
};
