import {
  Body,
  Controller,
  Delete,
  Get,
  HttpCode,
  Param,
  ParseUUIDPipe,
  Patch,
  Post,
  Query,
  Request,
  Res,
  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 {
  ApproveStepDto,
  CancelBookingDto,
  RejectStepDto,
  RequestChangesDto,
  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';

@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 })
  create(
    @Body() dto: CreateBookingDto,
    @UploadedFiles() files: Express.Multer.File[],
    @Request() req: { user?: { id?: string; sub?: string } },
  ) {
    const userId = req.user?.id ?? req.user?.sub;
    return this.bookingsService.create(dto, files ?? [], userId);
  }

  @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)' })
  findAll(@Query() filter: FilterBookingDto) {
    return this.bookingsService.findAll(filter);
  }

  @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('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) {
    const booking = await this.bookingsService.findByReference(reference);
    return this.transitionService.enrichBookingResponse(booking);
  }

  @Get(':id')
  @ApiOperation({ summary: 'Get booking by ID' })
  async findOne(@Param('id', ParseUUIDPipe) id: string) {
    const booking = await this.bookingsService.findById(id);
    return this.transitionService.enrichBookingResponse(booking);
  }

  @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 only)' })
  @ApiOkResponse({ type: GeneratePriceResponseDto })
  generatePrice(@Param('id', ParseUUIDPipe) id: string) {
    return this.pricingService.generatePrice(id);
  }

  @Post(':id/submit')
  @ApiOperation({ summary: 'Customer submit booking' })
  async submit(@Param('id', ParseUUIDPipe) id: string) {
    const booking = await this.transitionService.submit(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 → start approval chain' })
  async acceptIntake(
    @Param('id', ParseUUIDPipe) id: string,
    @CurrentUser() user: AuthUserPayload,
  ) {
    const booking = await this.transitionService.acceptIntake(
      id,
      resolveAuthUserId(user),
    );
    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/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) {
    return this.contractService.getContractView(id);
  }

  @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);
  }
}
