import {
  Body,
  Controller,
  Delete,
  ForbiddenException,
  Get,
  HttpCode,
  Param,
  Patch,
  Post,
  Query,
  UseGuards,
} from '@nestjs/common'
import { Types } from 'mongoose'
import { ActorService } from '../auth/actor.service.js'
import { CurrentUser } from '../auth/current-user.decorator.js'
import { JwtAuthGuard } from '../auth/jwt-auth.guard.js'
import type { AuthentikJwtPayload } from '../auth/jwt-payload.interface.js'
import { TenantsService } from '../tenants/tenants.service.js'
import { AvailabilityService } from './availability/availability.service.js'
import { CreateAvailabilityDto, UpdateAvailabilityDto } from './availability/dto/availability-dtos.js'
import { BookingsService, type BookingContext } from './bookings/bookings.service.js'
import { CancelBookingDto, RescheduleBookingDto } from './public/dto/public-dtos.js'
import { CreateEventTypeDto, UpdateEventTypeDto } from './event-types/dto/event-type-dtos.js'
import { EventTypesService } from './event-types/event-types.service.js'
import { CreateHostDto, SetHostActiveDto } from './hosts/dto/create-host.dto.js'
import { HostsService } from './hosts/hosts.service.js'
import { OverviewService } from './overview/overview.service.js'

// Authenticated host/admin scheduling config (OIDC via Authentik). Tenant-scoped
// and gated exactly like the rest of platform-api: platformAdmin OR a member of
// the tenant. Mounted under /api/v1 per the scheduling routing decision.
@Controller('api/v1/tenants/:slug/scheduling')
@UseGuards(JwtAuthGuard)
export class SchedulingAdminController {
  constructor(
    private readonly actor: ActorService,
    private readonly tenants: TenantsService,
    private readonly hosts: HostsService,
    private readonly availability: AvailabilityService,
    private readonly eventTypes: EventTypesService,
    private readonly bookings: BookingsService,
    private readonly overview: OverviewService,
  ) {}

  private async gate(slug: string, jwt: AuthentikJwtPayload): Promise<Types.ObjectId> {
    const actor = await this.actor.resolve(jwt)
    const tenant = await this.tenants.findOneBySlug(slug)
    if (!actor.platformAdmin && !actor.tenantIds.some((id) => id.equals(tenant._id))) {
      throw new ForbiddenException(`No access to tenant "${slug}"`)
    }
    return tenant._id
  }

  // ── Overview / analytics (read-only) ──
  @Get('overview')
  async getOverview(@Param('slug') slug: string, @CurrentUser() jwt: AuthentikJwtPayload) {
    return this.overview.forTenant(await this.gate(slug, jwt))
  }

  // ── Hosts ──
  @Get('hosts')
  async listHosts(@Param('slug') slug: string, @CurrentUser() jwt: AuthentikJwtPayload) {
    return this.hosts.list(await this.gate(slug, jwt))
  }

  @Post('hosts')
  async createHost(@Param('slug') slug: string, @Body() dto: CreateHostDto, @CurrentUser() jwt: AuthentikJwtPayload) {
    return this.hosts.create(await this.gate(slug, jwt), dto)
  }

  @Get('hosts/:hostId')
  async getHost(@Param('slug') slug: string, @Param('hostId') hostId: string, @CurrentUser() jwt: AuthentikJwtPayload) {
    return this.hosts.getById(await this.gate(slug, jwt), hostId)
  }

  @Patch('hosts/:hostId/active')
  async setHostActive(
    @Param('slug') slug: string,
    @Param('hostId') hostId: string,
    @Body() dto: SetHostActiveDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
  ) {
    return this.hosts.setActive(await this.gate(slug, jwt), hostId, dto.isActive)
  }

  @Post('hosts/:hostId/rotate-credential')
  @HttpCode(204)
  async rotateCredential(@Param('slug') slug: string, @Param('hostId') hostId: string, @CurrentUser() jwt: AuthentikJwtPayload) {
    await this.hosts.rotateCredential(await this.gate(slug, jwt), hostId)
  }

  @Delete('hosts/:hostId')
  @HttpCode(204)
  async removeHost(@Param('slug') slug: string, @Param('hostId') hostId: string, @CurrentUser() jwt: AuthentikJwtPayload) {
    await this.hosts.remove(await this.gate(slug, jwt), hostId)
  }

  // ── Availability schedules ──
  @Get('hosts/:hostId/availability')
  async listAvailability(@Param('slug') slug: string, @Param('hostId') hostId: string, @CurrentUser() jwt: AuthentikJwtPayload) {
    const tenantId = await this.gate(slug, jwt)
    return this.availability.list(tenantId, new Types.ObjectId(hostId))
  }

  @Post('hosts/:hostId/availability')
  async createAvailability(
    @Param('slug') slug: string,
    @Param('hostId') hostId: string,
    @Body() dto: CreateAvailabilityDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
  ) {
    const tenantId = await this.gate(slug, jwt)
    return this.availability.create(tenantId, new Types.ObjectId(hostId), dto)
  }

  @Patch('availability/:id')
  async updateAvailability(
    @Param('slug') slug: string,
    @Param('id') id: string,
    @Body() dto: UpdateAvailabilityDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
  ) {
    return this.availability.update(await this.gate(slug, jwt), id, dto)
  }

  @Delete('availability/:id')
  @HttpCode(204)
  async removeAvailability(@Param('slug') slug: string, @Param('id') id: string, @CurrentUser() jwt: AuthentikJwtPayload) {
    await this.availability.remove(await this.gate(slug, jwt), id)
  }

  // ── Event types ──
  @Get('hosts/:hostId/event-types')
  async listEventTypes(@Param('slug') slug: string, @Param('hostId') hostId: string, @CurrentUser() jwt: AuthentikJwtPayload) {
    const tenantId = await this.gate(slug, jwt)
    return this.eventTypes.list(tenantId, new Types.ObjectId(hostId))
  }

  @Post('hosts/:hostId/event-types')
  async createEventType(
    @Param('slug') slug: string,
    @Param('hostId') hostId: string,
    @Body() dto: CreateEventTypeDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
  ) {
    const tenantId = await this.gate(slug, jwt)
    return this.eventTypes.create(tenantId, new Types.ObjectId(hostId), dto)
  }

  @Patch('event-types/:id')
  async updateEventType(
    @Param('slug') slug: string,
    @Param('id') id: string,
    @Body() dto: UpdateEventTypeDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
  ) {
    return this.eventTypes.update(await this.gate(slug, jwt), id, dto)
  }

  @Delete('event-types/:id')
  @HttpCode(204)
  async removeEventType(@Param('slug') slug: string, @Param('id') id: string, @CurrentUser() jwt: AuthentikJwtPayload) {
    await this.eventTypes.remove(await this.gate(slug, jwt), id)
  }

  // ── Bookings ──
  @Get('bookings')
  async listBookings(
    @Param('slug') slug: string,
    @Query('hostId') hostId: string | undefined,
    @CurrentUser() jwt: AuthentikJwtPayload,
  ) {
    const tenantId = await this.gate(slug, jwt)
    return this.bookings.listForTenant(tenantId, hostId ? new Types.ObjectId(hostId) : undefined)
  }

  @Post('bookings/:bookingId/cancel')
  async cancelBooking(
    @Param('slug') slug: string,
    @Param('bookingId') bookingId: string,
    @Body() dto: CancelBookingDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
  ) {
    const tenantId = await this.gate(slug, jwt)
    const { booking, ctx } = await this.resolveBookingCtx(tenantId, bookingId)
    return this.bookings.cancelResolved(booking, dto.reason, ctx)
  }

  @Post('bookings/:bookingId/reschedule')
  async rescheduleBooking(
    @Param('slug') slug: string,
    @Param('bookingId') bookingId: string,
    @Body() dto: RescheduleBookingDto,
    @CurrentUser() jwt: AuthentikJwtPayload,
  ) {
    const tenantId = await this.gate(slug, jwt)
    const { booking, ctx } = await this.resolveBookingCtx(tenantId, bookingId)
    return this.bookings.rescheduleResolved(booking, new Date(dto.startUtc), ctx)
  }

  // Operator "retry now" for a pending / calendar_failed booking's calendar write.
  @Post('bookings/:bookingId/retry')
  async retryBooking(
    @Param('slug') slug: string,
    @Param('bookingId') bookingId: string,
    @CurrentUser() jwt: AuthentikJwtPayload,
  ) {
    const tenantId = await this.gate(slug, jwt)
    return this.bookings.adminRetryCalendarWrite(tenantId, bookingId)
  }

  // Load a tenant-scoped booking + its full booking context (tenant + host + event type).
  private async resolveBookingCtx(tenantId: Types.ObjectId, bookingId: string) {
    const booking = await this.bookings.getForTenant(tenantId, bookingId)
    const tenant = await this.tenants.findOneById(tenantId)
    const host = await this.hosts.getById(tenantId, String(booking.hostId))
    const eventType = await this.eventTypes.get(tenantId, String(booking.eventTypeId))
    const ctx: BookingContext = {
      tenant: { _id: tenant._id, slug: tenant.slug, name: tenant.name, brandColor: tenant.brandColor },
      host,
      eventType,
    }
    return { booking, ctx }
  }
}
