import type { TaskConfig } from 'payload'

import {
  combineDateAndTime,
  generateOccurrenceDates,
  type ScheduleEntry,
} from '@/jobs/lib/scheduleOccurrences'

/**
 * Payload Jobs Queue task that materializes each Church's
 * `recurringSchedule` into real `Worship` documents for the coming weeks.
 *
 * Rationale: the rest of the app (MassTimesBlock, gemeinde pages,
 * /gottesdienst/{id} detail pages) all read from the Worship collection
 * via `fetchWorship`. By generating real documents instead of rendering
 * from the schedule on the fly, we keep a single source of truth and
 * avoid duplicating rendering logic. Generated docs are normal Worship
 * documents and can be edited by hand (cancel, change celebrant, etc.).
 *
 * Triggers:
 *  - `afterChange` hook on Church (see collections/Churches.ts) queues
 *    a run whenever a schedule is saved
 *  - Own `schedule` entry below runs weekly to keep the rolling window
 *    populated
 *  - Manual: call `payload.jobs.queue({ task: 'generateRecurringMasses' })`
 */

// How far into the future we materialize documents on each run.
// The weekly cron keeps this rolling window populated.
const DEFAULT_WEEKS_AHEAD = 2
const MS_PER_WEEK = 7 * 24 * 60 * 60 * 1000

type GenerateRecurringMassesInput = {
  weeksAhead?: number
  churchId?: string
}

type GenerateRecurringMassesOutput = {
  created: number
  skipped: number
}

export const generateRecurringMassesTask: TaskConfig<{
  input: GenerateRecurringMassesInput
  output: GenerateRecurringMassesOutput
}> = {
  slug: 'generateRecurringMasses',
  label: 'Wiederkehrende Messzeiten erzeugen',
  inputSchema: [
    {
      name: 'weeksAhead',
      type: 'number',
      required: false,
    },
    {
      name: 'churchId',
      type: 'text',
      required: false,
    },
  ],
  outputSchema: [
    { name: 'created', type: 'number' },
    { name: 'skipped', type: 'number' },
  ],
  // Weekly cron to keep the rolling window populated even if nobody
  // edits a schedule. Payload's 6-field cron format starts with seconds.
  // → every Monday at 03:00 server time.
  schedule: [
    {
      cron: '0 0 3 * * 1',
      queue: 'default',
    },
  ],
  handler: async ({ input, req }) => {
    const { payload } = req
    const weeksAhead = input?.weeksAhead ?? DEFAULT_WEEKS_AHEAD
    const now = new Date()
    const horizon = new Date(now.getTime() + weeksAhead * MS_PER_WEEK)

    // Scope to a single church when invoked from the Church afterChange
    // hook, otherwise process every church in one run.
    const churchesResult = await payload.find({
      collection: 'church',
      depth: 0,
      limit: 1000,
      pagination: false,
      where: input?.churchId
        ? { id: { equals: input.churchId } }
        : undefined,
    })

    let created = 0
    let skipped = 0

    for (const church of churchesResult.docs) {
      // Cast needed because payload-types only exposes recurringSchedule
      // on the full Church interface and payload.find returns a looser
      // shape at depth: 0.
      const schedule = (church as { recurringSchedule?: unknown[] })
        .recurringSchedule
      if (!Array.isArray(schedule) || schedule.length === 0) continue

      for (const rawEntry of schedule) {
        const entry = rawEntry as ScheduleEntry & {
          time?: string | Date | null
          type?: 'MASS' | 'FAMILY' | 'WORD'
          defaultCelebrant?: string | null
          defaultTitle?: string | null
          defaultDescription?: string | null
        }

        // Guard: required fields may be missing if an editor saved a
        // half-filled row. Skip rather than crash the whole run.
        if (!entry.time || !entry.type) {
          skipped += 1
          continue
        }

        // Resolve the entry's recurrence pattern (weekly / biweekly /
        // monthly Nth weekday) into concrete calendar dates in the
        // [now, horizon] window. Returns dates at midnight only — we
        // combine with the time-of-day below.
        const occurrenceDates = generateOccurrenceDates(entry, now, horizon)
        if (occurrenceDates.length === 0) {
          skipped += 1
          continue
        }
        const timeSource = new Date(entry.time)

        for (const occurrenceDate of occurrenceDates) {
          // Build the real Worship.date as (calendar date) + (HH:mm from
          // the schedule) using local-time components. This is the step
          // that keeps DST transitions from shifting the wall-clock hour.
          const date = combineDateAndTime(occurrenceDate, timeSource)

          // `now` could land mid-week: skip any occurrence that already
          // passed earlier today.
          if (date.getTime() < now.getTime()) {
            skipped += 1
            continue
          }

          // Append-only: if *any* Worship doc already exists at this
          // exact (church, timestamp) slot — whether generated by a
          // previous run or entered manually — leave it alone. This is
          // what protects admin edits (cancellations, celebrant changes,
          // etc.) from being overwritten.
          const existing = await payload.find({
            collection: 'worship',
            depth: 0,
            limit: 1,
            pagination: false,
            where: {
              and: [
                { location: { equals: church.id } },
                { date: { equals: date.toISOString() } },
              ],
            },
          })

          if (existing.docs.length > 0) {
            skipped += 1
            continue
          }

          // `generated: true` marks this as auto-created so future
          // cleanup tooling can target it without touching manual rows.
          await payload.create({
            collection: 'worship',
            data: {
              date: date.toISOString(),
              location: church.id,
              type: entry.type,
              cancelled: false,
              title: entry.defaultTitle || undefined,
              celebrant: entry.defaultCelebrant || undefined,
              description: entry.defaultDescription || undefined,
              generated: true,
            },
          })

          created += 1
        }
      }
    }

    // Counts surface on the Payload Jobs admin page as the task output.
    payload.logger.info(
      { created, skipped, weeksAhead },
      'generateRecurringMasses finished',
    )

    return {
      output: { created, skipped },
    }
  },
}
