import { Role } from '@prisma/client';
import type { Request, Response } from 'express';
import prisma from '../config/db';
import { buildDatabasePatientProfile, buildFallbackPatientProfile } from './patient.controller';
import { listNotificationFeed } from '../services/communication.store';
import { listFallbackAppointments } from '../services/appointment.store';
import { findFallbackUserById } from '../services/settings.store';

const doctorRoles = [Role.ADMIN, Role.DENTIST] as const;
const activeTreatmentStatuses = new Set(['PLANIFICADO', 'EN_PROCESO', 'PAUSADO']);

type ResolvedDoctorUser = {
  id: string;
  email: string;
  name: string;
  role: Role;
  sourceMode: 'database' | 'local-fallback';
};

type DoctorAppointmentRecord = {
  id: string;
  fecha: Date | string;
  motivo: string;
  estado: string;
  notas: string | null;
  pacienteId: string;
  paciente: {
    id: string;
    nombres: string;
    apellidos: string;
    email: string | null;
    telefono: string | null;
  };
  dentista: {
    id: string;
    name: string;
  };
};

const normalizeOptionalString = (value: unknown) =>
  typeof value === 'string' && value.trim() ? value.trim() : null;

const isDoctorRole = (role: Role) => role === Role.ADMIN || role === Role.DENTIST;

const resolveHeaderDoctorId = (req: Request) => {
  const rawHeaderValue = req.header('x-operational-user-id') ?? req.header('x-user-id');
  return normalizeOptionalString(rawHeaderValue ?? req.query.userId);
};

const getDayBounds = (selectedDate: Date) => {
  const dayStart = new Date(selectedDate);
  dayStart.setHours(0, 0, 0, 0);

  const dayEnd = new Date(selectedDate);
  dayEnd.setHours(23, 59, 59, 999);

  return { dayStart, dayEnd };
};

const serializeDoctor = (doctor: ResolvedDoctorUser) => ({
  id: doctor.id,
  email: doctor.email,
  name: doctor.name,
  role: doctor.role,
});

const serializeDoctorAppointment = (appointment: DoctorAppointmentRecord) => ({
  id: appointment.id,
  fecha: appointment.fecha instanceof Date ? appointment.fecha.toISOString() : appointment.fecha,
  motivo: appointment.motivo,
  estado: appointment.estado,
  notas: appointment.notas,
  paciente: {
    id: appointment.paciente.id,
    nombres: appointment.paciente.nombres,
    apellidos: appointment.paciente.apellidos,
    nombreCompleto: `${appointment.paciente.nombres} ${appointment.paciente.apellidos}`.trim(),
    email: appointment.paciente.email,
    telefono: appointment.paciente.telefono,
  },
  dentista: {
    id: appointment.dentista.id,
    nombre: appointment.dentista.name,
  },
});

const buildAgendaMetrics = (appointments: ReturnType<typeof serializeDoctorAppointment>[]) => ({
  total: appointments.length,
  programadas: appointments.filter((appointment) => appointment.estado === 'PROGRAMADA').length,
  confirmadas: appointments.filter((appointment) => appointment.estado === 'CONFIRMADA').length,
  enSala: appointments.filter((appointment) => appointment.estado === 'EN_SALA').length,
  atendidas: appointments.filter((appointment) => appointment.estado === 'ATENDIDA').length,
});

const resolveDoctorUser = async (doctorUserId: string) => {
  try {
    const user = await prisma.user.findFirst({
      where: {
        id: doctorUserId,
        active: true,
        role: {
          in: [...doctorRoles],
        },
      },
    });

    if (user) {
      return {
        id: user.id,
        email: user.email,
        name: user.name,
        role: user.role,
        sourceMode: 'database' as const,
      };
    }
  } catch (error) {
    console.warn('Database unavailable while resolving doctor mobile user, using local fallback store.', error);
  }

  const fallbackUser = await findFallbackUserById(doctorUserId);

  if (fallbackUser && fallbackUser.active && isDoctorRole(fallbackUser.role)) {
    return {
      id: fallbackUser.id,
      email: fallbackUser.email,
      name: fallbackUser.name,
      role: fallbackUser.role,
      sourceMode: 'local-fallback' as const,
    };
  }

  return null;
};

const getDoctorContext = async (req: Request, res: Response) => {
  const doctorUserId = resolveHeaderDoctorId(req);

  if (!doctorUserId) {
    res.status(401).json({ error: 'Debes indicar el usuario operativo del doctor.' });
    return null;
  }

  const doctor = await resolveDoctorUser(doctorUserId);

  if (!doctor) {
    res.status(403).json({ error: 'El usuario no tiene permisos para el portal del doctor.' });
    return null;
  }

  return doctor;
};

const listDatabaseDoctorAppointments = async (doctorId: string, input?: { from?: Date; to?: Date; limit?: number }) => {
  const appointments = await prisma.cita.findMany({
    where: {
      dentistaId: doctorId,
      ...(input?.from || input?.to
        ? {
            fecha: {
              ...(input.from ? { gte: input.from } : {}),
              ...(input.to ? { lte: input.to } : {}),
            },
          }
        : {}),
    },
    include: {
      paciente: true,
      dentista: true,
    },
    orderBy: {
      fecha: 'asc',
    },
    ...(input?.limit ? { take: input.limit } : {}),
  });

  return appointments.map(
    (appointment) =>
      ({
        id: appointment.id,
        fecha: appointment.fecha,
        motivo: appointment.motivo,
        estado: appointment.estado,
        notas: appointment.notas,
        pacienteId: appointment.pacienteId,
        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,
          name: appointment.dentista.name,
        },
      }) satisfies DoctorAppointmentRecord,
  );
};

const listFallbackDoctorAppointments = async (doctorId: string, input?: { from?: Date; to?: Date; limit?: number }) => {
  const appointments = await listFallbackAppointments();

  return appointments
    .filter((appointment) => appointment.dentistaId === doctorId)
    .filter((appointment) => {
      const appointmentDate = new Date(appointment.fecha);

      if (input?.from && appointmentDate < input.from) {
        return false;
      }

      if (input?.to && appointmentDate > input.to) {
        return false;
      }

      return true;
    })
    .sort((left, right) => new Date(left.fecha).getTime() - new Date(right.fecha).getTime())
    .slice(0, input?.limit ?? Number.MAX_SAFE_INTEGER)
    .map(
      (appointment) =>
        ({
          id: appointment.id,
          fecha: appointment.fecha,
          motivo: appointment.motivo,
          estado: appointment.estado,
          notas: appointment.notas,
          pacienteId: appointment.pacienteId,
          paciente: {
            id: appointment.pacienteId,
            nombres: appointment.pacienteNombres,
            apellidos: appointment.pacienteApellidos,
            email: appointment.pacienteEmail,
            telefono: appointment.pacienteTelefono,
          },
          dentista: {
            id: appointment.dentistaId,
            name: appointment.dentistaNombre,
          },
        }) satisfies DoctorAppointmentRecord,
    );
};

const findDatabaseDoctorAppointment = async (doctorId: string, appointmentId: string) => {
  const appointment = await prisma.cita.findFirst({
    where: {
      id: appointmentId,
      dentistaId: doctorId,
    },
    include: {
      paciente: true,
      dentista: true,
    },
  });

  if (!appointment) {
    return null;
  }

  return {
    id: appointment.id,
    fecha: appointment.fecha,
    motivo: appointment.motivo,
    estado: appointment.estado,
    notas: appointment.notas,
    pacienteId: appointment.pacienteId,
    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,
      name: appointment.dentista.name,
    },
  } satisfies DoctorAppointmentRecord;
};

const findFallbackDoctorAppointment = async (doctorId: string, appointmentId: string) => {
  const appointments = await listFallbackAppointments();
  const appointment = appointments.find(
    (currentAppointment) => currentAppointment.id === appointmentId && currentAppointment.dentistaId === doctorId,
  );

  if (!appointment) {
    return null;
  }

  return {
    id: appointment.id,
    fecha: appointment.fecha,
    motivo: appointment.motivo,
    estado: appointment.estado,
    notas: appointment.notas,
    pacienteId: appointment.pacienteId,
    paciente: {
      id: appointment.pacienteId,
      nombres: appointment.pacienteNombres,
      apellidos: appointment.pacienteApellidos,
      email: appointment.pacienteEmail,
      telefono: appointment.pacienteTelefono,
    },
    dentista: {
      id: appointment.dentistaId,
      name: appointment.dentistaNombre,
    },
  } satisfies DoctorAppointmentRecord;
};

const hasDatabasePatientAssignment = async (doctorId: string, patientId: string) => {
  const relatedAppointment = await prisma.cita.findFirst({
    where: {
      dentistaId: doctorId,
      pacienteId: patientId,
    },
    select: {
      id: true,
    },
  });

  return Boolean(relatedAppointment);
};

const hasFallbackPatientAssignment = async (doctorId: string, patientId: string) => {
  const appointments = await listFallbackAppointments();
  return appointments.some(
    (appointment) => appointment.dentistaId === doctorId && appointment.pacienteId === patientId,
  );
};

export const getDoctorMobileTodayAgenda = async (req: Request, res: Response) => {
  const doctor = await getDoctorContext(req, res);

  if (!doctor) {
    return;
  }

  const { dayStart, dayEnd } = getDayBounds(new Date());

  try {
    const appointments = await listDatabaseDoctorAppointments(doctor.id, {
      from: dayStart,
      to: dayEnd,
    });
    const serializedAppointments = appointments.map(serializeDoctorAppointment);

    res.setHeader('X-Data-Source', 'database');
    return res.json({
      doctor: serializeDoctor(doctor),
      generatedAt: new Date().toISOString(),
      appointments: serializedAppointments,
      metrics: buildAgendaMetrics(serializedAppointments),
    });
  } catch (error) {
    console.warn('Database unavailable while loading doctor mobile today agenda, using local fallback store.', error);
  }

  const appointments = await listFallbackDoctorAppointments(doctor.id, {
    from: dayStart,
    to: dayEnd,
  });
  const serializedAppointments = appointments.map(serializeDoctorAppointment);

  res.setHeader('X-Data-Source', 'local-fallback');
  return res.json({
    doctor: serializeDoctor(doctor),
    generatedAt: new Date().toISOString(),
    appointments: serializedAppointments,
    metrics: buildAgendaMetrics(serializedAppointments),
  });
};

export const getDoctorMobileUpcomingAgenda = async (req: Request, res: Response) => {
  const doctor = await getDoctorContext(req, res);

  if (!doctor) {
    return;
  }

  const now = new Date();

  try {
    const appointments = await listDatabaseDoctorAppointments(doctor.id, {
      from: now,
      limit: 12,
    });

    res.setHeader('X-Data-Source', 'database');
    return res.json({
      doctor: serializeDoctor(doctor),
      appointments: appointments.map(serializeDoctorAppointment),
    });
  } catch (error) {
    console.warn('Database unavailable while loading doctor mobile upcoming agenda, using local fallback store.', error);
  }

  const appointments = await listFallbackDoctorAppointments(doctor.id, {
    from: now,
    limit: 12,
  });

  res.setHeader('X-Data-Source', 'local-fallback');
  return res.json({
    doctor: serializeDoctor(doctor),
    appointments: appointments.map(serializeDoctorAppointment),
  });
};

export const getDoctorMobileAppointmentDetail = async (req: Request, res: Response) => {
  const doctor = await getDoctorContext(req, res);

  if (!doctor) {
    return;
  }

  const appointmentId = normalizeOptionalString(req.params.appointmentId);

  if (!appointmentId) {
    return res.status(400).json({ error: 'La cita indicada no es valida.' });
  }

  try {
    const appointment = await findDatabaseDoctorAppointment(doctor.id, appointmentId);

    if (!appointment) {
      return res.status(404).json({ error: 'Cita no encontrada para este doctor.' });
    }

    res.setHeader('X-Data-Source', 'database');
    return res.json({
      doctor: serializeDoctor(doctor),
      appointment: serializeDoctorAppointment(appointment),
    });
  } catch (error) {
    console.warn('Database unavailable while loading doctor mobile appointment detail, using local fallback store.', error);
  }

  const appointment = await findFallbackDoctorAppointment(doctor.id, appointmentId);

  if (!appointment) {
    return res.status(404).json({ error: 'Cita no encontrada para este doctor.' });
  }

  res.setHeader('X-Data-Source', 'local-fallback');
  return res.json({
    doctor: serializeDoctor(doctor),
    appointment: serializeDoctorAppointment(appointment),
  });
};

export const getDoctorMobilePatientSummary = async (req: Request, res: Response) => {
  const doctor = await getDoctorContext(req, res);

  if (!doctor) {
    return;
  }

  const patientId = normalizeOptionalString(req.params.patientId);

  if (!patientId) {
    return res.status(400).json({ error: 'El paciente indicado no es valido.' });
  }

  try {
    const hasAssignment = await hasDatabasePatientAssignment(doctor.id, patientId);

    if (!hasAssignment) {
      return res.status(403).json({ error: 'No tienes una cita asociada con este paciente.' });
    }

    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({
      doctor: serializeDoctor(doctor),
      patient: profile.patient,
      clinicalHistory: profile.clinicalHistory,
      metrics: {
        upcomingAppointments: profile.metrics.upcomingAppointments,
        activeTreatmentCount: profile.metrics.activeTreatmentCount,
        affectedTeethCount: profile.odontogram.summary.affectedTeethCount,
        completedTreatments: profile.clinicalHistory.completedCount,
      },
      odontogramSummary: profile.odontogram.summary,
      recentAppointments: profile.appointments.slice(0, 6),
      activeTreatments: profile.treatments
        .filter((treatment) => treatment.activo || activeTreatmentStatuses.has(treatment.estado))
        .slice(0, 6),
    });
  } catch (error) {
    console.warn('Database unavailable while loading doctor mobile patient summary, using local fallback store.', error);
  }

  const hasAssignment = await hasFallbackPatientAssignment(doctor.id, patientId);

  if (!hasAssignment) {
    return res.status(403).json({ error: 'No tienes una cita asociada con este paciente.' });
  }

  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({
    doctor: serializeDoctor(doctor),
    patient: profile.patient,
    clinicalHistory: profile.clinicalHistory,
    metrics: {
      upcomingAppointments: profile.metrics.upcomingAppointments,
      activeTreatmentCount: profile.metrics.activeTreatmentCount,
      affectedTeethCount: profile.odontogram.summary.affectedTeethCount,
      completedTreatments: profile.clinicalHistory.completedCount,
    },
    odontogramSummary: profile.odontogram.summary,
    recentAppointments: profile.appointments.slice(0, 6),
    activeTreatments: profile.treatments
      .filter((treatment) => treatment.activo || activeTreatmentStatuses.has(treatment.estado))
      .slice(0, 6),
  });
};

export const getDoctorMobileNotifications = async (req: Request, res: Response) => {
  const doctor = await getDoctorContext(req, res);

  if (!doctor) {
    return;
  }

  try {
    const notifications = await listNotificationFeed(doctor.id);
    const unreadCount = notifications.filter((notification) => !notification.readAt).length;

    res.setHeader('X-Data-Source', 'local-fallback');
    return res.json({
      doctor: serializeDoctor(doctor),
      notifications,
      unreadCount,
    });
  } catch (error) {
    console.error('Error loading doctor mobile notifications:', error);
    return res.status(500).json({ error: 'Error interno del servidor.' });
  }
};
