import {
  Body,
  Controller,
  Delete,
  Get,
  HttpCode,
  Param,
  ParseUUIDPipe,
  Patch,
  Post,
  Query,
  Request,
  Res,
  UnauthorizedException,
  UploadedFiles,
  UseInterceptors,
} from '@nestjs/common';
import { CurrentUser } from '@edr/api-common';
import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type';
import { BookingStaff } from '../../common/booking-guards';
import { FREIGHT_PERMS } from '../../seed/freight-permissions.registry';
import { AnyFilesInterceptor } from '@nestjs/platform-express';
import {
  ApiBearerAuth,
  ApiBody,
  ApiConsumes,
  ApiOkResponse,
  ApiOperation,
  ApiTags,
} from '@nestjs/swagger';
import type { Response } from 'express';

import { BookingContractService } from './booking-contract.service';
import { BookingPricingService } from './booking-pricing.service';
import { BookingTransitionService } from './booking-transition.service';
import { BookingReferenceDataService } from './booking-reference-data.service';
import { BookingsService } from './bookings.service';
import { BookingReferenceDataDto } from './dto/booking-reference-data.dto';
import { CreateBookingDto } from './dto/create-booking.dto';
import { BookingListSummaryDto } from './dto/booking-list-summary.dto';
import { FilterBookingDto } from './dto/filter-booking.dto';
import { GeneratePriceResponseDto } from './dto/generate-price-response.dto';
import { SubmitBookingResponseDto } from './dto/submit-booking-response.dto';
import {
  AcceptIntakeDto,
  AdjustPriceDto,
  ApproveStepDto,
  CancelBookingDto,
  RejectBookingDto,
  RejectStepDto,
  RequestChangesDto,
  ReviewDocumentDto,
  RequestOperationDto,
  OperationReviewDto,
  ConfirmOperationPriceDto,
  StaffRejectDto,
} from './dto/request-changes.dto';
import { ContractViewDto } from './dto/contract-view.dto';
import { SignContractDto } from './dto/sign-contract.dto';
import { UpdateBookingDto } from './dto/update-booking.dto';
import {
  type AuthUserPayload,
  resolveAuthUserId,
} from '../../common/resolve-auth-user-id';
import { assertFreightPermission, hasFreightPermission } from '../../common/freight-permission.util';

@ApiTags('bookings')
@Controller('bookings')
@ApiBearerAuth()
export class BookingsController {
  constructor(
    private readonly bookingsService: BookingsService,
    private readonly bookingReferenceDataService: BookingReferenceDataService,
    private readonly pricingService: BookingPricingService,
    private readonly transitionService: BookingTransitionService,
    private readonly contractService: BookingContractService,
  ) {}

  @Post()
  @UseInterceptors(AnyFilesInterceptor())
  @ApiConsumes('multipart/form-data')
  @ApiOperation({ summary: 'Create a new freight booking (DRAFT)' })
  @ApiBody({ type: CreateBookingDto })
  async create(
    @Body() dto: CreateBookingDto,
    @UploadedFiles() files: Express.Multer.File[],
    @CurrentUser() user: TCurrentUser,
  ) {
    if (dto.isGovernment) {
      assertFreightPermission(user, FREIGHT_PERMS.bookings.staffAccept);
    }
    const result = await this.bookingsService.create(dto, files ?? [], user?.id);

    // Staff-created commercial bookings skip the draft stage: auto generate-price + submit.
    const isStaff = hasFreightPermission(user, FREIGHT_PERMS.bookings.staffAccept);
    if (isStaff && !dto.isGovernment) {
      try {
        await this.pricingService.generatePrice(result.booking.id);
        await this.transitionService.submit(result.booking.id);
        const submitted = await this.bookingsService.findById(result.booking.id);
        return { booking: submitted, warnings: result.warnings };
      } catch {
        // If auto-pricing/submit fails, fall back to the DRAFT so staff can finish manually.
        return result;
      }
    }
    return result;
  }

  @Patch(':id')
  @UseInterceptors(AnyFilesInterceptor())
  @ApiConsumes('multipart/form-data')
  @ApiOperation({
    summary: 'Update booking',
    description: 'Allowed when status is DRAFT or CHANGES_REQUESTED.',
  })
  @ApiBody({ type: UpdateBookingDto })
  update(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: UpdateBookingDto,
    @UploadedFiles() files: Express.Multer.File[],
  ) {
    return this.bookingsService.update(id, dto, files ?? []);
  }

  @Get()
  @ApiOperation({ summary: 'List freight bookings (paginated)' })
  async findAll(
    @Query() filter: FilterBookingDto,
    @CurrentUser() user: TCurrentUser,
  ) {
    // Staff (backoffice) see every booking. Customers (portal) are always
    // force-scoped to their own company, regardless of any companyId they pass.
    if (hasFreightPermission(user, FREIGHT_PERMS.bookings.view)) {
      return this.bookingsService.findAll(filter);
    }
    const userId = user?.id;
    if (!userId) throw new UnauthorizedException('Authentication required');
    const companyId =
      await this.bookingsService.resolveCustomerCompanyId(userId);
    // No linked company yet → no bookings to show (avoids leaking all bookings).
    if (!companyId) {
      const page = filter.page ?? 1;
      const pageSize = filter.pageSize ?? 20;
      return {
        items: [],
        total: 0,
        meta: {
          page,
          pageSize,
          total: 0,
          totalPages: 0,
          hasNextPage: false,
          hasPreviousPage: false,
        },
      };
    }
    // Company-wide by default; the optional filter.companyProfileId (per-page
    // service filter) narrows within the company. The company guard always
    // applies, so a customer can only ever see their own company's bookings.
    return this.bookingsService.findAll(filter, companyId);
  }

  @Get('by-company/:companyId/customer-view')
  @ApiOperation({ summary: 'List bookings for a company (customer-view shape, backoffice)' })
  findByCompanyCustomerView(
    @Param('companyId', ParseUUIDPipe) companyId: string,
  ) {
    return this.bookingsService.findCustomerBookings(companyId);
  }

  @Get('list-summary')
  @ApiOperation({ summary: 'Booking list metrics and tab counts (backoffice)' })
  @ApiOkResponse({ type: BookingListSummaryDto })
  findListSummary(@Query() filter: FilterBookingDto) {
    return this.bookingsService.getListSummary(filter);
  }

  @Get('my')
  @ApiOperation({
    summary: "List the current customer's bookings ready for payment",
    description:
      'Bookings owned by the authenticated user\'s company that are payable ' +
      '(FULLY_EXECUTED, SELECTED_FOR_BATCH, AWAITING_PAYMENT) and not yet PAID.',
  })
  findMyPayable(
    @CurrentUser() user: AuthUserPayload,
    @Query() filter: FilterBookingDto,
  ) {
    return this.bookingsService.findMyPayable(resolveAuthUserId(user), filter);
  }

  @Get('queues/:queue')
  @ApiOperation({
    summary: 'List bookings for a dashboard queue',
    description: 'Queues: intake, approval, signatures, marketing, finance',
  })
  findQueue(
    @Param('queue') queue: string,
    @Query() filter: FilterBookingDto,
    @Query('excludeBulk') excludeBulk?: string,
  ) {
    return this.bookingsService.findQueue(queue, filter, {
      excludeBulk: excludeBulk === 'true',
    });
  }

  @Get('reference-data')
  @ApiOperation({ summary: 'Booking form catalog' })
  @ApiOkResponse({ type: BookingReferenceDataDto })
  getReferenceData(): Promise<BookingReferenceDataDto> {
    return this.bookingReferenceDataService.getReferenceData();
  }

  @Get('by-reference/:reference')
  @ApiOperation({ summary: 'Get booking by reference' })
  async findByReference(
    @Param('reference') reference: string,
    @CurrentUser() user: TCurrentUser,
  ) {
    const booking = await this.bookingsService.findByReference(reference);
    // Staff see any booking; customers only their own company's.
    if (!hasFreightPermission(user, FREIGHT_PERMS.bookings.view)) {
      await this.bookingsService.assertCustomerCanAccessBooking(
        user?.id,
        booking,
      );
    }
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Get(':id')
  @ApiOperation({ summary: 'Get booking by ID' })
  async findOne(
    @Param('id', ParseUUIDPipe) id: string,
    @CurrentUser() user: TCurrentUser,
  ) {
    const booking = await this.bookingsService.findById(id);
    // Staff see any booking; customers only their own company's.
    if (!hasFreightPermission(user, FREIGHT_PERMS.bookings.view)) {
      await this.bookingsService.assertCustomerCanAccessBooking(
        user?.id,
        booking,
      );
    }
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Get(':id/tracking')
  @ApiOperation({
    summary: 'Shipment tracking timeline for a booking',
    description:
      "Returns the booking's consignment (once dispatched) and its ordered " +
      'tracking events. Scoped to the customer\'s own company.',
  })
  async findTracking(
    @Param('id', ParseUUIDPipe) id: string,
    @CurrentUser() user: TCurrentUser,
  ) {
    const booking = await this.bookingsService.findById(id);
    // Staff see any booking; customers only their own company's.
    if (!hasFreightPermission(user, FREIGHT_PERMS.bookings.view)) {
      await this.bookingsService.assertCustomerCanAccessBooking(
        user?.id,
        booking,
      );
    }
    return this.bookingsService.getBookingTracking(id);
  }

  @Delete(':id')
  @HttpCode(204)
  @ApiOperation({ summary: 'Soft-delete DRAFT booking' })
  remove(@Param('id', ParseUUIDPipe) id: string) {
    return this.bookingsService.remove(id);
  }

  @Post(':id/documents')
  @UseInterceptors(AnyFilesInterceptor())
  @ApiConsumes('multipart/form-data')
  @ApiOperation({ summary: 'Upload documents for a booking (DRAFT only)' })
  async uploadDocuments(
    @Param('id', ParseUUIDPipe) id: string,
    @UploadedFiles() files: Express.Multer.File[],
  ) {
    const booking = await this.bookingsService.uploadDocuments(id, files ?? []);
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/generate-price')
  @ApiOperation({
    summary: 'Generate price preview (DRAFT or CHANGES_REQUESTED)',
    description:
      'Computes and stores a price preview on the booking. Does not create rate snapshots.',
  })
  @ApiOkResponse({ type: GeneratePriceResponseDto })
  generatePrice(@Param('id', ParseUUIDPipe) id: string) {
    return this.pricingService.generatePrice(id);
  }

  @Post(':id/submit')
  @ApiOperation({
    summary: 'Customer submit booking',
    description:
      'Recomputes price against live rates. If unchanged, creates rate snapshots and submits. If changed, updates the booking price and returns priceChanged=true for confirmation.',
  })
  @ApiOkResponse({ type: SubmitBookingResponseDto })
  submit(@Param('id', ParseUUIDPipe) id: string) {
    return this.transitionService.submit(id);
  }

  @Post(':id/confirm-submit')
  @ApiOperation({
    summary: 'Confirm submit after price change',
    description:
      'Creates rate snapshots for the updated booking price and moves the booking to SUBMITTED.',
  })
  @ApiOkResponse({ type: SubmitBookingResponseDto })
  confirmSubmit(@Param('id', ParseUUIDPipe) id: string) {
    return this.transitionService.confirmSubmit(id);
  }

  @Post(':id/reject')
  @ApiOperation({
    summary: 'Customer reject price estimate',
    description:
      'Customer rejects the priced booking at the confirm step. The booking becomes REJECTED (terminal); the customer must create a new booking.',
  })
  async reject(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: RejectBookingDto,
  ) {
    const booking = await this.transitionService.reject(id, dto.reason);
    return this.transitionService.enrichBookingResponse(booking);
  }

  // ── Document clearance (post counter-sign) ────────────────────────────────

  @Get(':id/clearance')
  @ApiOperation({
    summary: 'Document-clearance grid (required docs + upload + GL review status)',
  })
  getClearance(@Param('id', ParseUUIDPipe) id: string) {
    return this.transitionService.getClearanceView(id);
  }

  @Post(':id/clearance/documents')
  @UseInterceptors(AnyFilesInterceptor())
  @ApiConsumes('multipart/form-data')
  @ApiOperation({
    summary: 'Customer uploads clearance documents (fieldname = document key)',
  })
  async submitClearanceDocuments(
    @Param('id', ParseUUIDPipe) id: string,
    @UploadedFiles() files: Express.Multer.File[],
  ) {
    const booking = await this.transitionService.submitClearanceDocuments(
      id,
      files ?? [],
    );
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/clearance/proceed')
  @ApiOperation({
    summary:
      'Customer requests operation with a schedule day ' +
      '(CLEARANCE_READY | OPERATION_CHANGES_REQUESTED → OPERATION_REQUEST_PENDING)',
  })
  async proceedToOperation(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: RequestOperationDto,
  ) {
    const booking = await this.transitionService.requestOperation(
      id,
      dto.scheduledDate,
    );
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/operation/review')
  @BookingStaff(FREIGHT_PERMS.bookings.operations)
  @ApiOperation({
    summary:
      'Operations reviews an operation request: ACCEPT (→ batch pool), ' +
      'REQUEST_CHANGES (→ back to customer), or ADJUST_PRICE (→ customer re-confirm)',
  })
  async reviewOperationRequest(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: OperationReviewDto,
    @CurrentUser() user: AuthUserPayload,
  ) {
    const booking = await this.transitionService.reviewOperationRequest(
      id,
      dto.decision,
      resolveAuthUserId(user),
      { note: dto.note, amount: dto.amount },
    );
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/operation/confirm-price')
  @ApiOperation({
    summary:
      'Customer confirms or rejects an operations price adjustment ' +
      '(OPERATION_PRICE_PENDING_CONFIRM → batch pool | OPERATION_CHANGES_REQUESTED)',
  })
  async confirmOperationPrice(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: ConfirmOperationPriceDto,
  ) {
    const booking = await this.transitionService.confirmOperationPrice(
      id,
      dto.accept,
    );
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/clearance/review')
  @BookingStaff(FREIGHT_PERMS.bookings.reviewDocuments)
  @ApiOperation({ summary: 'GL reviews a clearance document (Approve | Query)' })
  async reviewClearanceDocument(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: ReviewDocumentDto,
    @CurrentUser() user: AuthUserPayload,
  ) {
    const booking = await this.transitionService.reviewDocument(
      id,
      dto.fileKey,
      dto.status,
      resolveAuthUserId(user),
      dto.note,
    );
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/clearance/output-documents')
  @BookingStaff(FREIGHT_PERMS.bookings.uploadClearanceOutput)
  @UseInterceptors(AnyFilesInterceptor())
  @ApiConsumes('multipart/form-data')
  @ApiOperation({ summary: 'GL uploads customs output documents (IM4/EX3/…)' })
  async uploadClearanceOutput(
    @Param('id', ParseUUIDPipe) id: string,
    @UploadedFiles() files: Express.Multer.File[],
  ) {
    const booking = await this.transitionService.uploadClearanceOutputDocuments(
      id,
      files ?? [],
    );
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/clearance/finalize')
  @BookingStaff(FREIGHT_PERMS.bookings.finalizeClearance)
  @ApiOperation({
    summary: 'GL finalizes clearance (requires 100% approved) → CLEARANCE_READY',
  })
  async finalizeClearance(@Param('id', ParseUUIDPipe) id: string) {
    const booking = await this.transitionService.finalizeClearance(id);
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/staff/request-changes')
  @BookingStaff(FREIGHT_PERMS.bookings.requestChanges)
  @ApiOperation({ summary: 'Staff return booking for customer updates' })
  async requestChanges(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: RequestChangesDto,
    @CurrentUser() user: AuthUserPayload,
  ) {
    const booking = await this.transitionService.requestChanges(
      id,
      dto.note,
      resolveAuthUserId(user),
    );
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/staff/accept')
  @BookingStaff(FREIGHT_PERMS.bookings.staffAccept)
  @ApiOperation({
    summary:
      'Staff accept intake → set contract validity window + start approval chain',
  })
  async acceptIntake(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: AcceptIntakeDto,
    @CurrentUser() user: AuthUserPayload,
  ) {
    const booking = await this.transitionService.acceptIntake(
      id,
      resolveAuthUserId(user),
      dto.validityDays,
    );
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/staff/reject')
  @BookingStaff(FREIGHT_PERMS.bookings.reject)
  @ApiOperation({ summary: 'Staff final reject' })
  async staffReject(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: StaffRejectDto,
    @CurrentUser() user: AuthUserPayload,
  ) {
    const booking = await this.transitionService.staffReject(
      id,
      dto.reason,
      resolveAuthUserId(user),
    );
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/adjust-price')
  @BookingStaff(FREIGHT_PERMS.bookings.staffAccept)
  @ApiOperation({
    summary: 'Staff adjust booking total price (override; null clears it)',
  })
  async adjustPrice(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: AdjustPriceDto,
    @CurrentUser() user: AuthUserPayload,
  ) {
    const booking = await this.transitionService.adjustPrice(
      id,
      dto.amount ?? null,
      resolveAuthUserId(user),
      dto.reason,
    );
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/government-expedite')
  @BookingStaff(FREIGHT_PERMS.bookings.staffAccept)
  @ApiOperation({ summary: 'Expedite government booking to PAID / ELIGIBLE for scheduling' })
  async governmentExpedite(
    @Param('id', ParseUUIDPipe) id: string,
    @CurrentUser() user: AuthUserPayload,
  ) {
    const booking = await this.bookingsService.governmentExpedite(
      id,
      resolveAuthUserId(user),
    );
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/approval-steps/:stepId/approve')
  @BookingStaff([
    FREIGHT_PERMS.bookings.approveLineStaff,
    FREIGHT_PERMS.bookings.approveDirector,
    FREIGHT_PERMS.bookings.approveCeo,
  ])
  @ApiOperation({ summary: 'Approve one approval step in sequence' })
  async approveStep(
    @Param('id', ParseUUIDPipe) id: string,
    @Param('stepId', ParseUUIDPipe) stepId: string,
    @Body() dto: ApproveStepDto,
    @CurrentUser() user: TCurrentUser,
  ) {
    const booking = await this.transitionService.approveStep(
      id,
      stepId,
      resolveAuthUserId(user),
      dto.requiredRole,
      user,
    );
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/approval-steps/:stepId/reject')
  @BookingStaff(FREIGHT_PERMS.bookings.rejectApproval)
  @ApiOperation({ summary: 'Reject at approval step' })
  async rejectStep(
    @Param('id', ParseUUIDPipe) id: string,
    @Param('stepId', ParseUUIDPipe) stepId: string,
    @Body() dto: RejectStepDto,
    @CurrentUser() user: AuthUserPayload,
  ) {
    const booking = await this.transitionService.rejectStep(
      id,
      stepId,
      resolveAuthUserId(user),
      dto.reason,
    );
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/contract/generate')
  @BookingStaff(FREIGHT_PERMS.bookings.generateContract)
  @ApiOperation({ summary: 'Generate contract PDF from template' })
  async generateContract(@Param('id', ParseUUIDPipe) id: string) {
    const booking = await this.contractService.generateContract(id);
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Get(':id/contract/view')
  @ApiOkResponse({ type: ContractViewDto })
  @ApiOperation({ summary: 'Contract HTML view for portal and backoffice' })
  getContractView(
    @Param('id', ParseUUIDPipe) id: string,
    @Request() req: { user?: { id?: string; sub?: string } },
  ) {
    const userId = req.user?.id ?? req.user?.sub;
    return this.contractService.getContractView(id, userId);
  }

  @Get(':id/contract/document')
  @ApiOperation({ summary: 'Download contract PDF' })
  async downloadContractDocument(
    @Param('id', ParseUUIDPipe) id: string,
    @Res() res: Response,
  ): Promise<void> {
    const { stream, record } = await this.contractService.streamContract(id);
    res.setHeader('Content-Type', record.mimeType ?? 'application/pdf');
    res.setHeader(
      'Content-Disposition',
      `attachment; filename="${record.name}"`,
    );
    stream.pipe(res);
  }

  @Get(':id/contract')
  @ApiOperation({ summary: 'Download contract file (alias)' })
  async downloadContract(
    @Param('id', ParseUUIDPipe) id: string,
    @Res() res: Response,
  ): Promise<void> {
    return this.downloadContractDocument(id, res);
  }

  @Post(':id/contract/sign')
  @ApiOperation({ summary: 'Apply digital signature (customer or staff)' })
  async signContract(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: SignContractDto,
    @Request() req: { user?: { id?: string; sub?: string }; ip?: string },
  ) {
    const userId = req.user?.id ?? req.user?.sub;
    const booking = await this.contractService.signContract(id, dto, {
      signerUserId: userId,
      ipAddress: req.ip,
    });
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Get(':id/contract/signatures')
  @ApiOperation({ summary: 'List contract signatures' })
  getContractSignatures(@Param('id', ParseUUIDPipe) id: string) {
    return this.contractService.getSignatures(id);
  }

  @Get(':id/summary')
  @ApiOperation({ summary: 'Contract summary string for dashboard' })
  getSummary(@Param('id', ParseUUIDPipe) id: string) {
    return this.contractService.getSummary(id);
  }

  @Post(':id/customer/sign')
  @ApiOperation({
    summary: 'Customer digital signature (deprecated — use POST contract/sign)',
  })
  async customerSign(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: SignContractDto,
    @Request() req: { user?: { id?: string; sub?: string }; ip?: string },
  ) {
    const payload: SignContractDto = { ...dto, role: 'CUSTOMER' };
    const booking = await this.contractService.signContract(id, payload, {
      signerUserId: req.user?.id ?? req.user?.sub,
      ipAddress: req.ip,
    });
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/marketing/approve')
  @BookingStaff(FREIGHT_PERMS.bookings.signStaff)
  @ApiOperation({
    summary: 'Staff contract signature and fully execute (use contract/sign STAFF preferred)',
  })
  async marketingApprove(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: SignContractDto,
    @CurrentUser() user: AuthUserPayload,
    @Request() req: { ip?: string },
  ) {
    const payload: SignContractDto = {
      ...dto,
      role: 'STAFF',
    };
    const booking = await this.contractService.signContract(id, payload, {
      signerUserId: resolveAuthUserId(user),
      ipAddress: req.ip,
    });
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/operations/start-transit')
  @BookingStaff(FREIGHT_PERMS.bookings.operations)
  @ApiOperation({ summary: 'Mark in transit' })
  async startTransit(@Param('id', ParseUUIDPipe) id: string) {
    const booking = await this.transitionService.startTransit(id);
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/operations/complete')
  @BookingStaff(FREIGHT_PERMS.bookings.operations)
  @ApiOperation({ summary: 'Mark completed' })
  async complete(@Param('id', ParseUUIDPipe) id: string) {
    const booking = await this.transitionService.complete(id);
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/cancel')
  @BookingStaff(FREIGHT_PERMS.bookings.cancel)
  @ApiOperation({ summary: 'Cancel booking' })
  async cancel(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: CancelBookingDto,
  ) {
    const booking = await this.transitionService.cancel(id, dto.reason);
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Post(':id/consolidation')
  @ApiOperation({ summary: 'Request freight consolidation' })
  requestConsolidation(@Param('id', ParseUUIDPipe) id: string) {
    return this.bookingsService.requestConsolidation(id);
  }

  @Delete(':id/consolidation')
  @ApiOperation({ summary: 'Remove consolidation pairing' })
  removeConsolidation(@Param('id', ParseUUIDPipe) id: string) {
    return this.bookingsService.removeConsolidation(id);
  }

  @Get(':id/consolidation')
  @ApiOperation({ summary: 'Get consolidation details' })
  getConsolidationDetails(@Param('id', ParseUUIDPipe) id: string) {
    return this.bookingsService.getConsolidationDetails(id);
  }
}
