import { Body, Controller, Get, Param, ParseUUIDPipe, Patch, Post, Query, Request, Res } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import type { Response } from 'express';
import { CurrentUser } from '@edr/api-common';
import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type';

import { actorLabel } from './current-actor.util';
import { BookingStaff, StaffReference } from '../../common/booking-guards';
import { FREIGHT_PERMS } from '../../seed/freight-permissions.registry';
import { BulkReceiveDto } from './dto/bulk-receive.dto';
import { BulkInspectDto } from './dto/bulk-inspect.dto';
import { DeliverInventoryDto } from './dto/deliver-inventory.dto';
import { FilterWarehouseInventoryDto } from './dto/filter-inventory.dto';
import { InquiryWarehouseInventoryDto } from './dto/inquiry-inventory.dto';
import { LoadInventoryDto } from './dto/load-inventory.dto';
import { MoveInventoryDto } from './dto/move-inventory.dto';
import { StoreInventoryDto } from './dto/store-inventory.dto';
import { ReceiveWarehouseInventoryDto } from './dto/receive-inventory.dto';
import { ApproveDeliveryDto } from './dto/approve-delivery.dto';
import { SetDoubleHandlingDto } from './dto/double-handling.dto';
import { ReleaseOrderDto } from './dto/release-order.dto';
import { ReserveInventoryDto } from './dto/reserve-inventory.dto';
import { UnloadBookingDto } from './dto/unload-booking.dto';
import { SchedulingReadFacade } from './scheduling-read.facade';
import { WarehouseInventoryService } from './warehouse-inventory.service';
import { HandoverService } from './handover.service';

@ApiTags('warehouse-inventory')
@ApiBearerAuth()
@Controller('warehouse-inventory')
export class WarehouseInventoryController {
  constructor(
    private readonly inventoryService: WarehouseInventoryService,
    private readonly scheduling: SchedulingReadFacade,
    private readonly handoverService: HandoverService,
  ) {}

  @Get()
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'List warehouse inventory' })
  findAll(@Query() filter: FilterWarehouseInventoryDto) {
    return this.inventoryService.findAll(filter);
  }

  @Get('ready-for-loading')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'List inventory ready for loading' })
  findReadyForLoading(@Query() filter: FilterWarehouseInventoryDto) {
    return this.inventoryService.findReadyForLoading(filter);
  }

  @Get('inquiry')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Locate any item inside the warehouse' })
  inquiry(@Query() filter: InquiryWarehouseInventoryDto) {
    return this.inventoryService.inquiry(filter);
  }

  @Get('arrival-queue')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Arrived bookings awaiting unload / inspection' })
  arrivalQueue() {
    return this.inventoryService.arrivalQueue();
  }

  @Get('ops-stats')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'At-a-glance warehouse ops counters for the KPI strip' })
  opsStats() {
    return this.inventoryService.opsStats();
  }

  @Get('trucks-on-site')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({
    summary: 'Trucks currently in the yard (customer self-haul + EDR last-mile)',
  })
  trucksOnSite() {
    return this.inventoryService.trucksOnSite();
  }

  @Get('zone-occupancy')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Live occupancy per zone (rated capacity vs held) — heatmap data' })
  zoneOccupancy(@Query('yardId') yardId?: string) {
    return this.inventoryService.zoneOccupancy(yardId);
  }

  @Get('throughput')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Received-vs-dispatched throughput time series (week/month/year)' })
  throughput(@Query('granularity') granularity?: string) {
    const g = granularity === 'week' || granularity === 'year' ? granularity : 'month';
    return this.inventoryService.throughput(g);
  }

  @Get('dwell-stats')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Dwell time of in-warehouse items: average + aging buckets' })
  dwellStats() {
    return this.inventoryService.dwellStats();
  }

  @Get('cycle-stats')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Average stage cycle times over recently dispatched items' })
  cycleStats() {
    return this.inventoryService.cycleStats();
  }

  @Get('gate-stats')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Gate/dock throughput: cleared today, turnaround, hourly clearances' })
  gateStats() {
    return this.inventoryService.gateStats();
  }

  @Post('auto-unload-arrived')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.unload)
  @ApiOperation({ summary: 'Bulk auto-unload all arrived bookings into the warehouse' })
  autoUnloadArrived() {
    return this.inventoryService.autoUnloadArrived();
  }

  @Post('auto-load-ready')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.load)
  @ApiOperation({ summary: 'Auto-load READY_FOR_LOADING inventory with PAID bookings' })
  autoLoadReady() {
    return this.inventoryService.autoLoadReady();
  }

  @Get('eligible-bookings')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'PAID bookings not yet received, classified IMPORT/EXPORT by route; omit direction for all' })
  eligibleBookings(@Query('direction') direction?: string) {
    const dir = direction === 'IMPORT' || direction === 'EXPORT' ? direction : undefined;
    return this.inventoryService.eligibleBookings(dir);
  }

  @Post('receive-bulk')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.receive)
  @ApiOperation({ summary: 'Bulk-receive selected eligible PAID bookings into a location' })
  receiveBulk(@Body() dto: BulkReceiveDto, @CurrentUser() user: TCurrentUser) {
    dto.performedBy = actorLabel(user) ?? dto.performedBy;
    return this.inventoryService.bulkReceive(dto);
  }


  @Get('ready-to-load-export')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'EXPORT inventory that passed inspection and is READY_FOR_LOADING' })
  readyToLoadExport() {
    return this.inventoryService.readyToLoadExport();
  }

  @Get('received-export')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'EXPORT inventory that has been received and is awaiting inspection' })
  receivedExport() {
    return this.inventoryService.receivedExport();
  }

  @Get('loaded-export')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'EXPORT inventory that is LOADED and queued for dispatch' })
  loadedExport() {
    return this.inventoryService.loadedExport();
  }

  @Get('loadable-trains')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'EXPORT trains (pre-dispatch) with inventory waiting to be loaded' })
  loadableTrains() {
    return this.inventoryService.loadableTrains();
  }

  @Get('train/:scheduleId/loadable-items')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Container/cargo inventory assigned to a train, with allocated wagons' })
  trainLoadableItems(@Param('scheduleId', ParseUUIDPipe) scheduleId: string) {
    return this.inventoryService.trainLoadableItems(scheduleId);
  }

  @Post('train/:scheduleId/load')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.load)
  @ApiOperation({ summary: 'Load selected inventory items onto their allocated wagons for a train' })
  loadItemsOntoTrain(
    @Param('scheduleId', ParseUUIDPipe) scheduleId: string,
    @Body() dto: { inventoryIds: string[]; performedBy?: string },
    @CurrentUser() user: TCurrentUser,
  ) {
    return this.inventoryService.loadItemsOntoTrain(scheduleId, dto.inventoryIds ?? [], actorLabel(user) ?? dto.performedBy);
  }

  @Post('bulk-dispatch-export')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.dispatch)
  @ApiOperation({ summary: 'Bulk-dispatch loaded EXPORT inventory (LOADED → DISPATCHED)' })
  bulkDispatchExport(@Body() dto: { inventoryIds: string[]; performedBy?: string }, @CurrentUser() user: TCurrentUser) {
    return this.inventoryService.bulkDispatchExport(dto.inventoryIds ?? [], actorLabel(user) ?? dto.performedBy);
  }

  @Post('bulk-mark-inspected')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.inspect)
  @ApiOperation({ summary: 'Bulk mark received inventory inspection PASSED (EXPORT → READY_FOR_LOADING)' })
  bulkMarkInspected(@Body() dto: BulkInspectDto) {
    return this.inventoryService.bulkMarkInspected(dto);
  }

  @Post('bookings/:bookingId/unload')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.unload)
  @ApiOperation({ summary: 'Unload a single arrived booking into a location' })
  unloadBooking(
    @Param('bookingId', ParseUUIDPipe) bookingId: string,
    @Body() dto: UnloadBookingDto,
  ) {
    return this.inventoryService.unloadBooking(bookingId, dto);
  }

  @Post(':id/gate-clearance')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.gatePass)
  @ApiOperation({ summary: 'Final terminal release / gate clearance (blocked while fees unpaid)' })
  gateClearance(
    @Param('id', ParseUUIDPipe) id: string,
    @Body('performedBy') performedBy: string | undefined,
    @CurrentUser() user: TCurrentUser,
  ) {
    return this.inventoryService.gateClearance(id, actorLabel(user) ?? performedBy);
  }

  @Get('import/arrive-queue')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Arrived IMPORT train schedules (route-derived), read-only from scheduling' })
  importArriveQueue() {
    return this.scheduling.importArriveQueue();
  }

  @Get('import/trains/:scheduleId/items')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Assigned bookings/items for an arrived import train (read-only)' })
  importTrainDetail(@Param('scheduleId', ParseUUIDPipe) scheduleId: string) {
    return this.scheduling.importTrainDetail(scheduleId);
  }

  @Post('import/auto-unload-arrived-bookings')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.unload)
  @ApiOperation({ summary: 'Unload all eligible assigned bookings of an ARRIVED import train (→ UNLOADED)' })
  autoUnloadArrivedBookings(@Body() dto: {
    scheduleId: string;
    warehouseId?: string;
    performedBy?: string;
    assignments?: { bookingId: string; warehouseId: string; yardId: string; zoneId: string }[];
  }, @CurrentUser() user: TCurrentUser) {
    return this.inventoryService.autoUnloadArrivedBookings(
      dto.scheduleId,
      actorLabel(user) ?? dto.performedBy,
      dto.warehouseId,
      dto.assignments,
    );
  }

  @Get('import/unloaded-queue')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'IMPORT inventory in the Unloaded Queue (UNLOADED / destination inspection)' })
  importUnloadedQueue() {
    return this.inventoryService.importUnloadedQueue();
  }

  @Get('export/djibouti-arrival-queue')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Arrived EXPORT train schedules at Djibouti-side ports, ready for unloading' })
  exportDjiboutiArrivalQueue(
    @Query('scheduleId') scheduleId?: string,
    @Query('destination') destination?: string,
    @Query('status') status?: string,
    @Query('dateFrom') dateFrom?: string,
    @Query('dateTo') dateTo?: string,
  ) {
    return this.scheduling.exportDjiboutiArrivalQueue({
      scheduleId,
      destination,
      status,
      dateFrom,
      dateTo,
    });
  }

  @Get('export/djibouti-trains/:scheduleId/items')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Assigned export bookings/items for an arrived Djibouti-side train' })
  exportDjiboutiTrainDetail(@Param('scheduleId', ParseUUIDPipe) scheduleId: string) {
    return this.scheduling.exportDjiboutiTrainDetail(scheduleId);
  }

  @Post('export/auto-unload-at-djibouti')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.unload)
  @ApiOperation({ summary: 'Unload all eligible export items assigned to an arrived Djibouti-side train' })
  autoUnloadExportAtDjibouti(@Body() dto: { scheduleId: string; performedBy?: string }, @CurrentUser() user: TCurrentUser) {
    return this.inventoryService.autoUnloadExportAtDjibouti(dto.scheduleId, actorLabel(user) ?? dto.performedBy);
  }

  @Get('import/pickup-ready-queue')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'IMPORT inventory that is PICKUP_READY (READY_FOR_PICKUP) awaiting pickup/dispatch' })
  importPickupReadyQueue() {
    return this.inventoryService.importPickupReadyQueue();
  }

  @Get('loadable-wagons')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'List wagons usable for loading (read-only from scheduling)' })
  loadableWagons() {
    return this.scheduling.listLoadableWagons();
  }

  @Get('booking/:bookingId/schedule')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Read-only schedule + wagon + departure status for a booking' })
  bookingSchedule(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
    return this.scheduling.getBookingSchedule(bookingId);
  }

  @Post('receive')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.receive)
  @ApiOperation({ summary: 'Receive inventory at a warehouse location' })
  receive(@Body() dto: ReceiveWarehouseInventoryDto, @CurrentUser() user: TCurrentUser) {
    dto.performedBy = actorLabel(user) ?? dto.performedBy;
    return this.inventoryService.receive(dto);
  }

  @Post('reserve')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.move)
  @ApiOperation({ summary: 'Reserve stored inventory for a PAID booking' })
  reserve(@Body() dto: ReserveInventoryDto, @CurrentUser() user: TCurrentUser) {
    dto.performedBy = actorLabel(user) ?? dto.performedBy;
    return this.inventoryService.reserve(dto);
  }

  @Get(':id/movements')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Inventory movement history' })
  movements(@Param('id', ParseUUIDPipe) id: string) {
    return this.inventoryService.findMovements(id);
  }

  @Get(':id/activity')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Inventory activity log' })
  activity(@Param('id', ParseUUIDPipe) id: string) {
    return this.inventoryService.findActivity(id);
  }

  @Get(':id/loadings')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Loading records for an inventory item' })
  loadings(@Param('id', ParseUUIDPipe) id: string) {
    return this.inventoryService.findLoadingsByInventory(id);
  }

  @Post(':id/move')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.move)
  @ApiOperation({ summary: 'Move inventory to another warehouse/yard/zone' })
  move(@Param('id', ParseUUIDPipe) id: string, @Body() dto: MoveInventoryDto) {
    return this.inventoryService.move(id, dto);
  }

  @Post(':id/store')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.move)
  @ApiOperation({ summary: 'Mark received inventory as STORED (optional explicit warehouse/yard/zone)' })
  store(@Param('id', ParseUUIDPipe) id: string, @Body() dto: StoreInventoryDto, @CurrentUser() user: TCurrentUser) {
    return this.inventoryService.store(id, actorLabel(user) ?? dto.performedBy, dto);
  }

  @Post(':id/ready-for-loading')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.move)
  @ApiOperation({ summary: 'Mark reserved inventory READY_FOR_LOADING' })
  readyForLoading(
    @Param('id', ParseUUIDPipe) id: string,
    @Body('performedBy') performedBy: string | undefined,
    @CurrentUser() user: TCurrentUser,
  ) {
    return this.inventoryService.readyForLoading(id, actorLabel(user) ?? performedBy);
  }

  @Post(':id/load')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.load)
  @ApiOperation({ summary: 'Load READY_FOR_LOADING inventory onto a wagon' })
  load(@Param('id', ParseUUIDPipe) id: string, @Body() dto: LoadInventoryDto) {
    return this.inventoryService.load(id, dto);
  }

  @Post(':id/ready-for-pickup')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.move)
  @ApiOperation({ summary: 'Mark inspected IMPORT inventory READY_FOR_PICKUP' })
  readyForPickup(
    @Param('id', ParseUUIDPipe) id: string,
    @Body('performedBy') performedBy: string | undefined,
    @CurrentUser() user: TCurrentUser,
  ) {
    return this.inventoryService.readyForPickup(id, actorLabel(user) ?? performedBy);
  }

  @Post(':id/release')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.release)
  @ApiOperation({ summary: 'Issue a DO / release order for ready-for-pickup inventory' })
  release(@Param('id', ParseUUIDPipe) id: string, @Body() dto: ReleaseOrderDto) {
    return this.inventoryService.release(id, dto);
  }

  @Get(':id/release-document')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'View warehouse release / exit paper PDF' })
  async releaseDocument(@Param('id', ParseUUIDPipe) id: string, @Res() res: Response) {
    const { filename, buffer } = await this.inventoryService.releaseDocument(id);
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
    res.setHeader('Content-Length', buffer.length);
    return res.send(buffer);
  }

  @Get('customer-truck-exit-paper/:assignmentId')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Per-truck exit paper PDF (containers loaded on one customer truck)' })
  async truckExitPaper(
    @Param('assignmentId', ParseUUIDPipe) assignmentId: string,
    @Res() res: Response,
  ) {
    const { filename, buffer } = await this.inventoryService.truckExitPaper(assignmentId);
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
    res.setHeader('Content-Length', buffer.length);
    return res.send(buffer);
  }

  @Get('edr-truck-exit-paper/:assignmentId')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'Per-truck exit paper PDF for an EDR last-mile truck' })
  async edrTruckExitPaper(
    @Param('assignmentId', ParseUUIDPipe) assignmentId: string,
    @Res() res: Response,
  ) {
    const { filename, buffer } = await this.inventoryService.edrTruckExitPaper(assignmentId);
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
    res.setHeader('Content-Length', buffer.length);
    return res.send(buffer);
  }

  @Get(':id/grn-document')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
  @ApiOperation({ summary: 'View goods received note PDF' })
  async grnDocument(@Param('id', ParseUUIDPipe) id: string, @Res() res: Response) {
    const { filename, buffer } = await this.inventoryService.grnDocument(id);
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
    res.setHeader('Content-Length', buffer.length);
    return res.send(buffer);
  }

  @Get(':id/handover-document')
  @StaffReference()
  @ApiOperation({ summary: 'View import goods handover document PDF' })
  async handoverDocument(@Param('id', ParseUUIDPipe) id: string, @Res() res: Response) {
    const { filename, buffer } = await this.inventoryService.handoverDocument(id);
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
    res.setHeader('Content-Length', buffer.length);
    return res.send(buffer);
  }

  @Post('bookings/:bookingId/approve-delivery')
  @StaffReference()
  @ApiOperation({ summary: "Approve delivery — customer records their full name (signature optional)" })
  approveDeliveryForBooking(
    @Param('bookingId', ParseUUIDPipe) bookingId: string,
    @Body() dto: ApproveDeliveryDto,
    @Request() req: { user?: { id?: string; sub?: string } },
    @CurrentUser() user: TCurrentUser,
  ) {
    return this.inventoryService.approveDeliveryForBooking(
      bookingId,
      user?.id ?? req.user?.id ?? req.user?.sub,
      dto.signerName,
    );
  }

  @Get('bookings/:bookingId/handovers')
  @StaffReference()
  @ApiOperation({ summary: 'Handover records for a booking (per-booking or per-truck)' })
  bookingHandovers(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
    return this.handoverService.list(bookingId);
  }

  @Post('handovers/:handoverId/sign')
  @StaffReference()
  @ApiOperation({ summary: 'Customer signs one handover (EDR last-mile: one signature per truck)' })
  signHandover(
    @Param('handoverId', ParseUUIDPipe) handoverId: string,
    @Body() dto: ApproveDeliveryDto,
    @Request() req: { user?: { id?: string; sub?: string } },
    @CurrentUser() user: TCurrentUser,
  ) {
    return this.inventoryService.signHandover(
      handoverId,
      user?.id ?? req.user?.id ?? req.user?.sub,
      dto.signerName,
    );
  }

  @Post('bookings/:bookingId/request-handover-signature')
  @StaffReference()
  @ApiOperation({ summary: 'Ask the customer to sign the handover (creates one if none, then notifies)' })
  requestHandoverSignature(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
    return this.handoverService.requestSignature(bookingId);
  }

  @Get('bookings/:bookingId/grn-document')
  @StaffReference()
  @ApiOperation({ summary: 'View GRN PDF for a booking (customer portal)' })
  async bookingGrnDocument(@Param('bookingId', ParseUUIDPipe) bookingId: string, @Res() res: Response) {
    const { filename, buffer } = await this.inventoryService.grnDocumentForBooking(bookingId);
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
    res.setHeader('Content-Length', buffer.length);
    return res.send(buffer);
  }

  @Get('bookings/:bookingId/release-document')
  @StaffReference()
  @ApiOperation({ summary: 'View gate-clearance / release-order PDF for a booking (customer portal)' })
  async bookingReleaseDocument(@Param('bookingId', ParseUUIDPipe) bookingId: string, @Res() res: Response) {
    const { filename, buffer } = await this.inventoryService.releaseDocumentForBooking(bookingId);
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
    res.setHeader('Content-Length', buffer.length);
    return res.send(buffer);
  }

  @Get('bookings/:bookingId/handover-document')
  @StaffReference()
  @ApiOperation({ summary: 'View import goods handover document PDF (resolved by booking; ?handoverId= for the per-truck variant)' })
  async bookingHandoverDocument(
    @Param('bookingId', ParseUUIDPipe) bookingId: string,
    @Res() res: Response,
    @Query('handoverId') handoverId?: string,
  ) {
    const { filename, buffer } = await this.inventoryService.handoverDocumentForBooking(
      bookingId,
      handoverId || undefined,
    );
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
    res.setHeader('Content-Length', buffer.length);
    return res.send(buffer);
  }

  @Patch('bookings/:bookingId/double-handling')
  @BookingStaff([FREIGHT_PERMS.warehouseInventory.unload, FREIGHT_PERMS.warehouseInventory.inspect])
  @ApiOperation({
    summary: 'Record Yes/No double handling after unloading (Yes applies the double-handling fee rule)',
  })
  setDoubleHandling(
    @Param('bookingId', ParseUUIDPipe) bookingId: string,
    @Body() dto: SetDoubleHandlingDto,
    @CurrentUser() user: TCurrentUser,
  ) {
    return this.inventoryService.setDoubleHandling(
      bookingId,
      dto.doubleHandling,
      actorLabel(user),
    );
  }

  @Get('bookings/:bookingId/container-items')
  @StaffReference()
  @ApiOperation({ summary: 'Per-container/bulk items of a booking with lifecycle stage + refs' })
  containerItems(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
    return this.inventoryService.containerItems(bookingId);
  }

  @Get('bookings/:bookingId/container-weights')
  @StaffReference()
  @ApiOperation({ summary: "A booking's containers + VGM cargo weight (tonnes) for exit weighing" })
  containerWeights(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
    return this.inventoryService.bookingContainerWeights(bookingId);
  }

  @Get('bookings/:bookingId/location')
  @StaffReference()
  @ApiOperation({ summary: "Warehouse location of a booking's inventory (customer portal)" })
  bookingLocation(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
    return this.inventoryService.bookingLocation(bookingId);
  }

  @Post(':id/deliver')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.deliver)
  @ApiOperation({ summary: 'Deliver import goods to the customer + capture proof of delivery' })
  deliver(@Param('id', ParseUUIDPipe) id: string, @Body() dto: DeliverInventoryDto, @CurrentUser() user: TCurrentUser) {
    dto.performedBy = actorLabel(user) ?? dto.performedBy;
    return this.inventoryService.deliver(id, dto);
  }

  @Patch(':id/dispatch')
  @BookingStaff(FREIGHT_PERMS.warehouseInventory.dispatch)
  @ApiOperation({ summary: 'Mark loaded inventory DISPATCHED (left the terminal)' })
  dispatch(
    @Param('id', ParseUUIDPipe) id: string,
    @Body('performedBy') performedBy: string | undefined,
    @CurrentUser() user: TCurrentUser,
  ) {
    return this.inventoryService.dispatch(id, actorLabel(user) ?? performedBy);
  }
}
