import { defineStore } from 'pinia';
import type { Reservation } from './schedule.types';
import { ComputedRef, computed, reactive } from 'vue';
import { AppwriteIds, databases } from 'src/boot/appwrite';
import { ID, Query } from 'appwrite';
import { date, useQuasar } from 'quasar';
import { Timestamp, parseDate, today } from '@quasar/quasar-ui-qcalendar';
import { LoadingTypes } from 'src/utils/misc';
import { useAuthStore } from './auth';
import { isPast } from 'src/utils/schedule';
import { useRealtimeStore } from './realtime';

export const useReservationStore = defineStore('reservation', () => {
  const reservations = reactive<Map<string, Reservation>>(new Map());
  const datesLoaded = reactive<Record<string, LoadingTypes>>({});
  const userReservations = reactive<Map<string, Reservation>>(new Map());

  const authStore = useAuthStore();
  const $q = useQuasar();
  const realtimeStore = useRealtimeStore();

  realtimeStore.register(
    `databases.${AppwriteIds.databaseId}.collections.${AppwriteIds.collection.reservation}.documents`,
    (response) => {
      const payload = response.payload as Reservation;
      if (payload.$id) {
        if (
          response.events.includes(
            'databases.*.collections.*.documents.*.delete'
          )
        ) {
          reservations.delete(payload.$id);
          userReservations.delete(payload.$id);
        } else {
          reservations.set(payload.$id, payload);
          if (payload.user === authStore.currentUser?.$id)
            userReservations.set(payload.$id, payload);
        }
      }
    }
  );
  // Fetch reservations for a specific date range
  const fetchReservationsForDateRange = async (
    start: string = today(),
    end: string = start
  ) => {
    const startDate = new Date(start < end ? start : end + 'T00:00');
    const endDate = new Date(start < end ? end : start + 'T23:59');

    if (getUnloadedDates(startDate, endDate).length === 0) return;

    setDateLoaded(startDate, endDate, 'pending');

    try {
      const response = await databases.listDocuments(
        AppwriteIds.databaseId,
        AppwriteIds.collection.reservation,
        [
          Query.greaterThanEqual('end', startDate.toISOString()),
          Query.lessThanEqual('start', endDate.toISOString()),
        ]
      );

      response.documents.forEach((d) =>
        reservations.set(d.$id, d as Reservation)
      );
      setDateLoaded(startDate, endDate, 'loaded');
    } catch (error) {
      console.error('Failed to fetch reservations', error);
      setDateLoaded(startDate, endDate, 'error');
    }
  };
  const getReservationById = async (id: string) => {
    try {
      const response = await databases.getDocument(
        AppwriteIds.databaseId,
        AppwriteIds.collection.reservation,
        id
      );
      return response as Reservation;
    } catch (error) {
      console.error('Failed to fetch reservation: ', error);
    }
  };

  const createOrUpdateReservation = async (
    reservation: Reservation
  ): Promise<Reservation> => {
    let response;
    try {
      if (reservation.$id) {
        response = await databases.updateDocument(
          AppwriteIds.databaseId,
          AppwriteIds.collection.reservation,
          reservation.$id,
          reservation
        );
      } else {
        response = await databases.createDocument(
          AppwriteIds.databaseId,
          AppwriteIds.collection.reservation,
          ID.unique(),
          reservation
        );
      }
      reservations.set(response.$id, response as Reservation);
      userReservations.set(response.$id, response as Reservation);
      console.info('Reservation booked: ', response);
      return response as Reservation;
    } catch (e) {
      console.error('Error creating Reservation: ' + e);
      throw e;
    }
  };

  const deleteReservation = async (
    reservation: string | Reservation | null | undefined
  ) => {
    if (!reservation) return false;
    const id = typeof reservation === 'string' ? reservation : reservation.$id;
    if (!id) return false;

    const status = $q.notify({
      color: 'secondary',
      textColor: 'white',
      message: 'Deleting Reservation',
      spinner: true,
      closeBtn: 'Dismiss',
      position: 'top',
      timeout: 0,
      group: false,
    });
    try {
      await databases.deleteDocument(
        AppwriteIds.databaseId,
        AppwriteIds.collection.reservation,
        id
      );
      reservations.delete(id);
      userReservations.delete(id);
      console.info(`Deleted reservation: ${id}`);
      status({
        color: 'warning',
        message: 'Reservation Deleted',
        spinner: false,
        icon: 'delete',
        timeout: 4000,
      });
    } catch (e) {
      console.error('Error deleting reservation: ' + e);
      status({
        color: 'negative',
        message: 'Failed to Delete Reservation',
        spinner: false,
        icon: 'error',
      });
    }
  };

  // Set the loading state for dates
  const setDateLoaded = (start: Date, end: Date, state: LoadingTypes) => {
    if (start > end) return [];
    let curDate = start;
    while (curDate < end) {
      datesLoaded[(parseDate(curDate) as Timestamp).date] = state;
      curDate = date.addToDate(curDate, { days: 1 });
    }
  };

  const getUnloadedDates = (start: Date, end: Date): string[] => {
    if (start > end) return [];
    let curDate = start;
    const unloaded = [];
    while (curDate < end) {
      const parsedDate = (parseDate(curDate) as Timestamp).date;
      if (datesLoaded[parsedDate] === undefined) unloaded.push(parsedDate);
      curDate = date.addToDate(curDate, { days: 1 });
    }
    return unloaded;
  };

  // Get reservations by date and optionally filter by boat
  const getReservationsByDate = (
    searchDate: string,
    boat?: string
  ): ComputedRef<Reservation[]> => {
    if (!datesLoaded[searchDate]) {
      fetchReservationsForDateRange(searchDate);
    }
    const dayStart = new Date(searchDate + 'T00:00');
    const dayEnd = new Date(searchDate + 'T23:59');

    return computed(() => {
      return Array.from(reservations.values()).filter((reservation) => {
        const reservationStart = new Date(reservation.start);
        const reservationEnd = new Date(reservation.end);

        const isWithinDay =
          reservationStart < dayEnd && reservationEnd > dayStart;
        const matchesBoat = boat ? boat === reservation.resource : true;
        return isWithinDay && matchesBoat;
      });
    });
  };

  // Get conflicting reservations for a resource within a time range
  const getConflictingReservations = (
    resource: string,
    start: Date,
    end: Date
  ): Reservation[] => {
    return Array.from(reservations.values()).filter(
      (entry) =>
        entry.resource === resource &&
        new Date(entry.start) < end &&
        new Date(entry.end) > start
    );
  };

  // Check if a resource has time overlap
  const isResourceTimeOverlapped = (
    resource: string,
    start: Date,
    end: Date
  ): boolean => {
    return getConflictingReservations(resource, start, end).length > 0;
  };

  // Check if a reservation overlaps with existing reservations
  const isReservationOverlapped = (res: Reservation): boolean => {
    return isResourceTimeOverlapped(
      res.resource,
      new Date(res.start),
      new Date(res.end)
    );
  };

  const fetchUserReservations = async () => {
    if (!authStore.currentUser) return;
    try {
      const response = await databases.listDocuments(
        AppwriteIds.databaseId,
        AppwriteIds.collection.reservation,
        [Query.equal('user', authStore.currentUser.$id)]
      );
      response.documents.forEach((d) =>
        userReservations.set(d.$id, d as Reservation)
      );
    } catch (error) {
      console.error('Failed to fetch reservations for user: ', error);
    }
  };

  const sortedUserReservations = computed((): Reservation[] =>
    [...userReservations.values()].sort(
      (a, b) => new Date(b.start).getTime() - new Date(a.start).getTime()
    )
  );

  const futureUserReservations = computed((): Reservation[] => {
    if (!sortedUserReservations.value) return [];
    return sortedUserReservations.value.filter((b) => !isPast(b.end));
  });

  const pastUserReservations = computed((): Reservation[] => {
    if (!sortedUserReservations.value) return [];
    return sortedUserReservations.value?.filter((b) => isPast(b.end));
  });

  return {
    getReservationsByDate,
    getReservationById,
    createOrUpdateReservation,
    deleteReservation,
    fetchReservationsForDateRange,
    isReservationOverlapped,
    isResourceTimeOverlapped,
    getConflictingReservations,
    fetchUserReservations,
    sortedUserReservations,
    futureUserReservations,
    pastUserReservations,
    userReservations,
  };
});
