import { Button } from '@/components/ui/button';
import { Field, FieldError, FieldLabel } from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import { ApiClientError, apiClient } from '@/lib/api-client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useNavigate } from '@tanstack/react-router';
import { useEffect, useMemo, useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';

const DAYS_STEP = 5;

const bookingFormSchema = z.object({
  customerName: z
    .string()
    .trim()
    .min(2, 'Ingresa tu nombre.')
    .max(120, 'El nombre no puede superar los 120 caracteres.'),
  customerPhone: z
    .string()
    .trim()
    .min(6, 'Ingresa un telefono valido.')
    .max(30, 'El telefono no puede superar los 30 caracteres.'),
});

type BookingFormValues = z.infer<typeof bookingFormSchema>;

type PublicBookingPageProps = {
  complexSlug: string;
};

type SelectedSlot = {
  courtId: string;
  courtName: string;
  sportId: string;
  sportName: string;
  startTime: string;
  endTime: string;
};

function addDays(baseDate: Date, amount: number) {
  const next = new Date(baseDate);
  next.setDate(next.getDate() + amount);
  return next;
}

function toIsoDateLocal(date: Date) {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}

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

function formatDayLabel(date: Date) {
  const weekday = new Intl.DateTimeFormat('es-AR', { weekday: 'short' }).format(date);
  const dayMonth = new Intl.DateTimeFormat('es-AR', {
    day: '2-digit',
    month: '2-digit',
  }).format(date);

  return {
    weekday: weekday.replace('.', ''),
    dayMonth,
  };
}

function extractMessage(error: unknown, fallback: string) {
  if (error instanceof ApiClientError) {
    return error.message || fallback;
  }

  return fallback;
}

export function PublicBookingPage({ complexSlug }: PublicBookingPageProps) {
  const navigate = useNavigate();
  const today = useMemo(() => new Date(), []);
  const todayIsoDate = useMemo(() => toIsoDateLocal(today), [today]);
  const [windowStartOffset, setWindowStartOffset] = useState(0);
  const [selectedSportId, setSelectedSportId] = useState<string | undefined>();
  const [selectedSlot, setSelectedSlot] = useState<SelectedSlot | null>(null);
  const [navigationError, setNavigationError] = useState<string | null>(null);
  const [hasAutoAdjustedInitialDate, setHasAutoAdjustedInitialDate] = useState(false);
  const [autoAdjustedDateNotice, setAutoAdjustedDateNotice] = useState<string | null>(null);

  const dayOptions = useMemo(() => {
    return Array.from({ length: DAYS_STEP }).map((_, index) => {
      const current = addDays(today, windowStartOffset + index);
      return {
        value: toIsoDateLocal(current),
        date: current,
        ...formatDayLabel(current),
      };
    });
  }, [today, windowStartOffset]);

  const [selectedDate, setSelectedDate] = useState(dayOptions[0]?.value ?? '');

  useEffect(() => {
    if (dayOptions.some((option) => option.value === selectedDate)) {
      return;
    }

    setSelectedDate(dayOptions[0]?.value ?? '');
  }, [dayOptions, selectedDate]);

  const availabilityQuery = useQuery({
    queryKey: ['public-booking-availability', complexSlug, selectedDate, selectedSportId],
    enabled: Boolean(selectedDate),
    queryFn: () =>
      apiClient.publicBookings.getAvailability(complexSlug, {
        date: selectedDate,
        ...(selectedSportId ? { sportId: selectedSportId } : {}),
      }),
  });

  useEffect(() => {
    const availability = availabilityQuery.data;
    if (!availability) return;

    if (!availability.sportSelectionRequired) {
      if (!selectedSportId && availability.sports[0]) {
        setSelectedSportId(availability.sports[0].id);
      }
      return;
    }

    if (selectedSportId && !availability.sports.some((sport) => sport.id === selectedSportId)) {
      setSelectedSportId(undefined);
    }
  }, [availabilityQuery.data, selectedSportId]);

  const {
    register,
    handleSubmit,
    reset,
    formState: { errors, isSubmitting, isValid },
  } = useForm<BookingFormValues>({
    resolver: zodResolver(bookingFormSchema),
    mode: 'onChange',
    defaultValues: {
      customerName: '',
      customerPhone: '',
    },
  });

  const createBookingMutation = useMutation({
    mutationFn: (payload: BookingFormValues) => {
      if (!selectedSlot) {
        throw new Error('Debes seleccionar un horario.');
      }

      return apiClient.publicBookings.create(complexSlug, {
        date: selectedDate,
        sportId: selectedSportId,
        courtId: selectedSlot.courtId,
        startTime: selectedSlot.startTime,
        customerName: payload.customerName,
        customerPhone: payload.customerPhone,
      });
    },
  });

  const onSubmit = async (values: BookingFormValues) => {
    setNavigationError(null);
    const booking = await createBookingMutation.mutateAsync(values);

    if (!booking.bookingCode) {
      setNavigationError('No se pudo obtener el codigo de confirmacion de la reserva.');
      return;
    }

    reset();
    setSelectedSlot(null);
    await availabilityQuery.refetch();
    await navigate({
      to: '/$complexSlug/booking/confirmed/$bookingCode',
      params: {
        complexSlug,
        bookingCode: booking.bookingCode,
      },
    });
  };

  const visibleCourts = useMemo(() => {
    const courts = availabilityQuery.data?.courts ?? [];

    if (selectedDate !== todayIsoDate) {
      return courts;
    }

    const now = new Date();
    const nowMinutes = now.getHours() * 60 + now.getMinutes();

    return courts
      .map((court) => ({
        ...court,
        availableSlots: court.availableSlots.filter(
          (slot) => toMinutes(slot.startTime) >= nowMinutes
        ),
      }))
      .filter((court) => court.availableSlots.length > 0);
  }, [availabilityQuery.data?.courts, selectedDate, todayIsoDate]);

  useEffect(() => {
    if (!selectedSlot) return;

    const stillAvailable = visibleCourts.some(
      (court) =>
        court.courtId === selectedSlot.courtId &&
        court.availableSlots.some((slot) => slot.startTime === selectedSlot.startTime)
    );

    if (!stillAvailable) {
      setSelectedSlot(null);
    }
  }, [selectedSlot, visibleCourts]);

  useEffect(() => {
    if (hasAutoAdjustedInitialDate) return;
    if (selectedDate !== todayIsoDate) {
      setHasAutoAdjustedInitialDate(true);
      return;
    }
    if (availabilityQuery.isLoading || availabilityQuery.isError || !availabilityQuery.data) {
      return;
    }

    if (availabilityQuery.data.sportSelectionRequired && !selectedSportId) {
      return;
    }

    if (visibleCourts.length > 0) {
      setHasAutoAdjustedInitialDate(true);
      return;
    }

    let cancelled = false;

    const findNextAvailableDate = async () => {
      for (let dayOffset = 1; dayOffset <= 30; dayOffset += 1) {
        const candidateDate = toIsoDateLocal(addDays(today, dayOffset));

        try {
          const availability = await apiClient.publicBookings.getAvailability(complexSlug, {
            date: candidateDate,
            ...(selectedSportId ? { sportId: selectedSportId } : {}),
          });

          if (availability.courts.length === 0) {
            continue;
          }

          if (cancelled) return;

          setWindowStartOffset(dayOffset);
          setSelectedDate(candidateDate);
          setSelectedSlot(null);
          setAutoAdjustedDateNotice(
            'No habia turnos disponibles para hoy. Te mostramos el proximo dia con turnos.'
          );
          setHasAutoAdjustedInitialDate(true);
          return;
        } catch {
          break;
        }
      }

      if (!cancelled) {
        setHasAutoAdjustedInitialDate(true);
      }
    };

    void findNextAvailableDate();

    return () => {
      cancelled = true;
    };
  }, [
    availabilityQuery.data,
    availabilityQuery.isError,
    availabilityQuery.isLoading,
    complexSlug,
    hasAutoAdjustedInitialDate,
    selectedDate,
    selectedSportId,
    today,
    todayIsoDate,
    visibleCourts.length,
  ]);

  const canGoBack = windowStartOffset > 0;

  const goToPreviousFiveDays = () => {
    if (!canGoBack) return;

    setWindowStartOffset((previous) => {
      const next = Math.max(0, previous - DAYS_STEP);
      const nextStartDate = toIsoDateLocal(addDays(today, next));
      setSelectedDate(nextStartDate);
      setSelectedSlot(null);
      setAutoAdjustedDateNotice(null);
      return next;
    });
  };

  const goToNextFiveDays = () => {
    setWindowStartOffset((previous) => {
      const next = previous + DAYS_STEP;
      const nextStartDate = toIsoDateLocal(addDays(today, next));
      setSelectedDate(nextStartDate);
      setSelectedSlot(null);
      setAutoAdjustedDateNotice(null);
      return next;
    });
  };

  return (
    <main className="min-h-screen bg-linear-to-b from-background to-muted/40">
      <div className="mx-auto flex w-full max-w-5xl flex-col gap-4 px-4 py-4 sm:gap-6 sm:px-6 sm:py-8">
        <section className="rounded-2xl border bg-card p-4 shadow-sm sm:p-6">
          <div className="flex flex-col gap-1">
            <h1 className="text-2xl font-bold text-primary sm:text-3xl">
              {availabilityQuery.data?.complexName}
            </h1>
            {availabilityQuery.data?.complexAddress && (
              <p className="text-sm text-muted-foreground">
                {availabilityQuery.data.complexAddress}
              </p>
            )}
          </div>
          <p className="mt-4 text-sm text-muted-foreground">
            Elige un dia, revisa horarios disponibles y confirma con tu nombre y telefono.
          </p>
        </section>

        <section className="rounded-2xl border bg-card p-4 shadow-sm sm:p-6">
          <div className="space-y-3">
            <div>
              <h2 className="text-sm font-medium">Dia</h2>
              <p className="text-xs text-muted-foreground">Mostramos 5 dias por vez.</p>
            </div>
            {autoAdjustedDateNotice && (
              <p className="rounded-lg border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-900">
                {autoAdjustedDateNotice}
              </p>
            )}

            <div className="grid grid-cols-[2.25rem_repeat(5,minmax(0,1fr))_2.25rem] gap-2">
              {canGoBack ? (
                <Button
                  type="button"
                  variant="outline"
                  size="icon"
                  className="h-full"
                  onClick={goToPreviousFiveDays}
                  aria-label="Mostrar 5 dias anteriores"
                >
                  {'<'}
                </Button>
              ) : (
                <div />
              )}

              {dayOptions.map((option) => (
                <button
                  key={option.value}
                  type="button"
                  onClick={() => {
                    setSelectedDate(option.value);
                    setSelectedSlot(null);
                    setAutoAdjustedDateNotice(null);
                  }}
                  className={`rounded-xl border px-1 py-2 text-center transition-colors sm:px-2 ${
                    selectedDate === option.value
                      ? 'border-primary bg-primary text-primary-foreground'
                      : 'border-border bg-background hover:bg-muted'
                  }`}
                >
                  <p className="text-[11px] font-medium uppercase sm:text-xs">{option.weekday}</p>
                  <p className="text-xs sm:text-sm">{option.dayMonth}</p>
                </button>
              ))}

              <Button
                type="button"
                variant="outline"
                size="icon"
                className="h-full"
                onClick={goToNextFiveDays}
                aria-label="Mostrar proximos 5 dias"
              >
                {'>'}
              </Button>
            </div>
          </div>
        </section>

        <section className="rounded-2xl border bg-card p-4 shadow-sm sm:p-6">
          <div className="mb-4 flex items-center justify-between gap-3">
            <div>
              <h2 className="text-sm font-medium">Disponibilidad</h2>
              <p className="text-xs text-muted-foreground">Selecciona cancha y horario.</p>
            </div>
          </div>

          {availabilityQuery.data?.sportSelectionRequired && (
            <div className="mb-4">
              <Field>
                <FieldLabel>Deporte</FieldLabel>
                <Select
                  value={selectedSportId ?? ''}
                  onValueChange={(value) => {
                    setSelectedSportId(value);
                    setSelectedSlot(null);
                    setAutoAdjustedDateNotice(null);
                  }}
                >
                  <SelectTrigger className="h-10 w-full">
                    <SelectValue placeholder="Selecciona un deporte" />
                  </SelectTrigger>
                  <SelectContent>
                    {availabilityQuery.data.sports.map((sport) => (
                      <SelectItem key={sport.id} value={sport.id}>
                        {sport.name}
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>
              </Field>
            </div>
          )}

          {availabilityQuery.isLoading && (
            <p className="text-sm text-muted-foreground">Buscando disponibilidad...</p>
          )}

          {availabilityQuery.isError && (
            <p className="text-sm text-destructive">
              {extractMessage(availabilityQuery.error, 'No pudimos cargar la disponibilidad.')}
            </p>
          )}

          {!availabilityQuery.isLoading &&
            !availabilityQuery.isError &&
            availabilityQuery.data?.sportSelectionRequired &&
            !selectedSportId && (
              <p className="text-sm text-muted-foreground">
                Selecciona un deporte para ver los horarios disponibles.
              </p>
            )}

          {!availabilityQuery.isLoading &&
            !availabilityQuery.isError &&
            availabilityQuery.data &&
            (!availabilityQuery.data.sportSelectionRequired || selectedSportId) &&
            visibleCourts.length === 0 && (
              <p className="text-sm text-muted-foreground">
                No hay horarios disponibles para la fecha elegida.
              </p>
            )}

          <div className="space-y-3">
            {visibleCourts.map((court) => (
              <article key={court.courtId} className="rounded-xl border bg-background p-3 sm:p-4">
                <div className="mb-3 flex flex-col gap-1">
                  <p className="text-sm font-medium sm:text-base">{court.courtName}</p>
                  <p className="text-xs text-muted-foreground">
                    {court.sport.name} · Turnos de {court.slotDurationMinutes} min
                  </p>
                </div>

                <div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
                  {court.availableSlots.map((slot) => {
                    const isSelected =
                      selectedSlot?.courtId === court.courtId &&
                      selectedSlot.startTime === slot.startTime;

                    return (
                      <button
                        key={`${court.courtId}-${slot.startTime}`}
                        type="button"
                        onClick={() => {
                          setSelectedSlot({
                            courtId: court.courtId,
                            courtName: court.courtName,
                            sportId: court.sport.id,
                            sportName: court.sport.name,
                            startTime: slot.startTime,
                            endTime: slot.endTime,
                          });
                        }}
                        className={`h-10 rounded-lg border text-sm transition-colors ${
                          isSelected
                            ? 'border-primary bg-primary text-primary-foreground'
                            : 'border-border bg-card hover:bg-muted'
                        }`}
                      >
                        {slot.startTime}
                      </button>
                    );
                  })}
                </div>
              </article>
            ))}
          </div>
        </section>

        {selectedSlot && (
          <section className="rounded-2xl border bg-card p-4 shadow-sm sm:p-6">
            <h2 className="text-sm font-medium">Confirmar turno</h2>
            <p className="mt-1 text-xs text-muted-foreground">
              {selectedSlot.courtName} · {selectedSlot.sportName} · {selectedSlot.startTime} -{' '}
              {selectedSlot.endTime}
            </p>

            <form className="mt-4 space-y-3" onSubmit={handleSubmit(onSubmit)}>
              <Field data-invalid={Boolean(errors.customerName)}>
                <FieldLabel htmlFor="customerName">Nombre</FieldLabel>
                <Input
                  id="customerName"
                  placeholder="Ej: Juan Perez"
                  aria-invalid={Boolean(errors.customerName)}
                  {...register('customerName')}
                />
                <FieldError errors={[errors.customerName]} />
              </Field>

              <Field data-invalid={Boolean(errors.customerPhone)}>
                <FieldLabel htmlFor="customerPhone">Telefono</FieldLabel>
                <Input
                  id="customerPhone"
                  type="tel"
                  placeholder="Ej: 3875551234"
                  aria-invalid={Boolean(errors.customerPhone)}
                  {...register('customerPhone')}
                />
                <FieldError errors={[errors.customerPhone]} />
              </Field>

              {createBookingMutation.isError && (
                <p className="text-sm text-destructive">
                  {extractMessage(createBookingMutation.error, 'No pudimos confirmar el turno.')}
                </p>
              )}
              {navigationError && <p className="text-sm text-destructive">{navigationError}</p>}

              <Button type="submit" className="w-full" disabled={!isValid || isSubmitting}>
                {createBookingMutation.isPending ? 'Confirmando...' : 'Confirmar turno'}
              </Button>
            </form>
          </section>
        )}
      </div>
    </main>
  );
}
