import { Body, Controller, Get, Inject, Ip, Param, Post, Query, UseGuards } from '@nestjs/common'
import { Throttle, ThrottlerGuard } from '@nestjs/throttler'
import { TenantsService } from '../../tenants/tenants.service.js'
import { ABUSE_GUARD, type AbuseGuard } from '../abuse/abuse-guard.js'
import type { BookingContext } from '../bookings/bookings.service.js'
import { BookingsService } from '../bookings/bookings.service.js'
import { EventTypesService } from '../event-types/event-types.service.js'
import { HostsService } from '../hosts/hosts.service.js'
import { SlotService } from '../slots/slot.service.js'
import { BookingDocument } from '../../schemas/booking.schema.js'
import {
  CancelBookingDto,
  CreateBookingDto,
  CreateHoldDto,
  RescheduleBookingDto,
  SlotsQueryDto,
} from './dto/public-dtos.js'
import { PublicSchedulingService } from './public-scheduling.service.js'

// Public booking surface — unauthenticated, served to the booking.dezky.eu app.
// Rate-limited per-IP (anti-abuse). Returns UTC instants; the client renders in
// the visitor's tz. No internal ids or host PII beyond display name leak out.
@Controller('api/v1/public')
@UseGuards(ThrottlerGuard)
export class PublicSchedulingController {
  constructor(
    private readonly publicSvc: PublicSchedulingService,
    private readonly slots: SlotService,
    private readonly bookings: BookingsService,
    private readonly tenants: TenantsService,
    private readonly hosts: HostsService,
    private readonly eventTypes: EventTypesService,
    @Inject(ABUSE_GUARD) private readonly abuseGuard: AbuseGuard,
  ) {}

  @Get(':tenantSlug/:hostSlug/:eventTypeSlug')
  async info(
    @Param('tenantSlug') tenantSlug: string,
    @Param('hostSlug') hostSlug: string,
    @Param('eventTypeSlug') eventTypeSlug: string,
  ) {
    const ctx = await this.publicSvc.resolveContext(tenantSlug, hostSlug, eventTypeSlug)
    return this.publicSvc.publicInfo(ctx)
  }

  @Get(':tenantSlug/:hostSlug/:eventTypeSlug/slots')
  async availableSlots(
    @Param('tenantSlug') tenantSlug: string,
    @Param('hostSlug') hostSlug: string,
    @Param('eventTypeSlug') eventTypeSlug: string,
    @Query() q: SlotsQueryDto,
  ) {
    const ctx = await this.publicSvc.resolveContext(tenantSlug, hostSlug, eventTypeSlug)
    const from = new Date(q.from)
    const to = new Date(q.to)
    // Round-robin: offer the union of the whole pool's free time (a slot shows if
    // ANY pooled host is free). The single-host path is unchanged.
    const slots =
      ctx.eventType.assignment === 'round_robin'
        ? await this.slots.unionAvailableSlots(await this.slots.resolvePoolHosts(ctx.eventType, ctx.host), ctx.eventType, from, to)
        : await this.slots.availableSlots(ctx.host, ctx.eventType, from, to)
    return {
      timezone: q.timezone,
      durationMinutes: ctx.eventType.durationMinutes,
      slots: slots.map((s) => ({ startUtc: s.startUtc.toISOString(), endUtc: s.endUtc.toISOString() })),
    }
  }

  // Tighter limit on the write endpoints than the read default.
  @Throttle({ default: { limit: 10, ttl: 60_000 } })
  @Post(':tenantSlug/:hostSlug/:eventTypeSlug/holds')
  async createHold(
    @Param('tenantSlug') tenantSlug: string,
    @Param('hostSlug') hostSlug: string,
    @Param('eventTypeSlug') eventTypeSlug: string,
    @Body() dto: CreateHoldDto,
    @Ip() ip: string,
  ) {
    await this.abuseGuard.verify(dto.captchaToken, ip)
    const ctx = await this.publicSvc.resolveContext(tenantSlug, hostSlug, eventTypeSlug)
    const { holdId, expiresAt } = await this.bookings.hold(ctx, new Date(dto.startUtc))
    return { holdId, expiresAt: expiresAt.toISOString() }
  }

  @Throttle({ default: { limit: 10, ttl: 60_000 } })
  @Post(':tenantSlug/:hostSlug/:eventTypeSlug/bookings')
  async createBooking(
    @Param('tenantSlug') tenantSlug: string,
    @Param('hostSlug') hostSlug: string,
    @Param('eventTypeSlug') eventTypeSlug: string,
    @Body() dto: CreateBookingDto,
    @Ip() ip: string,
  ) {
    await this.abuseGuard.verify(dto.captchaToken, ip)
    const ctx = await this.publicSvc.resolveContext(tenantSlug, hostSlug, eventTypeSlug)
    const booking = await this.bookings.confirm(ctx, {
      startUtc: new Date(dto.startUtc),
      attendeeName: dto.attendeeName,
      attendeeEmail: dto.attendeeEmail,
      attendeeTimezone: dto.attendeeTimezone,
      attendeeNotes: dto.attendeeNotes,
      holdId: dto.holdId,
    })
    return this.publicBooking(booking, ctx)
  }

  @Get('bookings/:manageToken')
  async manageView(@Param('manageToken') manageToken: string) {
    const booking = await this.bookings.getByManageToken(manageToken)
    const ctx = await this.contextFromBooking(booking)
    return this.publicBooking(booking, ctx)
  }

  @Throttle({ default: { limit: 10, ttl: 60_000 } })
  @Post('bookings/:manageToken/cancel')
  async cancel(@Param('manageToken') manageToken: string, @Body() dto: CancelBookingDto) {
    const booking = await this.bookings.getByManageToken(manageToken)
    const ctx = await this.contextFromBooking(booking)
    const updated = await this.bookings.cancel(manageToken, dto.reason, ctx)
    return this.publicBooking(updated, ctx)
  }

  @Throttle({ default: { limit: 10, ttl: 60_000 } })
  @Post('bookings/:manageToken/reschedule')
  async reschedule(@Param('manageToken') manageToken: string, @Body() dto: RescheduleBookingDto) {
    const booking = await this.bookings.getByManageToken(manageToken)
    const ctx = await this.contextFromBooking(booking)
    const fresh = await this.bookings.reschedule(manageToken, new Date(dto.startUtc), ctx)
    return this.publicBooking(fresh, ctx)
  }

  // Rebuild the booking context (tenant + host + event type) from a stored booking.
  private async contextFromBooking(booking: BookingDocument): Promise<BookingContext> {
    const tenant = await this.tenants.findOneById(booking.tenantId)
    const host = await this.hosts.getById(tenant._id, String(booking.hostId))
    const eventType = await this.eventTypes.get(tenant._id, String(booking.eventTypeId))
    return {
      tenant: { _id: tenant._id, slug: tenant.slug, name: tenant.name, brandColor: tenant.brandColor },
      host,
      eventType,
    }
  }

  private publicBooking(booking: BookingDocument, ctx: BookingContext) {
    return {
      manageToken: booking.manageToken,
      status: booking.status,
      startUtc: booking.startUtc.toISOString(),
      endUtc: booking.endUtc.toISOString(),
      attendeeName: booking.attendeeName,
      attendeeEmail: booking.attendeeEmail,
      attendeeTimezone: booking.attendeeTimezone,
      attendeeNotes: booking.attendeeNotes ?? null,
      locationType: booking.locationType ?? null,
      locationUrl: booking.locationUrl ?? null,
      branding: {
        tenantSlug: ctx.tenant.slug,
        name: ctx.tenant.name,
        brandColor: ctx.tenant.brandColor ?? null,
      },
      host: { slug: ctx.host.slug, displayName: ctx.host.displayName, timezone: ctx.host.timezone },
      eventType: {
        slug: ctx.eventType.slug,
        title: ctx.eventType.title,
        durationMinutes: ctx.eventType.durationMinutes,
      },
    }
  }
}
