'use server'

import { prisma } from '@/lib/prisma'
import axios from 'axios'
import moment from 'moment-timezone'
import { v4 as uuidv4 } from 'uuid'
import { revalidatePath } from 'next/cache'

export const registerAvailability = async (
  availabilities,
  timeZone,

  expert_refresh_token,

  expertId,
  userId
) => {
  try {
    console.log(
      { availabilities },
      { userId },
      timeZone,
      expertId,
      expert_refresh_token
    )
    // await prisma.exception.deleteMany({
    //   where: {
    //     Slot: {
    //       Availability: {
    //         expertId: expertId, // Replace with actual expert ID
    //       },
    //     },
    //   },
    // })
    // const deleteSlotsResult = await prisma.slot.deleteMany({
    //   where: {
    //     Availability: {
    //       expertId: expertId,
    //     },
    //   },
    // })

    // const deleteResult = await prisma.availability.deleteMany({
    //   where: {
    //     expertId: expertId,
    //   },
    // })
    // return
    // console.log(availabilities, expert_refresh_token, expertId, timeZone)

    // const expertExists = await prisma.expert.findUnique({
    //   where: { id: expertId },
    // })

    // if (!expertExists) {
    //   console.log(`No expert found with ID: ${expertId}`)
    //   return
    // }

    // storing timezone to the expert  (next time storing it when becoming an expert maybe )

    for (const [date, data] of Object.entries(availabilities)) {
      console.log(1)
      // const day = new Date(date) // Convert the date string to a Date object
      const format = 'ddd MMM DD YYYY'
      // Specify the format of your input date string

      const dateInTimeZone = moment.tz(date, format, timeZone)
      console.log(2)

      const day = dateInTimeZone.format()
      console.log(3)

      // Convert date string to a Moment object in the specified time zone

      console.log({ date }, { day })

      // const day = localDay.utc().toISOString() // Convert to UTC and format to ISO date string

      // Ensure there is an availability for this day for the expert
      let availability = await prisma.availability.findFirst({
        where: {
          day: day,
          expertId: expertId,
        },
      })

      if (!availability) {
        availability = await prisma.availability.create({
          data: {
            timezone: timeZone,
            day: day,
            expert: {
              connect: { id: expertId },
            },
          },
        })
      }

      const existingSlots = await prisma.slot.findMany({
        where: { availabilityId: availability.id },
      })

      // // 2. Compare and Delete
      // @ts-ignore
      const newSlotIds = new Set(data.slots.map((slot) => slot.id))
      const slotsToDelete = existingSlots.filter(
        (slot) => !newSlotIds.has(slot.id)
      )
      for (const slot of slotsToDelete) {
        await prisma.slot.delete({ where: { id: slot.id } })
      }

      // @ts-ignore
      for (const slot of data.slots) {
        if (slot.unavailable) {
          continue
        }
        // console.log({ date })
        console.log({ timeZone })
        const formatInput = 'ddd MMM DD YYYY' // Input format
        const formatOutput = 'ddd MMM DD YYYY' // Desired output format

        const dateUTC = moment.tz(date, formatInput, timeZone)

        const dateInUTC = dateUTC.format(formatOutput)
        // console.log({ date }, { dateInUTC })

        // return
        const format = 'ddd MMM DD YYYY HH:mm'
        const endTime = slot.end === '24:00' ? '23:59' : slot.end
        const startDateString = `${dateInUTC} ${slot.start}`
        const endDateString =
          slot.end === '24:00'
            ? moment(`${dateInUTC} ${endTime}`, format)
                .add(1, 'day')
                .format(format)
            : `${dateInUTC} ${endTime}`

        console.log({ startDateString }, { endDateString }, slot.end)
        const localStartTime = moment.tz(startDateString, format, timeZone)
        const localEndTime = moment.tz(endDateString, format, timeZone)
        console.log({ localStartTime }, { localEndTime }, slot.end)

        // const utcStart = localStartTime.utc()
        // const utcEnd = localEndTime.utc()
        // console.log('from here', { utcStart }, { utcEnd })
        let slotsToCreateOrUpdate = []

      // @ts-ignore
        slotsToCreateOrUpdate.push({
          date: localStartTime.format('YYYY-MM-DD'),
          start: localStartTime.format('HH:mm'),
          end: localEndTime.format('HH:mm'),
        })
        // }

        console.log({ slotsToCreateOrUpdate })
        // return
        for (const splitSlot of slotsToCreateOrUpdate) {
          console.log('heree !!', { day })
          console.log('slot t oadd ', splitSlot, availability)

          await prisma.slot.upsert({
            where: { id: slot.id },
            update: {
              start: splitSlot.start,
              end: splitSlot.end,
              // ... other slot details
              recurring: slot.recurring,
              hide: slot.hide || false,
              // Update exceptions here if necessary
              exceptions: {
                deleteMany: {}, // Clears existing exceptions during update
                create: slot.exceptions?.map((exceptionDate) => {
                  const format = 'ddd MMM DD YYYY'
                  // Specify the format of your input date string

                  const dateInTimeZone = moment.tz(
                    exceptionDate,
                    format,
                    timeZone
                  )
                  const exceptionDay = dateInTimeZone.format()

                  return {
                    date: exceptionDay,
                  }
                }),
              },
            },
            create: {
              id: slot.id,
              start: splitSlot.start,
              end: splitSlot.end,
              // ... other slot details
              recurring: slot.recurring,
              hide: slot.hide || false,
              originalId: '',
              Availability: {
                connect: { id: availability.id }, // Connect using the unique availability ID
              },
              exceptions: {
                create: slot.exceptions.map((exceptionDate) => {
                  const format = 'ddd MMM DD YYYY'
                  // Specify the format of your input date string

                  const dateInTimeZone = moment.tz(
                    exceptionDate,
                    format,
                    timeZone
                  )
                  const exceptionDay = dateInTimeZone.format()

                  return {
                    date: exceptionDay,
                  }
                }),
              },
            },
          })
        }
      }
    }
    let expert_access_Token
    console.log('3')

    const allAvailabilities = await prisma.availability.findMany({
      where: { expertId: expertId },
    })

    // Delete availabilities with no slots
    for (const availability of allAvailabilities) {
      const slotCount = await prisma.slot.count({
        where: { availabilityId: availability.id },
      })

      if (slotCount === 0) {
        console.log('id to delete', availability.id)
        await prisma.availability.delete({
          where: { id: availability.id },
        })
      }
    }

    console.log(`about to revalidate /calendar?expertId=${userId}`)
    revalidatePath(`/calendar?expertId=${userId}`, 'page')

    if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_APP_SECRET) {
      const expert_response = await fetch(
        'https://oauth2.googleapis.com/token',
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
          },
          body: new URLSearchParams({
            client_id: process.env.GOOGLE_CLIENT_ID,
            client_secret: process.env.GOOGLE_APP_SECRET,
            refresh_token: expert_refresh_token, // Replace with the actual refresh token
            grant_type: 'refresh_token',
          }),
        }
      )

      const expert_data = await expert_response.json()

      expert_access_Token = expert_data.access_token
    }
    const res = await createCalendarEvent(
      expert_access_Token,
      availabilities,
      timeZone
    )

    console.log(' After Create')

    return res
    // await createCalendarEvent(client_access_Token, appointement)

    // console.log('done', newAppointment)
  } catch (err) {
    console.log(err.message)
  }
}

// cancel calendar
export const cancelCalendarEvent = async (accessToken, eventId) => {
  try {
    const response = await axios.delete(
      `https://www.googleapis.com/calendar/v3/calendars/primary/events/${eventId}`,
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
          'Content-Type': 'application/json',
        },
      }
    )

    if (response.status === 204) {
      // 204 No Content is the success status for deletion
      console.log('Event deleted successfully')
      return true
    } else {
      console.error('Failed to delete the event')
      return false
    }
  } catch (error) {
    console.error('Error canceling calendar event', error)
    throw error // or handle this error appropriately
  }
}

//  remove event
async function removeExtraneousEvents(accessToken, currentAvailabilities) {
  const allEvents = await fetchAllRelevantEvents(accessToken)
  console.log({ currentAvailabilities })

  const currentSlotIds = new Set()

  Object.values(currentAvailabilities).forEach((day) => {
    day.slots.forEach((slot) => currentSlotIds.add(slot.id))
  })

  for (const event of allEvents) {
    const slotId = event.extendedProperties?.private?.slotId
    const belongsToApp =
      event.extendedProperties?.private?.name === 'myBestCall'

    // If the event belongs to your application but isn't in the current availabilities, remove it
    if (belongsToApp && slotId && !currentSlotIds.has(slotId)) {
      await cancelCalendarEvent(accessToken, event.id) // Using the cancelCalendarEvent function from earlier
      console.log(`Removed extraneous event: ${event.summary} (ID: ${slotId})`)
    }
  }
}

// add event to google calendar
const createCalendarEvent = async (accessToken, availabilities, timeZone) => {
  try {
    //  remove events that we remove from front-endt and prisma
    await removeExtraneousEvents(accessToken, availabilities)

    for (const [dayString, data] of Object.entries(availabilities)) {
      for (const slot of data.slots) {
        console.log(
          'dayString:',
          dayString,
          'Start:',
          slot.start,
          'End:',
          slot.end
        )

        // Handle updating exceptions for both existing and new slots
        if (slot.recurring && slot.exceptions && slot.exceptions.length > 0) {
          // Format the exceptions as dates

          console.log('from recurring ', { slot })

          const exceptionDates = slot.exceptions.map(
            (exception) => new Date(exception)
          )

          // Update the recurring event in Google Calendar with the new exceptions
          // Ensure slot.calendarEventId links to the corresponding Google Calendar event ID
          await addExceptionsToRecurringEvent(
            accessToken,
            slot.id,
            exceptionDates,
            timeZone
          )
        }
      }

      // / Filter out slots that already exist in the calendar to avoid duplicate
      const slotsToAdd = await filterOutExistingSlots(accessToken, data.slots)

      for (const slot of slotsToAdd) {
        console.log(
          'dayString:',
          dayString,
          'Start:',
          slot.start,
          'End:',
          slot.end
        )

        const day = new Date(dayString) // Convert the readable string to a Date object
        // Extract the year, month (0-11), and day from the Date object
        const year = day.getFullYear()
        const month = day.getMonth() + 1 // +1 because getMonth() returns 0-11
        const date = day.getDate()

        // Ensure month and date are in 'MM' or 'DD' format
        const monthFormatted = month < 10 ? `0${month}` : month
        const dateFormatted = date < 10 ? `0${date}` : date

        // Recreate the ISO string for start and end times
        const startDateTime = new Date(
          `${year}-${monthFormatted}-${dateFormatted}T${slot.start}`
        ).toISOString()
        const endDateTime = new Date(
          `${year}-${monthFormatted}-${dateFormatted}T${slot.end}`
        ).toISOString()

        let recurrenceRule = []
        if (slot.recurring) {
          // RRULE for weekly recurrence, adjust as needed
          recurrenceRule = [`RRULE:FREQ=WEEKLY;COUNT=10`] // Recur weekly for 10 weeks as an example
        }
        console.log('here1')

        // Event details
        const eventDetails = {
          // id: slot.id,
          summary: 'MyBestCall availablity',
          description: 'This is the availabilities you set with MyBestCall ', // Customize as needed
          start: {
            dateTime: startDateTime,
            timeZone,
          },
          end: {
            dateTime: endDateTime,
            timeZone,
          },
          recurrence: recurrenceRule,
          // ...additional fields like reminders
          extendedProperties: {
            private: {
              slotId: slot.id, // Replace with your actual slot ID
              name: 'myBestCall',
            },
          },
        }

        const response = await fetch(
          `https://www.googleapis.com/calendar/v3/calendars/primary/events`,
          {
            method: 'POST',
            headers: {
              Authorization: `Bearer ${accessToken}`,
              'Content-Type': 'application/json',
            },
            body: JSON.stringify(eventDetails),
          }
        )

        const result = await response.json()
        console.log(result) // Log or handle the created event response
        return { succeded: true }
      }
    }
  } catch (err) {
    console.error('Error creating calendar event:', err.message)
    throw err // Or handle error appropriately
  }
}

//  get from prisma
export const fetchExpertAvailabilities = async (expertId) => {
  const availabilities = await prisma.expert.findUnique({
    where: { id: expertId },
    include: {
      Availabilities: {
        include: {
          slots: {
            include: {
              exceptions: true, // Include related exceptions for each slot
            },
          }, // Include related slots and their exceptions
        },
      },
    },
  })
  let groupedSlots = {}
  let dataToSend = {}
  // console.log('Expert Availabilities', availabilities)

  console.log(
    availabilities?.Availabilities.map((av) => {
      av.slots?.map((slot, index) => {
        // console.log({ slot })
      })
    })
  )

  if (availabilities) {
    groupedSlots = groupSlotsByDay(availabilities.Availabilities)
    console.log({ groupedSlots })

    dataToSend = JSON.stringify(groupedSlots)
  }

  return dataToSend
}
//  from prisma

function groupSlotsByDay(availabilities) {
  const grouped = {}

  availabilities.forEach((availability) => {
    const timeZone = availability.timezone

    // convert from utc to timezone

    // Keep the day in UTC
    const utcDay = moment.tz(availability.day, timeZone).format('YYYY-MM-DD')

    // Convert the UTC day to the local time zone
    // console.log('availability.day, { utcDay }')
    // console.log('availability.day, { utcDay }')
    // console.log('availability.day, { utcDay }')
    // console.log('availability.day, { utcDay }')
    // console.log(availability)

    const dayKey = moment
      .utc(availability.day)
      .tz(timeZone || "Europe/Paris")
      .format('ddd MMM DD YYYY');

    if (!grouped[dayKey]) {
      grouped[dayKey] = { slots: [] }
    }

    availability.slots.forEach((slot) => {
      const day = moment(utcDay).tz(timeZone || "Europe/Paris").format('YYYY-MM-DD')
      const startMoment = moment.tz(`${utcDay}T${slot.start}`, timeZone || "Europe/Paris")
      const endMoment = moment.tz(`${utcDay}T${slot.end}`, timeZone || "Europe/Paris")

      const formattedExceptions = (slot.exceptions || []).map((exception) => {
        return new Date(exception.date).toDateString() // Convert date to "Wed Jan 03 2024" format
      })

      let formattedSlot = {
        id: slot.id,
        start: startMoment.format('HH:mm'),
        end: endMoment.format('HH:mm'),
        hide: slot.hide,
        recurring: slot.recurring,
        exceptions: formattedExceptions,
        // ... other properties ...
      }

      // If the slot does not span two days
      grouped[dayKey].slots.push(formattedSlot)
    })
  })

  // console.log("groupSlotsByDaygroupSlotsByDay")
  // console.log({ availabilities })
  return grouped
}

// check for duplicate in google calendar
async function findEventBySlotId(accessToken, slotId) {
  try {
    const response = await axios.get(
      `https://www.googleapis.com/calendar/v3/calendars/primary/events`,
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
          'Content-Type': 'application/json',
        },
        params: {
          privateExtendedProperty: `slotId=${slotId}`, // Query for your specific slot ID
        },
      }
    )

    const events = response.data.items // The events in the calendar
    return events.find(
      (event) => event.extendedProperties.private.slotId === slotId
    ) // Find and return the event with the matching slotId
  } catch (error) {
    console.error('Error searching for event by slot ID', error)
    return null
  }
}

async function filterOutExistingSlots(accessToken, allSlots) {
  const slotsToAdd = []

  for (const slot of allSlots) {
    const existingEvent = await findEventBySlotId(accessToken, slot.id)

    if (!existingEvent) {
      // This slot doesn't exist in the calendar, so we need to add it
      slotsToAdd.push(slot)
    } else {
      console.log(
        `Skipping addition for slot ${slot.id} as it already exists in the calendar.`
      )
    }
  }

  return slotsToAdd // Return only the slots that don't exist in the calendar
}

//  fetch all google calendar events belongs to our website
async function fetchAllRelevantEvents(accessToken) {
  try {
    const response = await axios.get(
      `https://www.googleapis.com/calendar/v3/calendars/primary/events`,
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
          'Content-Type': 'application/json',
        },
        params: {
          privateExtendedProperty: 'name=myBestCall', // Filter for events with your application's identifier
        },
      }
    )

    return response.data.items // The events in the calendar
  } catch (error) {
    console.error('Error fetching calendar events', error.message)
    throw error
  }
}

async function addExceptionsToRecurringEvent(
  accessToken,
  eventId,
  exceptionDates,
  timeZone
) {
  try {
    const event = await findEventBySlotId(accessToken, eventId)

    if (!event || !event.start || !event.recurrence) {
      console.error(
        'Event details incomplete or not a recurring event:',
        eventId
      )
      return null
    }

    // Extract and format the start time portion from the event's start dateTime
    const eventStartTime = event.start.dateTime.substring(11, 19) // "HH:MM:SS"

    let eventTime = moment(eventStartTime, 'HH:mm:ss', timeZone)

    // Construct EXDATE string for each exception date
    const formattedExceptions = exceptionDates
      .map((isoDate) => {
        // Parse the ISO date while keeping the original zone
        let combinedDateTime = moment(isoDate, 'YYYYMMDDTHHmmss')

        combinedDateTime.hour(eventTime.hour())
        combinedDateTime.minute(eventTime.minute())
        combinedDateTime.second(eventTime.second())
        // Convert to the event's time zone and format
        return combinedDateTime.format('YYYYMMDDTHHmmss')
      })
      .join(',')

    console.log('Formatted Exceptions:', formattedExceptions)

    // Ensure the RRULE is correctly formatted and extracted
    const rrule = event.recurrence
      .find((r) => r.startsWith('RRULE:'))
      .replace('RRULE:', '')

    const updatedRecurrence = [
      `RRULE:${rrule}`,
      `EXDATE;TZID=${timeZone}:${formattedExceptions}`,
    ]

    // Patch the event with updated recurrence data
    const response = await axios.patch(
      `https://www.googleapis.com/calendar/v3/calendars/primary/events/${event.id}`,
      { recurrence: updatedRecurrence },
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
          'Content-Type': 'application/json',
        },
      }
    )

    return response.data
  } catch (error) {
    console.error(
      'Error adding exceptions to recurring event:',
      error.response?.data || error.message
    )
    return null
  }
}
