import { ApiClientError, apiClient } from '@/lib/api-client';
import type { AdminBooking, ComplexWithRole, Court } from '@repo/api-contract';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import {
  type ReactNode,
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
} from 'react';
import type {
  BookingCourtSchedule,
  BookingSlotDraft,
  BookingStatusFilter,
  BookingSummary,
  BookingTimeRange,
  BookingTimelineSegment,
  BookingViewMode,
} from './booking.types';
import {
  addDaysIso,
  formatBookingDate,
  getDayOfWeek,
  getNowTime,
  isTodayIso,
  minutesToTime,
  timeToMinutes,
  toIsoDateLocal,
} from './lib/booking-time';

interface BookingProviderProps {
  children: ReactNode;
  complex: ComplexWithRole;
}

interface CreateManualBookingPayload {
  courtId: string;
  date: string;
  startTime: string;
  customerName: string;
  customerPhone: string;
  customerEmail: string;
}

interface BookingContextValue {
  complex: ComplexWithRole;
  courts: Court[];
  bookings: AdminBooking[];
  schedules: BookingCourtSchedule[];
  selectedDate: string;
  selectedSportId: string;
  selectedStatus: BookingStatusFilter;
  viewMode: BookingViewMode;
  visibleTimeRange: BookingTimeRange;
  timelineHours: string[];
  summary: BookingSummary;
  currentTime: string | null;
  selectedSlot: BookingSlotDraft | null;
  selectedSegment: BookingTimelineSegment | null;
  isLoading: boolean;
  isError: boolean;
  errorMessage: string | null;
  isCreateBookingOpen: boolean;
  createBookingError: string | null;
  isCreatingBooking: boolean;
  bookingToolsOpen: boolean;
  setSelectedDate: (date: string) => void;
  moveSelectedDate: (amount: number) => void;
  setSelectedSportId: (sportId: string) => void;
  setSelectedStatus: (status: BookingStatusFilter) => void;
  setViewMode: (mode: BookingViewMode) => void;
  setVisibleTimeRange: (range: BookingTimeRange) => void;
  openCreateBooking: (draft?: Partial<BookingSlotDraft>) => void;
  closeCreateBooking: () => void;
  createBooking: (payload: CreateManualBookingPayload) => Promise<void>;
  updateBookingStatus: (bookingId: string, status: 'CANCELLED' | 'COMPLETED' | 'NOSHOW') => void;
  exportDayReport: () => void;
  openBookingTools: (segment?: BookingTimelineSegment) => void;
  closeBookingTools: () => void;
}

const BookingContext = createContext<BookingContextValue | null>(null);

const DEFAULT_TIME_RANGE: BookingTimeRange = {
  start: '08:00',
  end: '22:00',
};

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

  if (error instanceof Error) {
    return error.message || fallback;
  }

  return fallback;
}

function makeTimelineHours(range: BookingTimeRange) {
  const start = timeToMinutes(range.start);
  const end = timeToMinutes(range.end);
  const hours: string[] = [];

  for (let minute = start; minute <= end; minute += 60) {
    hours.push(minutesToTime(minute));
  }

  return hours;
}

function isActiveBooking(booking: AdminBooking) {
  return (
    booking.status === 'CONFIRMED' || booking.status === 'COMPLETED' || booking.status === 'NOSHOW'
  );
}

function getCourtBookings(court: Court, bookings: AdminBooking[], selectedDate: string) {
  return bookings
    .filter(
      (booking) =>
        booking.courtId === court.id && booking.date === selectedDate && isActiveBooking(booking)
    )
    .sort((a, b) => timeToMinutes(a.startTime) - timeToMinutes(b.startTime));
}

function overlapsBooking(start: number, end: number, booking: AdminBooking) {
  const bookingStart = timeToMinutes(booking.startTime);
  const bookingEnd = timeToMinutes(booking.endTime);

  return start < bookingEnd && end > bookingStart;
}

function buildSegmentsForCourt(
  court: Court,
  bookings: AdminBooking[],
  selectedDate: string,
  visibleRange: BookingTimeRange
) {
  const dayOfWeek = getDayOfWeek(selectedDate);
  const rangeStart = timeToMinutes(visibleRange.start);
  const rangeEnd = timeToMinutes(visibleRange.end);
  const courtBookings = getCourtBookings(court, bookings, selectedDate);
  const segments: BookingTimelineSegment[] = [];
  const slotDuration = court.slotDurationMinutes;

  const nowMinutes = timeToMinutes(getNowTime());
  const isToday = isTodayIso(selectedDate);

  const availabilityRanges = court.availability
    .filter((range) => range.dayOfWeek === dayOfWeek)
    .map((range) => ({
      start: Math.max(timeToMinutes(range.startTime), rangeStart),
      end: Math.min(timeToMinutes(range.endTime), rangeEnd),
    }))
    .filter((range) => range.start < range.end)
    .sort((a, b) => a.start - b.start);

  for (const booking of courtBookings) {
    const bookingStart = timeToMinutes(booking.startTime);
    const bookingEnd = timeToMinutes(booking.endTime);

    if (bookingEnd > rangeStart && bookingStart < rangeEnd) {
      segments.push({
        id: booking.id,
        courtId: court.id,
        status: 'reserved',
        startTime: booking.startTime,
        endTime: booking.endTime,
        startMinutes: Math.max(bookingStart, rangeStart),
        endMinutes: Math.min(bookingEnd, rangeEnd),
        booking,
      });
    }
  }

  for (const availability of availabilityRanges) {
    for (
      let slotStart = availability.start;
      slotStart + slotDuration <= availability.end;
      slotStart += slotDuration
    ) {
      const slotEnd = slotStart + slotDuration;

      if (isToday && slotEnd <= nowMinutes) continue;

      const isBooked = courtBookings.some((booking) =>
        overlapsBooking(slotStart, slotEnd, booking)
      );

      if (isBooked) continue;

      const slotStartTime = minutesToTime(slotStart);
      const slotEndTime = minutesToTime(slotEnd);

      segments.push({
        id: `${court.id}-free-${slotStartTime}-${slotEndTime}`,
        courtId: court.id,
        status: 'free',
        startTime: slotStartTime,
        endTime: slotEndTime,
        startMinutes: slotStart,
        endMinutes: slotEnd,
      });
    }
  }

  segments.sort((a, b) => a.startMinutes - b.startMinutes);

  return segments;
}

function getSummary(schedules: BookingCourtSchedule[]): BookingSummary {
  const freeSlots = schedules.reduce((total, schedule) => total + schedule.metrics.free, 0);
  const reservedSlots = schedules.reduce((total, schedule) => total + schedule.metrics.reserved, 0);
  const totalSegments = freeSlots + reservedSlots || 1;

  return {
    totalReservations: freeSlots + reservedSlots,
    freeSlots,
    reservedSlots,
    freePercent: Math.round((freeSlots / totalSegments) * 100),
    reservedPercent: Math.round((reservedSlots / totalSegments) * 100),
  };
}

export function BookingProvider({ children, complex }: BookingProviderProps) {
  const queryClient = useQueryClient();
  const todayIso = useMemo(() => toIsoDateLocal(new Date()), []);
  const [selectedDate, setSelectedDate] = useState(todayIso);
  const [selectedSportId, setSelectedSportId] = useState('all');
  const [selectedStatus, setSelectedStatus] = useState<BookingStatusFilter>('all');
  const [viewMode, setViewMode] = useState<BookingViewMode>('panel');
  const [visibleTimeRange, setVisibleTimeRange] = useState(DEFAULT_TIME_RANGE);
  const [selectedSlot, setSelectedSlot] = useState<BookingSlotDraft | null>(null);
  const [isCreateBookingOpen, setIsCreateBookingOpen] = useState(false);
  const [selectedSegment, setSelectedSegment] = useState<BookingTimelineSegment | null>(null);
  const [bookingToolsOpen, setBookingToolsOpen] = useState(false);

  const courtsQuery = useQuery({
    queryKey: ['courts', complex.id],
    queryFn: () => apiClient.courts.listByComplex(complex.id),
  });

  const bookingsQuery = useQuery({
    queryKey: ['admin-bookings', complex.id, selectedDate],
    queryFn: () =>
      apiClient.adminBookings.listByComplex(complex.id, {
        fromDate: selectedDate,
      }),
  });

  const bookings = useMemo(
    () => (bookingsQuery.data?.bookings ?? []).filter((booking) => booking.date === selectedDate),
    [bookingsQuery.data?.bookings, selectedDate]
  );

  const courts = useMemo(
    () => courtsQuery.data?.sort((a, b) => a.name.localeCompare(b.name)) ?? [],
    [courtsQuery.data]
  );

  const filteredCourts = useMemo(() => {
    if (selectedSportId === 'all') return courts;
    return courts.filter((court) => court.sportId === selectedSportId);
  }, [courts, selectedSportId]);

  const schedules = useMemo<BookingCourtSchedule[]>(() => {
    return filteredCourts
      .map((court) => {
        const segments = buildSegmentsForCourt(court, bookings, selectedDate, visibleTimeRange);
        const metrics = {
          free: segments.filter((segment) => segment.status === 'free').length,
          reserved: segments.filter((segment) => segment.status === 'reserved').length,
        };

        return { court, segments, metrics };
      })
      .filter((schedule) => {
        if (selectedStatus === 'all') return true;
        return schedule.segments.some((segment) => segment.status === selectedStatus);
      });
  }, [bookings, filteredCourts, selectedDate, selectedStatus, visibleTimeRange]);

  const summary = useMemo(() => getSummary(schedules), [schedules]);
  const timelineHours = useMemo(() => makeTimelineHours(visibleTimeRange), [visibleTimeRange]);

  const [now, setNow] = useState(() => getNowTime());
  useEffect(() => {
    const interval = setInterval(() => {
      setNow(getNowTime());
    }, 60 * 1000);

    return () => clearInterval(interval);
  }, []);

  const currentTime = useMemo(() => {
    if (!isTodayIso(selectedDate)) return null;
    const nowMinutes = timeToMinutes(now);
    const start = timeToMinutes(visibleTimeRange.start);
    const end = timeToMinutes(visibleTimeRange.end);

    if (nowMinutes < start || nowMinutes > end) return null;
    return now;
  }, [selectedDate, visibleTimeRange, now]);

  const createBookingMutation = useMutation({
    mutationFn: (payload: CreateManualBookingPayload) =>
      apiClient.adminBookings.create(complex.id, {
        date: payload.date,
        courtId: payload.courtId,
        startTime: payload.startTime,
        customerName: payload.customerName,
        customerPhone: payload.customerPhone,
        customerEmail: payload.customerEmail,
      }),
    onSuccess: async () => {
      await queryClient.invalidateQueries({ queryKey: ['admin-bookings', complex.id] });
      await queryClient.invalidateQueries({ queryKey: ['manual-booking-availability'] });
      setIsCreateBookingOpen(false);
      setSelectedSlot(null);
    },
  });

  const updateStatusMutation = useMutation({
    mutationFn: (payload: { bookingId: string; status: 'CANCELLED' | 'COMPLETED' | 'NOSHOW' }) =>
      apiClient.adminBookings.updateStatus(payload.bookingId, { status: payload.status }),
    onSuccess: async () => {
      await queryClient.invalidateQueries({ queryKey: ['admin-bookings', complex.id] });
    },
  });

  const openCreateBooking = useCallback(
    (draft?: Partial<BookingSlotDraft>) => {
      setSelectedSlot({
        date: draft?.date ?? selectedDate,
        courtId: draft?.courtId,
        startTime: draft?.startTime,
        sportId: draft?.sportId,
      });
      setIsCreateBookingOpen(true);
    },
    [selectedDate]
  );

  const openBookingTools = useCallback(
    (segment?: BookingTimelineSegment) => {
      setSelectedSegment(segment ?? null);
      setBookingToolsOpen(true);
      console.log(JSON.stringify(segment, null, 2));
    },
    [selectedDate]
  );

  const closeBookingTools = () => setBookingToolsOpen(false);

  useEffect(() => {
    const handleOpenCreateBooking = () => {
      openCreateBooking();
    };

    window.addEventListener('playzer:open-booking-create', handleOpenCreateBooking);

    return () => {
      window.removeEventListener('playzer:open-booking-create', handleOpenCreateBooking);
    };
  }, [openCreateBooking]);

  const closeCreateBooking = useCallback(() => {
    setIsCreateBookingOpen(false);
    setSelectedSlot(null);
    createBookingMutation.reset();
  }, [createBookingMutation]);

  const createBooking = useCallback(
    async (payload: CreateManualBookingPayload) => {
      await createBookingMutation.mutateAsync(payload);
    },
    [createBookingMutation]
  );

  const updateBookingStatus = useCallback(
    (bookingId: string, status: 'CANCELLED' | 'COMPLETED' | 'NOSHOW') => {
      updateStatusMutation.mutate({ bookingId, status });
    },
    [updateStatusMutation]
  );

  const exportDayReport = useCallback(() => {
    const rows = [
      ['Fecha', 'Cancha', 'Deporte', 'Inicio', 'Fin', 'Cliente', 'Telefono', 'Estado'],
      ...bookings.map((booking) => [
        booking.date,
        booking.courtName,
        booking.sport.name,
        booking.startTime,
        booking.endTime,
        booking.customerName,
        booking.customerPhone,
        booking.status,
      ]),
    ];
    const csv = rows.map((row) => row.map((cell) => `"${cell}"`).join(',')).join('\n');
    const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = `reservas-${complex.complexSlug}-${selectedDate}.csv`;
    link.click();
    URL.revokeObjectURL(url);
  }, [bookings, complex.complexSlug, selectedDate]);

  const value = useMemo<BookingContextValue>(
    () => ({
      complex,
      courts,
      bookings,
      schedules,
      selectedDate,
      selectedSportId,
      selectedStatus,
      viewMode,
      visibleTimeRange,
      timelineHours,
      summary,
      currentTime,
      selectedSlot,
      isLoading: courtsQuery.isLoading || bookingsQuery.isLoading,
      isError: courtsQuery.isError || bookingsQuery.isError,
      errorMessage:
        courtsQuery.isError || bookingsQuery.isError
          ? extractMessage(
              courtsQuery.error ?? bookingsQuery.error,
              'No pudimos cargar el panel de reservas.'
            )
          : null,
      isCreateBookingOpen,
      createBookingError: createBookingMutation.isError
        ? extractMessage(createBookingMutation.error, 'No pudimos crear la reserva.')
        : null,
      isCreatingBooking: createBookingMutation.isPending,
      setSelectedDate,
      moveSelectedDate: (amount) => setSelectedDate((date) => addDaysIso(date, amount)),
      setSelectedSportId,
      setSelectedStatus,
      setViewMode,
      setVisibleTimeRange,
      openCreateBooking,
      closeCreateBooking,
      createBooking,
      updateBookingStatus,
      exportDayReport,
      bookingToolsOpen,
      openBookingTools,
      closeBookingTools,
      selectedSegment,
    }),
    [
      bookings,
      bookingsQuery.error,
      bookingsQuery.isError,
      bookingsQuery.isLoading,
      closeCreateBooking,
      complex,
      courts,
      courtsQuery.error,
      courtsQuery.isError,
      courtsQuery.isLoading,
      createBooking,
      createBookingMutation.error,
      createBookingMutation.isError,
      createBookingMutation.isPending,
      currentTime,
      exportDayReport,
      isCreateBookingOpen,
      openCreateBooking,
      schedules,
      selectedDate,
      selectedSlot,
      selectedSportId,
      selectedStatus,
      summary,
      timelineHours,
      updateBookingStatus,
      viewMode,
      visibleTimeRange,
      bookingToolsOpen,
    ]
  );

  return <BookingContext.Provider value={value}>{children}</BookingContext.Provider>;
}

export function useBooking() {
  const context = useContext(BookingContext);

  if (!context) {
    throw new Error('useBooking must be used within BookingProvider');
  }

  return context;
}

export { formatBookingDate };
