import { z } from 'zod';

const TIME_REGEX = /^([01]\d|2[0-3]):([0-5]\d)$/;

function toMinutes(value: string): number {
  const [hours, minutes] = value.split(':').map((part) => Number(part));
  return hours * 60 + minutes;
}

function hasOverlaps(
  availability: Array<{
    dayOfWeek: DayOfWeek;
    startTime: string;
    endTime: string;
  }>
): boolean {
  const grouped = new Map<
    DayOfWeek,
    Array<{
      start: number;
      end: number;
    }>
  >();

  for (const range of availability) {
    const start = toMinutes(range.startTime);
    const end = toMinutes(range.endTime);
    const current = grouped.get(range.dayOfWeek) ?? [];
    current.push({ start, end });
    grouped.set(range.dayOfWeek, current);
  }

  for (const ranges of grouped.values()) {
    ranges.sort((a, b) => a.start - b.start);

    for (let index = 1; index < ranges.length; index += 1) {
      const previous = ranges[index - 1];
      const current = ranges[index];

      if (previous.end > current.start) {
        return true;
      }
    }
  }

  return false;
}

function refineAvailability(
  availability: Array<{
    dayOfWeek: DayOfWeek;
    startTime: string;
    endTime: string;
  }>,
  context: z.RefinementCtx
) {
  for (const range of availability) {
    if (toMinutes(range.startTime) >= toMinutes(range.endTime)) {
      context.addIssue({
        code: z.ZodIssueCode.custom,
        message: `El rango ${range.startTime}-${range.endTime} es invalido. La hora de inicio debe ser menor a la de fin.`,
      });
    }
  }

  if (hasOverlaps(availability)) {
    context.addIssue({
      code: z.ZodIssueCode.custom,
      message: 'Hay rangos horarios superpuestos para el mismo dia.',
    });
  }
}

export const dayOfWeekSchema = z.enum([
  'MONDAY',
  'TUESDAY',
  'WEDNESDAY',
  'THURSDAY',
  'FRIDAY',
  'SATURDAY',
  'SUNDAY',
]);

export type DayOfWeek = z.infer<typeof dayOfWeekSchema>;

export const courtAvailabilitySchema = z.object({
  id: z.uuid(),
  dayOfWeek: dayOfWeekSchema,
  startTime: z.string().regex(TIME_REGEX, 'La hora debe tener formato HH:mm.'),
  endTime: z.string().regex(TIME_REGEX, 'La hora debe tener formato HH:mm.'),
});

export const courtAvailabilityInputSchema = z.object({
  dayOfWeek: dayOfWeekSchema,
  startTime: z.string().regex(TIME_REGEX, 'La hora debe tener formato HH:mm.'),
  endTime: z.string().regex(TIME_REGEX, 'La hora debe tener formato HH:mm.'),
});

export const courtPriceRuleSchema = z.object({
  id: z.uuid(),
  dayOfWeek: dayOfWeekSchema.nullable(),
  startTime: z.string().nullable(),
  endTime: z.string().nullable(),
  price: z.number().nonnegative(),
  isActive: z.boolean(),
});

export const courtSportSchema = z.object({
  id: z.uuid(),
  name: z.string(),
  slug: z.string(),
});

export const courtSchema = z.object({
  id: z.uuid(),
  complexId: z.uuid(),
  name: z.string(),
  sportId: z.uuid(),
  sport: courtSportSchema,
  slotDurationMinutes: z.int().positive(),
  basePrice: z.number().nonnegative(),
  availability: z.array(courtAvailabilitySchema),
  priceRules: z.array(courtPriceRuleSchema),
  hasCustomPricing: z.boolean(),
  createdAt: z.string().datetime(),
  updatedAt: z.string().datetime(),
});

export const createCourtSchema = z
  .object({
    name: z
      .string()
      .trim()
      .min(2, 'La cancha debe tener al menos 2 caracteres.')
      .max(120, 'La cancha no puede superar los 120 caracteres.'),
    sportId: z.uuid('El sportId debe ser un UUID valido.'),
    slotDurationMinutes: z
      .int()
      .min(15, 'La duracion minima por turno es 15 minutos.')
      .max(480, 'La duracion maxima por turno es 480 minutos.'),
    basePrice: z.number().nonnegative('El precio base no puede ser negativo.'),
    availability: z
      .array(courtAvailabilityInputSchema)
      .min(1, 'Debes definir al menos un rango horario.'),
  })
  .superRefine((payload, context) => refineAvailability(payload.availability, context));

export const updateCourtSchema = z
  .object({
    name: z
      .string()
      .trim()
      .min(2, 'La cancha debe tener al menos 2 caracteres.')
      .max(120, 'La cancha no puede superar los 120 caracteres.')
      .optional(),
    sportId: z.uuid('El sportId debe ser un UUID valido.').optional(),
    slotDurationMinutes: z
      .int()
      .min(15, 'La duracion minima por turno es 15 minutos.')
      .max(480, 'La duracion maxima por turno es 480 minutos.')
      .optional(),
    basePrice: z.number().nonnegative('El precio base no puede ser negativo.').optional(),
    availability: z
      .array(courtAvailabilityInputSchema)
      .min(1, 'Debes definir al menos un rango horario.')
      .optional(),
  })
  .superRefine((payload, context) => {
    if (
      payload.name === undefined &&
      payload.sportId === undefined &&
      payload.slotDurationMinutes === undefined &&
      payload.basePrice === undefined &&
      payload.availability === undefined
    ) {
      context.addIssue({
        code: z.ZodIssueCode.custom,
        message: 'Debes enviar al menos un campo para actualizar.',
      });
    }

    if (payload.availability) {
      refineAvailability(payload.availability, context);
    }
  });

export type Court = z.infer<typeof courtSchema>;
export type CourtAvailability = z.infer<typeof courtAvailabilitySchema>;
export type CourtAvailabilityInput = z.infer<typeof courtAvailabilityInputSchema>;
export type CourtPriceRule = z.infer<typeof courtPriceRuleSchema>;
export type CreateCourtInput = z.infer<typeof createCourtSchema>;
export type UpdateCourtInput = z.infer<typeof updateCourtSchema>;
