import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Post,
  Patch,
  Query,
  SetMetadata,
  UseGuards,
} from "@nestjs/common";
import {
  ApiTags,
  ApiOperation,
  ApiBearerAuth,
  ApiParam,
  ApiQuery,
  ApiResponse,
  ApiBody,
} from "@nestjs/swagger";
import { SeatsService } from "./seats.service";
import { HoldSeatsDto, ReleaseHoldDto } from "./seats.dto";
import { GetDuplicateSeatsQuery, ResolveDuplicatesDto } from "./duplicate-seats.dto";
import { JwtGuard } from "../../common/jwt.guard";
import { PassengerStaff } from "../../common/passenger-guards";
import { PASSENGER_PERMS } from "../../seed/passenger-permissions.registry";

@ApiTags("Seats")
@Controller("seats")
export class SeatsController {
  constructor(private service: SeatsService) {}

  // ── Blocked Seats ─────────────────────────────────────────────────────────
  @Get('blocks')
  @PassengerStaff([PASSENGER_PERMS.seats.manage, PASSENGER_PERMS.admin])
  @ApiBearerAuth('IAM-auth')
  @ApiOperation({ summary: 'List all blocked seats with reason and coach info' })
  @ApiResponse({ status: 200, description: 'Blocked seat records' })
  getBlockedSeats() {
    return this.service.getBlockedSeats();
  }

  // ── Coach Availability ────────────────────────────────────────────────────
  @Get('coaches/:scheduleId')
  @SetMetadata('isPublic', true)
  @ApiOperation({
    summary: 'List coaches with remaining seat counts for a schedule',
    description: 'Returns each coach assigned to the schedule with total, available, held, and booked seat counts. Optionally scoped to a specific origin→destination leg.',
  })
  @ApiParam({ name: 'scheduleId', description: 'TrainSchedule UUID' })
  @ApiQuery({ name: 'originStationId', required: false, description: 'Scope availability to this origin station' })
  @ApiQuery({ name: 'destinationStationId', required: false, description: 'Scope availability to this destination station' })
  @ApiResponse({ status: 200, description: 'Coaches with seat availability counts' })
  getCoachesWithAvailability(
    @Param('scheduleId') scheduleId: string,
    @Query('originStationId') originStationId?: string,
    @Query('destinationStationId') destinationStationId?: string,
  ) {
    return this.service.getCoachesWithAvailability(scheduleId, originStationId, destinationStationId);
  }

  // ── Seat Map ──────────────────────────────────────────────────────────────
  @Get("seatmap/:scheduleId")
  @SetMetadata('isPublic', true)
  @ApiOperation({
    summary: "Get seat map filtered by coach type",
    description: `Returns all coaches of the given coachTypeId assigned to the schedule, each with their full seat list and real-time availability. Origin and destination are derived from the schedule. Omit coachTypeId to get all coaches. Use journeyDirection to filter seat holds (OUTBOUND vs RETURN for round-trip bookings).`,
  })
  @ApiParam({ name: "scheduleId", description: "TrainSchedule UUID" })
  @ApiQuery({
    name: "coachTypeId",
    required: false,
    description:
      "Filter by CoachType UUID — returns all coaches of that type (e.g. all Economy coaches)",
  })
  @ApiQuery({
    name: "journeyDirection",
    required: false,
    enum: ['ONE_WAY', 'OUTBOUND', 'RETURN'],
    description:
      "Journey direction for round-trip bookings. Filters seat holds to show only conflicting holds. Use OUTBOUND for outbound leg, RETURN for return leg. Defaults to ONE_WAY (shows all holds).",
  })
  @ApiQuery({
    name: "originStationId",
    required: false,
    description: "Origin station UUID for segment-specific seat availability",
  })
  @ApiQuery({
    name: "destinationStationId",
    required: false,
    description: "Destination station UUID for segment-specific seat availability",
  })
  @ApiResponse({
    status: 200,
    description:
      "List of coaches of the given type with their seats and availability",
  })
  getSeatMap(
    @Param("scheduleId") scheduleId: string,
    @Query("coachTypeId") coachTypeId?: string,
    @Query("journeyDirection") journeyDirection?: string,
    @Query("originStationId") originStationId?: string,
    @Query("destinationStationId") destinationStationId?: string,
  ) {
    return this.service.getSeatMap(
      scheduleId, 
      coachTypeId, 
      journeyDirection as any,
      originStationId,
      destinationStationId
    );
  }

  // ── Hold / Release ────────────────────────────────────────────────────────
  @Get("holds")
  @UseGuards(JwtGuard)
  @ApiBearerAuth("JWT-auth")
  @ApiOperation({
    summary: "List active seat holds with full leg context",
    description: `Returns all non-expired holds enriched with:
- **schedule**: train number, departure/arrival, full route origin→destination
- **leg**: the specific origin→destination this hold covers (station name, code, stop sequence)
- **seats**: seat label, coach, seat class, row, col
- **ttlSeconds**: seconds remaining before the hold expires

This makes it clear which segment of the route each seat is held for, enabling segment-based reuse of the same seat on non-overlapping legs.`,
  })
  @ApiQuery({
    name: "scheduleId",
    required: false,
    description: "Filter by TrainSchedule UUID",
  })
  @ApiQuery({
    name: "passengerId",
    required: false,
    description: "Filter by Passenger UUID",
  })
  @ApiResponse({
    status: 200,
    description: "Active holds with schedule, leg, and seat details",
  })
  getHolds(
    @Query("scheduleId") scheduleId?: string,
    @Query("passengerId") passengerId?: string,
  ) {
    return this.service.getHolds(scheduleId, passengerId);
  }

  @Get("holds/:holdId")
  @UseGuards(JwtGuard)
  @ApiBearerAuth("JWT-auth")
  @ApiOperation({ summary: "Get a single hold with full leg context" })
  @ApiParam({ name: "holdId", description: "SeatHold UUID" })
  @ApiResponse({
    status: 200,
    description: "Hold with schedule, leg, and seat details",
  })
  @ApiResponse({ status: 404, description: "Hold not found" })
  getHold(@Param("holdId") holdId: string) {
    return this.service.getHold(holdId);
  }

  @Post("hold")
  @SetMetadata('isPublic', true)
  @ApiOperation({
    summary:
      "Hold seats for 15 minutes before booking (Public - Guest booking supported)",
    description: `Temporarily reserves seats for a passenger to complete booking.

**Features:**
- 15-minute hold duration
- Auto-release after expiry
- Prevents double booking
- Required before creating booking
- **Public endpoint** - No authentication required (supports guest booking)`,
  })
  @ApiResponse({
    status: 201,
    description: "Seats held successfully with holdId",
  })
  @ApiResponse({ status: 409, description: "One or more seats unavailable" })
  holdSeats(@Body() dto: HoldSeatsDto) {
    return this.service.holdSeats(dto);
  }

  @Delete("hold/:holdId")
  @UseGuards(JwtGuard)
  @ApiBearerAuth("JWT-auth")
  @ApiOperation({ summary: "Release a seat hold" })
  @ApiParam({ name: "holdId", description: "Hold UUID" })
  @ApiResponse({ status: 200, description: "Hold released" })
  @ApiResponse({ status: 404, description: "Hold not found" })
  releaseHold(@Param("holdId") holdId: string) {
    return this.service.releaseHold(holdId);
  }

  @Post("release")
  @SetMetadata('isPublic', true)
  @ApiOperation({
    summary: "Release a seat hold by holdId (portal-server use only)",
    description:
      "Frees a previously-created hold's seats immediately instead of waiting for it to " +
      "expire — used when a guest or logged-in user changes their seat selection, so the " +
      "stale hold doesn't linger and block that seat for other travellers.\n\n" +
      "This is a public endpoint (no JWT), like POST /seats/hold, since guest sessions have " +
      "no login to authenticate with. It must ONLY ever be called from the passenger portal's " +
      "own Next.js server (a server-side route handler), never directly from browser code — " +
      "calling it straight from client JS would let anyone script mass hold-cancellation " +
      "against other travellers' in-progress seat selections. The portal's server-side proxy " +
      "is what keeps this endpoint's existence out of the browser's network requests.",
  })
  @ApiResponse({ status: 200, description: "Hold released" })
  @ApiResponse({ status: 404, description: "Hold not found" })
  releaseSeatById(@Body() dto: ReleaseHoldDto) {
    return this.service.releaseHold(dto.holdId);
  }

  // ── Seat Block / Unblock ───────────────────────────────────────────────────
  @Post(":seatId/block")
  @PassengerStaff([PASSENGER_PERMS.seats.manage, PASSENGER_PERMS.admin])
  @ApiBearerAuth("IAM-auth")
  @ApiOperation({ summary: "Block a seat (e.g., maintenance, damage)" })
  @ApiParam({ name: "seatId", description: "Seat UUID" })
  @ApiResponse({ status: 200, description: "Seat blocked" })
  blockSeat(@Param("seatId") seatId: string, @Body() body: { reason: string; scheduleId?: string }) {
    return this.service.blockSeat(seatId, body.reason, body.scheduleId);
  }

  @Delete(":seatId/block")
  @PassengerStaff([PASSENGER_PERMS.seats.manage, PASSENGER_PERMS.admin])
  @ApiBearerAuth("IAM-auth")
  @ApiOperation({ summary: "Unblock a seat" })
  @ApiParam({ name: "seatId", description: "Seat UUID" })
  @ApiResponse({ status: 200, description: "Seat unblocked" })
  unblockSeat(@Param("seatId") seatId: string, @Query("scheduleId") scheduleId?: string) {
    return this.service.unblockSeat(seatId, scheduleId);
  }

  // ── Maintenance ───────────────────────────────────────────────────────────
  @Post(":seatId/maintenance")
  @PassengerStaff([PASSENGER_PERMS.seats.manage, PASSENGER_PERMS.admin])
  @ApiBearerAuth("IAM-auth")
  @ApiOperation({ summary: "Set seat status to Under Maintenance" })
  @ApiParam({ name: "seatId", description: "Seat UUID" })
  @ApiResponse({ status: 200, description: "Seat set to under maintenance" })
  setMaintenance(@Param("seatId") seatId: string, @Body() body: { reason: string }) {
    return this.service.setMaintenance(seatId, body.reason);
  }

  @Delete(":seatId/maintenance")
  @PassengerStaff([PASSENGER_PERMS.seats.manage, PASSENGER_PERMS.admin])
  @ApiBearerAuth("IAM-auth")
  @ApiOperation({ summary: "Clear seat maintenance status" })
  @ApiParam({ name: "seatId", description: "Seat UUID" })
  @ApiResponse({ status: 200, description: "Seat cleared from maintenance" })
  clearMaintenance(@Param("seatId") seatId: string) {
    return this.service.clearMaintenance(seatId);
  }

  // ── Remove Seat ────────────────────────────────────────────────────────────
  @Patch(":seatId/remove")
  @PassengerStaff([PASSENGER_PERMS.seats.manage, PASSENGER_PERMS.admin])
  @ApiBearerAuth("IAM-auth")
  @ApiOperation({
    summary: "Remove a seat by marking with negative seatNumber",
  })
  @ApiParam({ name: "seatId", description: "Seat UUID" })
  @ApiResponse({
    status: 200,
    description: "Seat removed (seatNumber negated), shows as empty space",
  })
  @ApiResponse({ status: 404, description: "Seat not found" })
  removeSeat(@Param("seatId") seatId: string) {
    return this.service.removeSeat(seatId);
  }

  @Patch(":seatId/undo-remove")
  @PassengerStaff([PASSENGER_PERMS.seats.manage, PASSENGER_PERMS.admin])
  @ApiBearerAuth("IAM-auth")
  @ApiOperation({
    summary: "Undo seat removal by restoring original seatNumber",
  })
  @ApiParam({ name: "seatId", description: "Seat UUID" })
  @ApiResponse({
    status: 200,
    description: "Seat restored (negative seatNumber removed)",
  })
  @ApiResponse({ status: 404, description: "Seat not found" })
  @ApiResponse({ status: 400, description: "Seat is not removed" })
  undoRemoveSeat(@Param("seatId") seatId: string) {
    return this.service.undoRemoveSeat(seatId);
  }

  @Get("export/csv/:scheduleId")
  @UseGuards(JwtGuard)
  @ApiBearerAuth("JWT-auth")
  @ApiOperation({ summary: "Export seats as CSV" })
  async exportCSV(@Param("scheduleId") scheduleId: string) {
    const csv = await this.service.exportSeatsCSV(scheduleId);
    return { csv, filename: `seats-${scheduleId}.csv` };
  }

  @Post("import/preview")
  @UseGuards(JwtGuard)
  @ApiBearerAuth("JWT-auth")
  @ApiOperation({ summary: "Preview CSV import" })
  previewCSV(@Body() body: { csv: string }) {
    return this.service.previewSeatsCSV(body.csv);
  }

  @Post("import/commit")
  @UseGuards(JwtGuard)
  @ApiBearerAuth("JWT-auth")
  @ApiOperation({ summary: "Commit CSV import" })
  importCSV(
    @Body() body: { scheduleId: string; csv: string; commit: boolean },
  ) {
    return this.service.importSeatsCSV(body.scheduleId, body.csv, body.commit);
  }

  // ── Duplicate seat management (backoffice) ────────────────────────────────

  @Get("duplicates")
  @UseGuards(JwtGuard)
  @ApiBearerAuth("JWT-auth")
  @ApiOperation({
    summary: "List duplicate seat assignments by schedule date",
    description:
      "Returns all schedules on the given date that have bookings sharing " +
      "the same seat, grouped by coach. Each coach entry includes the duplicate " +
      "groups (with full booking info) and the list of currently available seats " +
      "that can be used for reassignment.",
  })
  @ApiQuery({ name: "date", example: "2026-07-17", description: "Schedule date (YYYY-MM-DD)" })
  @ApiQuery({ name: "scheduleId", required: false, description: "Filter to a specific schedule" })
  @ApiResponse({
    status: 200,
    description: "Duplicate seat report grouped by schedule → coach",
    schema: {
      example: {
        date: "2026-07-17",
        totalDuplicates: 1,
        schedules: [{
          scheduleId: "uuid",
          departureAt: "2026-07-17T06:00:00.000Z",
          origin: "Addis Ababa",
          destination: "Dire Dawa",
          coaches: [{
            coachId: "uuid",
            coachNumber: "C1",
            coachTypeName: "SBC",
            duplicates: [{
              seatId: "uuid",
              seatNumber: "12A",
              leg: 1,
              bookings: [
                { bookingSeatId: "uuid", bookingId: "uuid", bookingRef: "ATPC9F", passengerName: "Abebe", contactPhone: "+251911000000", createdAt: "2026-07-16T10:00:00.000Z" },
                { bookingSeatId: "uuid", bookingId: "uuid", bookingRef: "XYZ123", passengerName: "Kebede", contactPhone: "+251922000000", createdAt: "2026-07-16T11:00:00.000Z" },
              ],
            }],
            availableSeats: [
              { seatId: "uuid", seatNumber: "14B" },
              { seatId: "uuid", seatNumber: "15A" },
            ],
          }],
        }],
      },
    },
  })
  getDuplicateSeats(@Query() query: GetDuplicateSeatsQuery) {
    return this.service.getDuplicateSeats(query.date, query.scheduleId);
  }

  @Post("duplicates/resolve")
  @UseGuards(JwtGuard)
  @ApiBearerAuth("JWT-auth")
  @ApiOperation({
    summary: "Auto-assign duplicate bookings to seats in selected coaches",
    description:
      "Staff selects which duplicate BookingSeat IDs to fix and which coaches to pull replacement seats from. " +
      "The system automatically picks the first available (non-blocked, non-occupied) seat in the given coaches " +
      "for each booking, updates BookingSeat + Ticket + JourneySegment atomically so the seatmap reflects the " +
      "change immediately, then sends an SMS notification to the passenger. " +
      "Coaches are searched in the order provided; seats within each coach are assigned by row then column.",
  })
  @ApiBody({ type: ResolveDuplicatesDto })
  @ApiResponse({
    status: 200,
    description: "Resolution summary — resolved count, unresolved count, per-booking results",
    schema: {
      example: {
        resolved: 2,
        unresolved: 0,
        results: [
          { bookingRef: "XYZ123", oldSeatNumber: "1A", newSeatNumber: "14B", contactPhone: "+251922000000" },
          { bookingRef: "ABC456", oldSeatNumber: "1A", newSeatNumber: "15A", contactPhone: "+251933000000" },
        ],
      },
    },
  })
  @ApiResponse({ status: 400, description: "Booking not in CONFIRMED/BOARDED status" })
  @ApiResponse({ status: 404, description: "BookingSeat ID not found" })
  resolveDuplicateSeats(@Body() dto: ResolveDuplicatesDto) {
    return this.service.resolveDuplicateSeats(dto.bookingSeatIds, dto.coachIds);
  }
}
