import type { Request, Response } from 'express';
import {
  DoctorScheduleNotFoundError,
  DoctorScheduleOverrideAlreadyExistsError,
  DoctorScheduleOverrideNotFoundError,
  InvalidDoctorScheduleError,
  createDoctorScheduleOverride,
  deleteDoctorScheduleOverride,
  getDoctorWeeklySchedule,
  listDoctorScheduleOverrides,
  updateDoctorScheduleOverride,
  updateDoctorWeeklySchedule,
} from '../services/doctor-availability.service';

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

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

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

  return doctorUserId;
};

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

  if (!dateKey) {
    res.status(400).json({ error: 'Fecha invalida' });
    return null;
  }

  return dateKey;
};

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

const normalizeScheduleEntries = (input: unknown) => {
  const rawEntries =
    Array.isArray(input) ? input : input && typeof input === 'object' && Array.isArray((input as { schedule?: unknown[] }).schedule)
      ? (input as { schedule: unknown[] }).schedule
      : null;

  if (!rawEntries) {
    throw new InvalidDoctorScheduleError('Debes enviar una lista de dias para actualizar el horario.');
  }

  return rawEntries.map((entry) => {
    if (!entry || typeof entry !== 'object') {
      throw new InvalidDoctorScheduleError('Cada dia del horario debe ser un objeto valido.');
    }

    const candidate = entry as Record<string, unknown>;

    if (typeof candidate.isWorkingDay !== 'boolean') {
      throw new InvalidDoctorScheduleError('Cada dia debe indicar si es laborable o no.');
    }

    return {
      dayOfWeek: typeof candidate.dayOfWeek === 'number' ? candidate.dayOfWeek : Number(candidate.dayOfWeek),
      isWorkingDay: candidate.isWorkingDay,
      startTime: normalizeNullableText(candidate.startTime),
      endTime: normalizeNullableText(candidate.endTime),
      slotDurationMinutes:
        typeof candidate.slotDurationMinutes === 'number'
          ? candidate.slotDurationMinutes
          : candidate.slotDurationMinutes === null || candidate.slotDurationMinutes === undefined
            ? null
            : Number(candidate.slotDurationMinutes),
      breakStartTime: normalizeNullableText(candidate.breakStartTime),
      breakEndTime: normalizeNullableText(candidate.breakEndTime),
    };
  });
};

const normalizeOverrideInput = (input: unknown) => {
  if (!input || typeof input !== 'object') {
    throw new InvalidDoctorScheduleError('Debes enviar una excepcion valida.');
  }

  const candidate = input as Record<string, unknown>;

  if (typeof candidate.isWorkingDay !== 'boolean') {
    throw new InvalidDoctorScheduleError('La excepcion debe indicar si el doctor labora ese dia.');
  }

  return {
    dateKey: typeof candidate.dateKey === 'string' ? candidate.dateKey.trim() : '',
    isWorkingDay: candidate.isWorkingDay,
    startTime: normalizeNullableText(candidate.startTime),
    endTime: normalizeNullableText(candidate.endTime),
    slotDurationMinutes:
      typeof candidate.slotDurationMinutes === 'number'
        ? candidate.slotDurationMinutes
        : candidate.slotDurationMinutes === null || candidate.slotDurationMinutes === undefined
          ? null
          : Number(candidate.slotDurationMinutes),
    breakStartTime: normalizeNullableText(candidate.breakStartTime),
    breakEndTime: normalizeNullableText(candidate.breakEndTime),
    reason: normalizeNullableText(candidate.reason),
  };
};

const handleDoctorScheduleError = (error: unknown, res: Response) => {
  if (error instanceof DoctorScheduleNotFoundError) {
    return res.status(404).json({ error: error.message });
  }

  if (error instanceof InvalidDoctorScheduleError) {
    return res.status(400).json({ error: error.message });
  }

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

  if (error instanceof DoctorScheduleOverrideNotFoundError) {
    return res.status(404).json({ error: error.message });
  }

  console.error('Error handling doctor schedule request:', error);
  return res.status(500).json({ error: 'Error interno del servidor' });
};

export const getDoctorSchedule = async (req: Request, res: Response) => {
  const doctorUserId = getDoctorIdFromRequest(req, res);

  if (!doctorUserId) {
    return;
  }

  try {
    const result = await getDoctorWeeklySchedule(doctorUserId);
    res.setHeader('X-Data-Source', result.sourceMode);
    return res.json({ schedule: result.schedule });
  } catch (error) {
    return handleDoctorScheduleError(error, res);
  }
};

export const putDoctorSchedule = async (req: Request, res: Response) => {
  const doctorUserId = getDoctorIdFromRequest(req, res);

  if (!doctorUserId) {
    return;
  }

  try {
    const schedule = normalizeScheduleEntries(req.body);
    const result = await updateDoctorWeeklySchedule(doctorUserId, schedule);
    res.setHeader('X-Data-Source', result.sourceMode);
    return res.json({ schedule: result.schedule });
  } catch (error) {
    return handleDoctorScheduleError(error, res);
  }
};

export const getDoctorOverrides = async (req: Request, res: Response) => {
  const doctorUserId = getDoctorIdFromRequest(req, res);

  if (!doctorUserId) {
    return;
  }

  try {
    const result = await listDoctorScheduleOverrides(doctorUserId, {
      dateFrom: typeof req.query.dateFrom === 'string' ? req.query.dateFrom : null,
      dateTo: typeof req.query.dateTo === 'string' ? req.query.dateTo : null,
    });
    res.setHeader('X-Data-Source', result.sourceMode);
    return res.json({ overrides: result.overrides });
  } catch (error) {
    return handleDoctorScheduleError(error, res);
  }
};

export const postDoctorOverride = async (req: Request, res: Response) => {
  const doctorUserId = getDoctorIdFromRequest(req, res);

  if (!doctorUserId) {
    return;
  }

  try {
    const overrideInput = normalizeOverrideInput(req.body);
    const result = await createDoctorScheduleOverride(doctorUserId, overrideInput);
    res.setHeader('X-Data-Source', result.sourceMode);
    return res.status(201).json({ override: result.override });
  } catch (error) {
    return handleDoctorScheduleError(error, res);
  }
};

export const putDoctorOverride = async (req: Request, res: Response) => {
  const doctorUserId = getDoctorIdFromRequest(req, res);
  const dateKey = getDateKeyFromRequest(req, res);

  if (!doctorUserId || !dateKey) {
    return;
  }

  try {
    const overrideInput = normalizeOverrideInput(req.body);
    const result = await updateDoctorScheduleOverride(doctorUserId, dateKey, {
      isWorkingDay: overrideInput.isWorkingDay,
      startTime: overrideInput.startTime,
      endTime: overrideInput.endTime,
      slotDurationMinutes: overrideInput.slotDurationMinutes,
      breakStartTime: overrideInput.breakStartTime,
      breakEndTime: overrideInput.breakEndTime,
      reason: overrideInput.reason,
    });
    res.setHeader('X-Data-Source', result.sourceMode);
    return res.json({ override: result.override });
  } catch (error) {
    return handleDoctorScheduleError(error, res);
  }
};

export const removeDoctorOverride = async (req: Request, res: Response) => {
  const doctorUserId = getDoctorIdFromRequest(req, res);
  const dateKey = getDateKeyFromRequest(req, res);

  if (!doctorUserId || !dateKey) {
    return;
  }

  try {
    const result = await deleteDoctorScheduleOverride(doctorUserId, dateKey);
    res.setHeader('X-Data-Source', result.sourceMode);
    return res.status(204).send();
  } catch (error) {
    return handleDoctorScheduleError(error, res);
  }
};
