import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
import { HydratedDocument, Types } from 'mongoose'
import type { LocationType } from './event-type.schema.js'

export type BookingDocument = HydratedDocument<Booking>

// 'pending' is the compensating state when the SlotLock is held but the calendar
// write hasn't succeeded yet (§8.2.4) — never surfaced as a confirmed booking.
// 'calendar_failed' is the terminal state once the retry worker exhausts its
// attempts; the SlotLock is released so the slot frees up.
export type BookingStatus = 'pending' | 'confirmed' | 'cancelled' | 'rescheduled' | 'calendar_failed'

// A confirmed appointment. All instants are UTC; attendee/host tz are IANA
// strings for display. `calendarEventUid` is generated client-side BEFORE the
// Stalwart write so a retried write is idempotent (§9). `manageToken` backs the
// unauthenticated self-service cancel/reschedule links.
@Schema({ collection: 'scheduling_bookings', timestamps: true })
export class Booking {
  @Prop({ type: Types.ObjectId, ref: 'Tenant', required: true, index: true })
  tenantId!: Types.ObjectId

  @Prop({ type: Types.ObjectId, ref: 'EventType', required: true, index: true })
  eventTypeId!: Types.ObjectId

  @Prop({ type: Types.ObjectId, ref: 'Host', required: true, index: true })
  hostId!: Types.ObjectId

  @Prop({ enum: ['pending', 'confirmed', 'cancelled', 'rescheduled', 'calendar_failed'], default: 'pending', index: true })
  status!: BookingStatus

  @Prop({ required: true, index: true })
  startUtc!: Date

  @Prop({ required: true })
  endUtc!: Date

  @Prop({ required: true, trim: true })
  attendeeName!: string

  @Prop({ required: true, lowercase: true, trim: true })
  attendeeEmail!: string

  @Prop({ required: true })
  attendeeTimezone!: string

  @Prop({ trim: true })
  attendeeNotes?: string

  // UID written to Stalwart — generated client-side for idempotent retries.
  @Prop({ required: true, index: true })
  calendarEventUid!: string

  // Server-assigned CalendarEvent id (Stalwart's own id, distinct from the UID).
  // Needed for delete/update, which address events by server id, not UID.
  @Prop()
  calendarEventId?: string

  @Prop({ enum: ['jitsi', 'phone', 'in_person', 'custom'] })
  locationType?: LocationType

  // Resolved location (e.g. generated Jitsi room url, or copied details).
  @Prop({ trim: true })
  locationUrl?: string

  // Opaque token for the self-service manage page. Unique + indexed for lookup.
  @Prop({ required: true, unique: true, index: true })
  manageToken!: string

  @Prop({ type: Types.ObjectId, ref: 'Booking' })
  rescheduledFromBookingId?: Types.ObjectId

  @Prop()
  cancelledAt?: Date

  @Prop({ trim: true })
  cancellationReason?: string

  // Reminder bookkeeping for Phase 2 (e.g. 'none' | 'sent_24h'). Kept for
  // backwards compatibility; the reminder worker now tracks idempotency via
  // `sentReminderOffsets` instead.
  @Prop({ default: 'none' })
  reminderState!: string

  // Reminder offsets (in minutes-before-start) that have already been emailed for
  // this booking. The reminder cron only sends an offset absent from this list,
  // then atomically appends it — making reminders idempotent across runs.
  @Prop({ type: [Number], default: [] })
  sentReminderOffsets!: number[]

  // Calendar-write retry bookkeeping (§8.2.4). When the synchronous Stalwart
  // write fails, the booking stays 'pending' with its SlotLock retained and the
  // retry worker drives `calendarWriteAttempts` up to the configured max. The
  // last error is kept for diagnostics; on terminal failure status becomes
  // 'calendar_failed' and the lock is released.
  @Prop({ default: 0 })
  calendarWriteAttempts!: number

  @Prop({ trim: true })
  lastCalendarError?: string
}

export const BookingSchema = SchemaFactory.createForClass(Booking)
