import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  ParseUUIDPipe,
  Patch,
  Post,
  Query,
} from "@nestjs/common";
import { CurrentUser } from "@edr/api-common";
import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger";
import type { AuthUserPayload } from "../../common/resolve-auth-user-id";
import { resolveAuthUserId } from "../../common/resolve-auth-user-id";

import {
  TrainSchedulingManage,
  TrainSchedulingView,
} from "../../common/booking-guards";
import { AssignBookingsDto } from "./dto/assign-bookings.dto";
import { AssignUnassignedBookingDto } from "./dto/assign-unassigned-booking.dto";
import { CreateContainerTrainScheduleDto } from "./dto/create-container-train-schedule.dto";
import { GetEligibleBookingsDto } from "./dto/get-eligible-bookings.dto";
import { GetEligibleBulkBookingsDto } from "./dto/get-eligible-bulk-bookings.dto";
import { GetEligibleContainerBookingsDto } from "./dto/get-eligible-container-bookings.dto";
import { PinWagonsDto } from "./dto/pin-wagons.dto";
import { UpdateContainerItemDto } from "./dto/update-container-item.dto";
import { PreviewBulkTrainScheduleDto } from "./dto/preview-bulk-train-schedule.dto";
import { PreviewContainerTrainScheduleDto } from "./dto/preview-container-train-schedule.dto";
import { PreviewTrainScheduleDto } from "./dto/preview-train-schedule.dto";
import { RecordCheckpointDto } from "./dto/record-checkpoint.dto";
import { AvailableLocomotivesQueryDto } from "./dto/available-locomotives-query.dto";
import { BookableSchedulesQueryDto } from "./dto/bookable-schedules-query.dto";
import { AvailableDaysQueryDto } from "./dto/available-days-query.dto";
import { UpdateTrainSchedulingGlobalRulesDto } from "./dto/update-train-scheduling-global-rules.dto";
import { TrainSchedulingService } from "./train-scheduling.service";
import { BookingBatchService } from "./booking-batch.service";

@ApiTags("train-scheduling")
@ApiBearerAuth()
@Controller("train-scheduling")
export class TrainSchedulingController {
  constructor(
    private readonly trainSchedulingService: TrainSchedulingService,
    private readonly bookingBatchService: BookingBatchService,
  ) { }

  @Get("global-rules")
  @TrainSchedulingView()
  @ApiOperation({ summary: "Get global train scheduling rules (singleton)" })
  getGlobalRules() {
    return this.trainSchedulingService.getTrainSchedulingGlobalRules();
  }

  @Patch("global-rules")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Update global train scheduling rules (singleton)" })
  updateGlobalRules(@Body() dto: UpdateTrainSchedulingGlobalRulesDto) {
    return this.trainSchedulingService.updateTrainSchedulingGlobalRules(dto);
  }

  @Get("eligible-bookings")
  @TrainSchedulingView()
  @ApiOperation({ summary: "List eligible bookings (container and/or bulk)" })
  getEligibleBookings(@Query() query: GetEligibleBookingsDto) {
    return this.trainSchedulingService.getEligibleBookings(query);
  }

  @Get("batch-board")
  @TrainSchedulingView()
  @ApiOperation({
    summary: "Batch monitoring board: schedules with bookings grouped by state",
  })
  getBatchBoard() {
    return this.bookingBatchService.getBatchBoard();
  }

  @Get("batch-board/:scheduleId")
  @TrainSchedulingView()
  @ApiOperation({
    summary: "Batch board detail for one schedule with EAT 3h windows",
  })
  getBatchBoardDetail(@Param("scheduleId", ParseUUIDPipe) scheduleId: string) {
    return this.bookingBatchService.getBatchBoardDetail(scheduleId);
  }

  @Get("available-locomotives")
  @TrainSchedulingView()
  @ApiOperation({
    summary: "List AVAILABLE locomotives at the route origin yard",
  })
  getAvailableLocomotives(@Query() query: AvailableLocomotivesQueryDto) {
    return this.trainSchedulingService.getAvailableLocomotivesForRoute(
      query.routeId,
    );
  }

  @Get("bookable-schedules")
  // No staff guard: customers hit this while creating a booking to find OPEN
  // same-route schedules. Do not attach train_scheduling permissions here.
  @ApiOperation({
    summary: "OPEN same-route schedules a new booking can target",
  })
  getBookableSchedules(@Query() query: BookableSchedulesQueryDto) {
    return this.trainSchedulingService.getBookableSchedules(
      query.originYardId,
      query.destinationYardId,
    );
  }

  @Get("available-days")
  // No staff guard: customers hit this while creating a booking to find which
  // DAYS have a departure on their route. Day-level pooling — no capacity is
  // returned, only the list of bookable days.
  @ApiOperation({
    summary: "Distinct days with an OPEN same-route departure (day-level pool)",
  })
  getAvailableDays(@Query() query: AvailableDaysQueryDto) {
    return this.trainSchedulingService.getAvailableDays(
      query.originYardId,
      query.destinationYardId,
    );
  }

  @Get("container/eligible-bookings")
  @TrainSchedulingView()
  @ApiOperation({ summary: "List eligible container bookings" })
  getEligibleContainerBookings(
    @Query() query: GetEligibleContainerBookingsDto,
  ) {
    return this.trainSchedulingService.getEligibleContainerBookings(query);
  }

  @Get("bulk/eligible-bookings")
  @TrainSchedulingView()
  @ApiOperation({ summary: "List eligible bulk bookings" })
  getEligibleBulkBookings(@Query() query: GetEligibleBulkBookingsDto) {
    return this.trainSchedulingService.getEligibleBulkBookings(query);
  }

  @Post("preview")
  @TrainSchedulingView()
  @ApiOperation({ summary: "Preview a mixed-capable train schedule" })
  previewTrainSchedule(@Body() dto: PreviewTrainScheduleDto) {
    return this.trainSchedulingService.previewTrainSchedule(dto);
  }

  @Post("container/preview")
  @TrainSchedulingView()
  @ApiOperation({ summary: "Preview a container train schedule" })
  previewContainerTrainSchedule(@Body() dto: PreviewContainerTrainScheduleDto) {
    return this.trainSchedulingService.previewContainerTrainSchedule(dto);
  }

  @Post("bulk/preview")
  @TrainSchedulingView()
  @ApiOperation({ summary: "Preview a bulk train schedule" })
  previewBulkTrainSchedule(@Body() dto: PreviewBulkTrainScheduleDto) {
    return this.trainSchedulingService.previewBulkTrainSchedule(dto);
  }

  @Post("container/schedules")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Create a container train schedule" })
  createContainerTrainSchedule(@Body() dto: CreateContainerTrainScheduleDto) {
    return this.trainSchedulingService.createContainerTrainSchedule(dto);
  }

  @Post("bulk/schedules")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Create a bulk train schedule" })
  createBulkTrainSchedule(@Body() dto: CreateContainerTrainScheduleDto) {
    return this.trainSchedulingService.createContainerTrainSchedule(dto);
  }

  @Post("schedules/:id/assign-bookings")
  @TrainSchedulingManage()
  @ApiOperation({
    summary: "Assign bookings to a train schedule (mixed-capable)",
  })
  assignBookings(
    @Param("id", ParseUUIDPipe) id: string,
    @Body() dto: AssignBookingsDto,
  ) {
    return this.trainSchedulingService.assignBookingsToSchedule(id, dto);
  }

  @Post("container/schedules/:id/assign-bookings")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Assign container bookings to a train schedule" })
  assignContainerBookings(
    @Param("id", ParseUUIDPipe) id: string,
    @Body() dto: AssignBookingsDto,
  ) {
    return this.trainSchedulingService.assignBookingsToSchedule(
      id,
      dto,
      "CONTAINER",
    );
  }

  @Post("bulk/schedules/:id/assign-bookings")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Assign bulk bookings to a train schedule" })
  assignBulkBookings(
    @Param("id", ParseUUIDPipe) id: string,
    @Body() dto: AssignBookingsDto,
  ) {
    return this.trainSchedulingService.assignBookingsToSchedule(
      id,
      dto,
      "BULK",
    );
  }

  @Delete("schedules/:id/bookings/:bookingId")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Unassign a booking from a train schedule" })
  unassignBooking(
    @Param("id", ParseUUIDPipe) id: string,
    @Param("bookingId", ParseUUIDPipe) bookingId: string,
    @CurrentUser() user: AuthUserPayload,
  ) {
    return this.trainSchedulingService.unassignBooking(
      id,
      bookingId,
      resolveAuthUserId(user),
    );
  }

  @Delete("schedules/:id/wagons/:trainSetWagonId")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Remove an empty wagon slot from a train" })
  removeWagonSlot(
    @Param("id", ParseUUIDPipe) id: string,
    @Param("trainSetWagonId", ParseUUIDPipe) trainSetWagonId: string,
  ) {
    return this.trainSchedulingService.removeTrainSetWagonSlot(
      id,
      trainSetWagonId,
    );
  }

  @Patch("schedules/:id/container-items/:itemId")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Update a container number on a wagon slot" })
  updateContainerItem(
    @Param("id", ParseUUIDPipe) id: string,
    @Param("itemId", ParseUUIDPipe) itemId: string,
    @Body() dto: UpdateContainerItemDto,
  ) {
    return this.trainSchedulingService.updateContainerItem(id, itemId, dto);
  }

  @Get("schedules/:id/unassigned-bookings")
  @TrainSchedulingView()
  @ApiOperation({ summary: "Get unassigned bookings for a schedule" })
  getUnassignedBookings(@Param("id", ParseUUIDPipe) id: string) {
    return this.trainSchedulingService.getUnassignedBookings(id);
  }

  @Post("schedules/:id/assign-unassigned-booking")
  @TrainSchedulingManage()
  @ApiOperation({
    summary:
      "Assign one linked unallocated booking to wagons (preserves existing assignments)",
  })
  assignUnassignedBooking(
    @Param("id", ParseUUIDPipe) id: string,
    @Body() dto: AssignUnassignedBookingDto,
  ) {
    return this.trainSchedulingService.assignUnassignedBookingToWagons(
      id,
      dto.bookingId,
    );
  }

  @Get("schedules/:id/composition-removals")
  @TrainSchedulingView()
  @ApiOperation({ summary: "Get removal log for a schedule" })
  getCompositionRemovals(@Param("id", ParseUUIDPipe) id: string) {
    return this.trainSchedulingService.getCompositionRemovals(id);
  }

  @Post("schedules/:id/pin-wagons")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Pin physical wagons to train set slots" })
  pinWagons(@Param("id", ParseUUIDPipe) id: string, @Body() dto: PinWagonsDto) {
    return this.trainSchedulingService.pinWagons(id, dto);
  }

  @Post("schedules/:id/finalize")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Finalize a draft train schedule" })
  finalizeSchedule(@Param("id", ParseUUIDPipe) id: string) {
    return this.trainSchedulingService.finalizeSchedule(id);
  }

  @Post("schedules/:id/dispatch")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Dispatch a scheduled train" })
  dispatchSchedule(@Param("id", ParseUUIDPipe) id: string) {
    return this.trainSchedulingService.dispatchSchedule(id);
  }

  // ---- batch / booking-window staff actions ----

  @Post("schedules/:id/run-batch")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Manually run the batch fill for a schedule" })
  async runBatch(@Param("id", ParseUUIDPipe) id: string) {
    await this.bookingBatchService.fillSchedule(id);
    return this.bookingBatchService.getBatchBoardDetail(id);
  }

  @Post("schedules/:id/run-allocation")
  @TrainSchedulingManage()
  @ApiOperation({
    summary: "Run wagon-level allocation for all eligible linked bookings",
  })
  async runAllocation(@Param("id", ParseUUIDPipe) id: string) {
    return this.bookingBatchService.runWagonAllocation(id);
  }

  @Patch("schedules/:id/booking-window")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Open or close a schedule booking window" })
  async setBookingWindow(
    @Param("id", ParseUUIDPipe) id: string,
    @Body("status") status: "OPEN" | "CLOSED",
  ) {
    await this.trainSchedulingService.setBookingWindow(
      id,
      status === "CLOSED" ? "CLOSED" : "OPEN",
    );
    return this.trainSchedulingService.getContainerTrainScheduleById(id);
  }

  @Post("bookings/:bookingId/mark-paid")
  @TrainSchedulingManage()
  @ApiOperation({
    summary: "Staff: mark a reserved booking paid and allocate it now",
  })
  async markBookingPaid(@Param("bookingId", ParseUUIDPipe) bookingId: string) {
    await this.bookingBatchService.markPaid(bookingId);
    return { ok: true };
  }

  @Post("bookings/:bookingId/expire")
  @TrainSchedulingManage()
  @ApiOperation({
    summary: "Staff: expire a reservation and free its capacity",
  })
  async expireBooking(@Param("bookingId", ParseUUIDPipe) bookingId: string) {
    await this.bookingBatchService.expireReservation(bookingId);
    return { ok: true };
  }

  @Post("bookings/:bookingId/move-schedule")
  @TrainSchedulingManage()
  @ApiOperation({
    summary: "Re-point a booking to another OPEN same-route schedule",
  })
  async moveBookingSchedule(
    @Param("bookingId", ParseUUIDPipe) bookingId: string,
    @Body("trainScheduleId", ParseUUIDPipe) trainScheduleId: string,
  ) {
    await this.bookingBatchService.moveToSchedule(bookingId, trainScheduleId);
    return { ok: true };
  }

  @Get("schedules/:id/checkpoints")
  @TrainSchedulingView()
  @ApiOperation({
    summary: "Get the tracking corridor + logged checkpoints for a train",
  })
  getScheduleCheckpoints(@Param("id", ParseUUIDPipe) id: string) {
    return this.trainSchedulingService.getScheduleCheckpoints(id);
  }

  @Post("schedules/:id/checkpoints")
  @TrainSchedulingManage()
  @ApiOperation({
    summary: "Log the train passing a station (final station triggers arrival)",
  })
  recordCheckpoint(
    @Param("id", ParseUUIDPipe) id: string,
    @Body() dto: RecordCheckpointDto,
  ) {
    return this.trainSchedulingService.recordCheckpoint(id, dto);
  }

  @Post("schedules/:id/arrive")
  @TrainSchedulingManage()
  @ApiOperation({
    summary:
      "Mark a dispatched train arrived (move assets to destination yard, free assets)",
  })
  arriveSchedule(@Param("id", ParseUUIDPipe) id: string) {
    return this.trainSchedulingService.arriveSchedule(id);
  }

  @Get("container/schedules")
  @TrainSchedulingView()
  @ApiOperation({ summary: "List container train schedules" })
  getContainerTrainSchedules() {
    return this.trainSchedulingService.getContainerTrainSchedules();
  }

  @Get("bulk/schedules")
  @TrainSchedulingView()
  @ApiOperation({ summary: "List bulk train schedules" })
  getBulkTrainSchedules() {
    return this.trainSchedulingService.getContainerTrainSchedules();
  }

  @Get("container/schedules/:id")
  @TrainSchedulingView()
  @ApiOperation({ summary: "Get container train schedule detail" })
  getContainerTrainScheduleById(@Param("id", ParseUUIDPipe) id: string) {
    return this.trainSchedulingService.getContainerTrainScheduleById(id);
  }

  @Get("bulk/schedules/:id")
  @TrainSchedulingView()
  @ApiOperation({ summary: "Get bulk train schedule detail" })
  getBulkTrainScheduleById(@Param("id", ParseUUIDPipe) id: string) {
    return this.trainSchedulingService.getContainerTrainScheduleById(id);
  }

  @Post("container/schedules/:id/cancel")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Cancel container train schedule" })
  cancelTrainSchedule(@Param("id", ParseUUIDPipe) id: string) {
    return this.trainSchedulingService.cancelTrainSchedule(id);
  }

  @Post("bulk/schedules/:id/cancel")
  @TrainSchedulingManage()
  @ApiOperation({ summary: "Cancel bulk train schedule" })
  cancelBulkTrainSchedule(@Param("id", ParseUUIDPipe) id: string) {
    return this.trainSchedulingService.cancelTrainSchedule(id);
  }
}
