import { getPayload, PaginatedDocs } from 'payload'
import { unstable_cache } from 'next/cache'
import config from '@/payload.config'
import { EventOccurrence } from '@/payload-types'

const CACHE_TTL = 300 // 5 minutes

type ListArgs = {
  parishId?: string
  groupId?: string
  eventId?: string
  limit?: number
  page?: number
  fromDate?: Date
  toDate?: Date
}

// Resolved args use ISO strings so they serialize into a stable cache key.
type ResolvedArgs = {
  parishId?: string
  groupId?: string
  eventId?: string
  limit: number
  page: number
  fromDate?: string
  toDate?: string
}

// Round down to the cache window so a default `new Date()` (millisecond
// precision) produces a stable key for the duration of the TTL.
function roundDownToTtl(date: Date): Date {
  const ms = CACHE_TTL * 1000
  return new Date(Math.floor(date.getTime() / ms) * ms)
}

/**
 * Fetch upcoming event occurrences, joined to their parent event. Always
 * filters to occurrences whose parent event is published.
 */
const getUpcomingOccurrences = unstable_cache(
  async (args: ResolvedArgs): Promise<PaginatedDocs<EventOccurrence>> => {
    const { parishId, groupId, eventId, limit, page, fromDate, toDate } = args

    const query: any = {
      and: [
        // Overlap semantics: an occurrence stays "upcoming" while it is still
        // running, not only before it starts. endDateTime is null for
        // occurrences without an end; those fall back to the start date.
        {
          or: [
            { date: { greater_than_equal: fromDate } },
            { endDateTime: { greater_than_equal: fromDate } },
          ],
        },
        { 'event._status': { equals: 'published' } },
      ],
    }

    if (toDate) {
      query.and.push({ date: { less_than: toDate } })
    }
    if (eventId) {
      query.and.push({ event: { equals: eventId } })
    }
    if (parishId) {
      query.and.push({ 'event.parish': { equals: parishId } })
    }
    if (groupId) {
      query.and.push({ 'event.group': { equals: groupId } })
    }

    const payload = await getPayload({ config })
    return payload.find({
      collection: 'eventOccurrence',
      sort: 'date',
      where: query,
      depth: 2,
      limit,
      page,
    }) as Promise<PaginatedDocs<EventOccurrence>>
  },
  ['fetchUpcomingOccurrences'],
  { tags: ['events'], revalidate: CACHE_TTL },
)

export async function fetchUpcomingOccurrences(
  args?: ListArgs,
): Promise<PaginatedDocs<EventOccurrence>> {
  const {
    parishId,
    groupId,
    eventId,
    limit = 30,
    page = 0,
    fromDate = new Date(),
    toDate,
  } = args || {}

  return getUpcomingOccurrences({
    parishId,
    groupId,
    eventId,
    limit,
    page,
    fromDate: roundDownToTtl(fromDate).toISOString(),
    toDate: toDate?.toISOString(),
  })
}

/**
 * Fetch past event occurrences (most recent first), joined to their parent
 * event. Always filters to occurrences whose parent event is published.
 */
const getPastOccurrences = unstable_cache(
  async (args: ResolvedArgs): Promise<PaginatedDocs<EventOccurrence>> => {
    const { parishId, groupId, eventId, limit, page, fromDate, toDate } = args

    const query: any = {
      and: [
        { date: { less_than: toDate } },
        { 'event._status': { equals: 'published' } },
      ],
    }

    if (fromDate) {
      query.and.push({ date: { greater_than_equal: fromDate } })
    }
    if (eventId) {
      query.and.push({ event: { equals: eventId } })
    }
    if (parishId) {
      query.and.push({ 'event.parish': { equals: parishId } })
    }
    if (groupId) {
      query.and.push({ 'event.group': { equals: groupId } })
    }

    const payload = await getPayload({ config })
    return payload.find({
      collection: 'eventOccurrence',
      sort: '-date',
      where: query,
      depth: 2,
      limit,
      page,
    }) as Promise<PaginatedDocs<EventOccurrence>>
  },
  ['fetchPastOccurrences'],
  { tags: ['events'], revalidate: CACHE_TTL },
)

export async function fetchPastOccurrences(
  args?: ListArgs,
): Promise<PaginatedDocs<EventOccurrence>> {
  const {
    parishId,
    groupId,
    eventId,
    limit = 30,
    page = 0,
    fromDate,
    toDate = new Date(),
  } = args || {}

  return getPastOccurrences({
    parishId,
    groupId,
    eventId,
    limit,
    page,
    fromDate: fromDate?.toISOString(),
    toDate: roundDownToTtl(toDate).toISOString(),
  })
}

/**
 * Fetch upcoming occurrences, falling back to past occurrences if there
 * are no upcoming ones.
 */
export async function fetchUpcomingOrPastOccurrences(
  args?: ListArgs,
): Promise<PaginatedDocs<EventOccurrence>> {
  const upcoming = await fetchUpcomingOccurrences(args)
  if (upcoming.docs.length > 0) return upcoming
  return fetchPastOccurrences(args)
}

/**
 * Fetch a single occurrence by id with its parent event populated.
 * Returns undefined if not found or if the id is malformed.
 */
export async function fetchOccurrenceById(
  id: string,
  draft: boolean = false,
): Promise<EventOccurrence | undefined> {
  try {
    const payload = await getPayload({ config })
    return await payload.findByID({
      collection: 'eventOccurrence',
      id,
      depth: 2,
      draft,
    })
  } catch {
    return undefined
  }
}
