import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
import { SeatsService } from '../seats/seats.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { CreateBookingDto, ModifyBookingDto } from './bookings.dto';
import { Cron, CronExpression } from '@nestjs/schedule';
import { VerifaydaService } from '../verifayda/verifayda.service';
import { CurrencyService } from '../currency/currency.service';
import { FareEngineService } from '../fare-engine/fare-engine.service';
import { Currency, PassengerCategory, IdDocumentType } from '@prisma/client';

function generateRef(): string {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  return Array.from({ length: 6 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}

function calculateAge(dateOfBirth: Date): number {
  const today = new Date();
  let age = today.getFullYear() - dateOfBirth.getFullYear();
  const monthDiff = today.getMonth() - dateOfBirth.getMonth();
  if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dateOfBirth.getDate())) age--;
  return age;
}

interface BookingFilters {
  search?: string;
  status?: string;
  returnLegStatus?: string;
  page?: number;
  pageSize?: number;
}

@Injectable()
export class BookingsService {
  constructor(
    private prisma: PrismaService,
    private seatsService: SeatsService,
    private eventEmitter: EventEmitter2,
    private verifaydaService: VerifaydaService,
    private currencyService: CurrencyService,
    private fareEngine: FareEngineService,
  ) {}

  async findByPassengerId(passengerId: string, filters: BookingFilters = {}) {
    const { search, status, page = 1, pageSize = 20 } = filters;
    const skip = (page - 1) * pageSize;
    
    const where: any = { passengerId };
    
    if (search) {
      where.OR = [
        { bookingRef: { contains: search, mode: 'insensitive' } },
        { schedule: { originStation: { name: { contains: search, mode: 'insensitive' } } } },
        { schedule: { destinationStation: { name: { contains: search, mode: 'insensitive' } } } },
      ];
    }
    
    if (status) {
      where.status = status;
    }
    
    const [items, total] = await Promise.all([
      this.prisma.booking.findMany({
        where,
        skip,
        take: pageSize,
        orderBy: { createdAt: 'desc' },
        include: {
          schedule: { include: { originStation: true, destinationStation: true, train: true } },
          paymentIntent: true,
          seats: { include: { seat: true } },
        },
      }),
      this.prisma.booking.count({ where }),
    ]);
    
    return {
      items: items.map(booking => ({
        id: booking.id,
        bookingRef: booking.bookingRef,
        status: booking.status,
        totalMinor: booking.totalMinor,
        currency: 'ETB',
        displayCurrency: booking.displayCurrency,
        displayTotalMinor: booking.displayTotalMinor,
        adultCount: booking.adultCount,
        childCount: booking.childCount,
        bookingType: booking.bookingType,
        returnLegStatus: (booking as any).returnLegStatus ?? null,
        createdAt: booking.createdAt,
        schedule: {
          train: booking.schedule.train,
          originStation: booking.schedule.originStation,
          destinationStation: booking.schedule.destinationStation,
          departureAt: booking.schedule.departureAt,
          arrivalAt: booking.schedule.arrivalAt,
        },
        paymentIntent: booking.paymentIntent,
        seatCount: booking.seats.length,
      })),
      meta: {
        page,
        pageSize,
        total,
        totalPages: Math.ceil(total / pageSize),
      },
    };
  }

  async findByDeviceId(deviceId: string, filters: BookingFilters = {}) {
    const { search, status, page = 1, pageSize = 20 } = filters;
    const skip = (page - 1) * pageSize;
    
    // Find user with this device ID
    const device = await this.prisma.device.findUnique({
      where: { id: deviceId },
      include: { user: { include: { passenger: true } } },
    }).catch(() => null);
    
    const searchConditions = search ? [
      { bookingRef: { contains: search, mode: 'insensitive' } },
      { schedule: { originStation: { name: { contains: search, mode: 'insensitive' } } } },
      { schedule: { destinationStation: { name: { contains: search, mode: 'insensitive' } } } },
    ] : [];
    
    const where: any = {
      OR: [
        { userAgent: deviceId },
        ...(device?.user?.passenger ? [{ passengerId: device.user.passenger.id }] : []),
      ],
    };
    
    if (search) {
      where.AND = [{ OR: searchConditions }];
    }
    
    if (status) {
      where.status = status;
    }
    
    const [items, total] = await Promise.all([
      this.prisma.booking.findMany({
        where,
        skip,
        take: pageSize,
        orderBy: { createdAt: 'desc' },
        include: {
          schedule: { include: { originStation: true, destinationStation: true, train: true } },
          paymentIntent: true,
          seats: { include: { seat: true } },
        },
      }),
      this.prisma.booking.count({ where }),
    ]);
    
    return {
      items: items.map(booking => ({
        id: booking.id,
        bookingRef: booking.bookingRef,
        status: booking.status,
        totalMinor: booking.totalMinor,
        currency: 'ETB',
        displayCurrency: booking.displayCurrency,
        displayTotalMinor: booking.displayTotalMinor,
        adultCount: booking.adultCount,
        childCount: booking.childCount,
        bookingType: booking.bookingType,
        returnLegStatus: (booking as any).returnLegStatus ?? null,
        createdAt: booking.createdAt,
        schedule: {
          train: booking.schedule.train,
          originStation: booking.schedule.originStation,
          destinationStation: booking.schedule.destinationStation,
          departureAt: booking.schedule.departureAt,
          arrivalAt: booking.schedule.arrivalAt,
        },
        paymentIntent: booking.paymentIntent,
        seatCount: booking.seats.length,
      })),
      meta: {
        page,
        pageSize,
        total,
        totalPages: Math.ceil(total / pageSize),
      },
    };
  }

  async findAll(filters: BookingFilters = {}) {
    const { search, status, returnLegStatus, page = 1, pageSize = 20 } = filters;
    const skip = (page - 1) * pageSize;
    
    const where: any = {};
    
    if (search) {
      where.OR = [
        { bookingRef: { contains: search, mode: 'insensitive' } },
        { contactEmail: { contains: search, mode: 'insensitive' } },
        { contactPhone: { contains: search, mode: 'insensitive' } },
        { passenger: { user: { fullName: { contains: search, mode: 'insensitive' } } } },
      ];
    }
    
    if (status) where.status = status;
    if (returnLegStatus) (where as any).returnLegStatus = returnLegStatus;
    
    const [items, total] = await Promise.all([
      this.prisma.booking.findMany({
        where,
        skip,
        take: pageSize,
        orderBy: { createdAt: 'desc' },
        include: {
          passenger: { include: { user: true } },
          schedule: { include: { originStation: true, destinationStation: true, train: true } },
          paymentIntent: true,
          seats: { include: { seat: true } },
        },
      }),
      this.prisma.booking.count({ where }),
    ]);
    
    return {
      items: items.map(booking => ({
        id: booking.id,
        bookingRef: booking.bookingRef,
        status: booking.status,
        totalMinor: booking.totalMinor,
        currency: 'ETB',
        displayCurrency: booking.displayCurrency,
        displayTotalMinor: booking.displayTotalMinor,
        contactEmail: booking.contactEmail,
        contactPhone: booking.contactPhone,
        bookingType: booking.bookingType,
        returnLegStatus: (booking as any).returnLegStatus ?? null,
        adultCount: booking.adultCount,
        childCount: booking.childCount,
        createdAt: booking.createdAt,
        passenger: booking.passenger?.user,
        schedule: {
          train: booking.schedule.train,
          originStation: booking.schedule.originStation,
          destinationStation: booking.schedule.destinationStation,
          departureAt: booking.schedule.departureAt,
        },
        paymentIntent: booking.paymentIntent,
        seatCount: booking.seats.length,
      })),
      meta: {
        page,
        pageSize,
        total,
        totalPages: Math.ceil(total / pageSize),
      },
    };
  }

  async create(dto: CreateBookingDto) {
    if (dto.bookingType === 'ROUND_TRIP')         return this.createRoundTripBooking(dto);
    if (dto.bookingType === 'TRANSIT')            return this.createTransitBooking(dto);
    if (dto.bookingType === 'ROUND_TRIP_TRANSIT') return this.createRoundTripTransitBooking(dto);
    return this.createOneWayBooking(dto);
  }

  private async createOneWayBooking(dto: CreateBookingDto) {
    const hold = await this.prisma.seatHold.findUnique({ where: { id: dto.holdId } });
    if (!hold || hold.expiresAt < new Date()) throw new BadRequestException('Seat hold expired');
    
    const schedule = await this.prisma.trainSchedule.findUnique({
      where: { id: dto.scheduleId },
      include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } }
    });
    if (!schedule) throw new NotFoundException('Schedule not found');

    const originStop = schedule.stopTimes.find(s => s.stationId === dto.originStationId);
    const destStop = schedule.stopTimes.find(s => s.stationId === dto.destinationStationId);
    if (!originStop || !destStop) throw new NotFoundException('Origin or destination not found');

    const passengersData = await this.processPassengers(dto.passengers as any[]);
    const { adultCount, childCount } = this.countPassengers(passengersData);
    const fareCalculation = await this.calculateFare(dto.scheduleId, dto.seatClassId, originStop, destStop, passengersData[0]?.nationality, adultCount, childCount, dto.promoCode, dto.loyaltyRedemptionPoints);
    
    const displayCurrency = dto.displayCurrency || Currency.ETB;
    let displayTotalMinor = fareCalculation.totalMinor;
    if (displayCurrency !== Currency.ETB) {
      displayTotalMinor = await this.currencyService.convertAmount(fareCalculation.totalMinor, Currency.ETB, displayCurrency);
    }

    const booking = await this.prisma.booking.create({
      data: {
        bookingRef: generateRef(),
        passengerId: dto.passengerId,
        scheduleId: dto.scheduleId,
        status: 'PENDING_PAYMENT',
        bookingType: 'ONE_WAY',
        totalMinor: fareCalculation.totalMinor,
        adultCount,
        childCount,
        displayCurrency,
        displayTotalMinor,
        seats: {
          create: passengersData.map(p => ({
            seat: { connect: { id: p.seatId } },
            passengerName: p.passengerName,
            dateOfBirth: p.dateOfBirth,
            passengerCategory: p.category,
            idDocumentType: p.idDocumentType,
            passportNumber: p.passportNumber,
            passportCountry: p.passportCountry,
            verifaydaVerified: p.verifaydaVerified,
            verifaydaData: p.verifaydaData,
            fareMinor: p.category === PassengerCategory.ADULT ? fareCalculation.baseFareMinor : (fareCalculation.paidChildrenCount > 0 ? fareCalculation.baseFareMinor : 0),
            displayCurrency
          }))
        }
      },
      include: { seats: { include: { seat: true } }, schedule: { include: { originStation: true, destinationStation: true, train: true } } }
    });

    await this.seatsService.confirmSeats(passengersData.map(p => p.seatId));
    this.eventEmitter.emit('booking.created', { booking });
    return { ...booking, fareBreakdown: fareCalculation };
  }

  private async createRoundTripBooking(dto: CreateBookingDto) {
    if (!dto.returnScheduleId || !dto.returnHoldId || !dto.returnOriginStationId || !dto.returnDestinationStationId) {
      throw new BadRequestException('Return trip details required for round-trip booking');
    }

    const [outboundHold, returnHold] = await Promise.all([
      this.prisma.seatHold.findUnique({ where: { id: dto.holdId } }),
      this.prisma.seatHold.findUnique({ where: { id: dto.returnHoldId } })
    ]);
    
    if (!outboundHold || outboundHold.expiresAt < new Date()) throw new BadRequestException('Outbound seat hold expired');
    if (!returnHold || returnHold.expiresAt < new Date()) throw new BadRequestException('Return seat hold expired');

    const [outboundSchedule, returnSchedule] = await Promise.all([
      this.prisma.trainSchedule.findUnique({
        where: { id: dto.scheduleId },
        include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } }
      }),
      this.prisma.trainSchedule.findUnique({
        where: { id: dto.returnScheduleId },
        include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } }
      })
    ]);

    if (!outboundSchedule || !returnSchedule) throw new NotFoundException('Schedule not found');

    const outboundOriginStop = outboundSchedule.stopTimes.find(s => s.stationId === dto.originStationId);
    const outboundDestStop = outboundSchedule.stopTimes.find(s => s.stationId === dto.destinationStationId);
    const returnOriginStop = returnSchedule.stopTimes.find(s => s.stationId === dto.returnOriginStationId);
    const returnDestStop = returnSchedule.stopTimes.find(s => s.stationId === dto.returnDestinationStationId);

    if (!outboundOriginStop || !outboundDestStop || !returnOriginStop || !returnDestStop) {
      throw new NotFoundException('Origin or destination stops not found');
    }

    const passengersData = await this.processRoundTripPassengers(dto.passengers as any[]);
    const { adultCount, childCount } = this.countPassengers(passengersData);

    const [outboundFare, returnFare] = await Promise.all([
      this.calculateFare(dto.scheduleId, dto.seatClassId, outboundOriginStop, outboundDestStop, passengersData[0]?.nationality, adultCount, childCount),
      this.calculateFare(dto.returnScheduleId, dto.returnSeatClassId || dto.seatClassId, returnOriginStop, returnDestStop, passengersData[0]?.nationality, adultCount, childCount)
    ]);

    const combinedBaseFareMinor = outboundFare.totalBaseFareMinor + returnFare.totalBaseFareMinor;
    let discountMinor = 0;
    if (dto.promoCode) {
      const promo = await this.prisma.promotion.findUnique({ where: { code: dto.promoCode } });
      if (promo?.active && promo.validUntil > new Date()) {
        discountMinor = promo.percentOff ? Math.round(combinedBaseFareMinor * promo.percentOff / 100) : (promo.amountOffMinor ?? 0);
      }
    }

    const loyaltyMinor = (dto.loyaltyRedemptionPoints ?? 0) * 10;
    const taxesMinor = Math.round(combinedBaseFareMinor * 0.05);
    const totalMinor = Math.max(0, combinedBaseFareMinor - discountMinor - loyaltyMinor + taxesMinor);

    const displayCurrency = dto.displayCurrency || Currency.ETB;
    let displayTotalMinor = totalMinor;
    if (displayCurrency !== Currency.ETB) {
      displayTotalMinor = await this.currencyService.convertAmount(totalMinor, Currency.ETB, displayCurrency);
    }

    const booking = await this.prisma.booking.create({
      data: {
        bookingRef: generateRef(),
        passengerId: dto.passengerId,
        scheduleId: dto.scheduleId,
        status: 'PENDING_PAYMENT',
        bookingType: 'ROUND_TRIP',
        totalMinor,
        adultCount,
        childCount,
        displayCurrency,
        displayTotalMinor,
        returnScheduleId: dto.returnScheduleId,
        returnOriginStationId: dto.returnOriginStationId,
        returnDestinationStationId: dto.returnDestinationStationId,
        returnHoldId: dto.returnHoldId,
        returnSeatClassId: dto.returnSeatClassId,
        returnLegStatus: 'NEITHER_USED',
        seats: {
          create: [
            ...passengersData.map(p => ({
              seat: { connect: { id: p.outboundSeatId } },
              leg: 1,
              scheduleId: dto.scheduleId,
              passengerName: p.passengerName,
              dateOfBirth: p.dateOfBirth,
              passengerCategory: p.category,
              idDocumentType: p.idDocumentType,
              passportNumber: p.passportNumber,
              passportCountry: p.passportCountry,
              verifaydaVerified: p.verifaydaVerified,
              verifaydaData: p.verifaydaData,
              fareMinor: p.category === PassengerCategory.ADULT ? outboundFare.baseFareMinor : (outboundFare.paidChildrenCount > 0 ? outboundFare.baseFareMinor : 0),
              displayCurrency,
            })),
            ...passengersData.map(p => ({
              seat: { connect: { id: p.returnSeatId } },
              leg: 2,
              scheduleId: dto.returnScheduleId,
              passengerName: p.passengerName,
              dateOfBirth: p.dateOfBirth,
              passengerCategory: p.category,
              idDocumentType: p.idDocumentType,
              passportNumber: p.passportNumber,
              passportCountry: p.passportCountry,
              verifaydaVerified: p.verifaydaVerified,
              verifaydaData: p.verifaydaData,
              fareMinor: p.category === PassengerCategory.ADULT ? returnFare.baseFareMinor : (returnFare.paidChildrenCount > 0 ? returnFare.baseFareMinor : 0),
              displayCurrency,
            })),
          ],
        },
      } as any,
      include: { seats: { include: { seat: true } }, schedule: { include: { originStation: true, destinationStation: true, train: true } } }
    });

    const outboundSeatIds = passengersData.map(p => p.outboundSeatId);
    const returnSeatIds = passengersData.map(p => p.returnSeatId);
    await Promise.all([
      this.seatsService.confirmSeats(outboundSeatIds),
      this.seatsService.confirmSeats(returnSeatIds)
    ]);

    this.eventEmitter.emit('booking.created', { booking });

    return {
      ...booking,
      fareBreakdown: {
        outboundFare: outboundFare.baseFareMinor,
        returnFare: returnFare.baseFareMinor,
        combinedBaseFareMinor,
        discountMinor,
        loyaltyRedemptionMinor: loyaltyMinor,
        taxesFeesMinor: taxesMinor,
        totalMinor,
        currency: 'ETB',
        displayCurrency,
        displayTotalMinor
      }
    };
  }

  private async createTransitBooking(dto: CreateBookingDto) {
    if (!dto.leg2ScheduleId || !dto.leg2HoldId || !dto.transitStationId || !dto.leg2DestinationStationId) {
      throw new BadRequestException('leg2ScheduleId, leg2HoldId, transitStationId and leg2DestinationStationId are required for TRANSIT bookings');
    }

    const [leg1Hold, leg2Hold] = await Promise.all([
      this.prisma.seatHold.findUnique({ where: { id: dto.holdId } }),
      this.prisma.seatHold.findUnique({ where: { id: dto.leg2HoldId } }),
    ]);
    if (!leg1Hold || leg1Hold.expiresAt < new Date()) throw new BadRequestException('Leg-1 seat hold expired');
    if (!leg2Hold || leg2Hold.expiresAt < new Date()) throw new BadRequestException('Leg-2 seat hold expired');

    const [leg1Schedule, leg2Schedule] = await Promise.all([
      this.prisma.trainSchedule.findUnique({
        where: { id: dto.scheduleId },
        include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } },
      }),
      this.prisma.trainSchedule.findUnique({
        where: { id: dto.leg2ScheduleId },
        include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } },
      }),
    ]);
    if (!leg1Schedule) throw new NotFoundException('Leg-1 schedule not found');
    if (!leg2Schedule) throw new NotFoundException('Leg-2 schedule not found');

    const leg1OriginStop = leg1Schedule.stopTimes.find(s => s.stationId === dto.originStationId);
    const leg1DestStop   = leg1Schedule.stopTimes.find(s => s.stationId === dto.transitStationId);
    const leg2OriginStop = leg2Schedule.stopTimes.find(s => s.stationId === dto.transitStationId);
    const leg2DestStop   = leg2Schedule.stopTimes.find(s => s.stationId === dto.leg2DestinationStationId);
    if (!leg1OriginStop || !leg1DestStop) throw new NotFoundException('Leg-1 origin or transit station not found on schedule');
    if (!leg2OriginStop || !leg2DestStop) throw new NotFoundException('Transit or leg-2 destination station not found on leg-2 schedule');

    const passengersData = await this.processPassengers(dto.passengers as any[]);
    const { adultCount, childCount } = this.countPassengers(passengersData);

    const leg2SeatClassId = dto.leg2SeatClassId ?? dto.seatClassId;
    const [leg1Fare, leg2Fare] = await Promise.all([
      this.calculateFare(dto.scheduleId, dto.seatClassId, leg1OriginStop, leg1DestStop, passengersData[0]?.nationality, adultCount, childCount),
      this.calculateFare(dto.leg2ScheduleId, leg2SeatClassId, leg2OriginStop, leg2DestStop, passengersData[0]?.nationality, adultCount, childCount),
    ]);

    const combinedBase = leg1Fare.totalBaseFareMinor + leg2Fare.totalBaseFareMinor;
    let discountMinor = 0;
    if (dto.promoCode) {
      const promo = await this.prisma.promotion.findUnique({ where: { code: dto.promoCode } });
      if (promo?.active && promo.validUntil > new Date()) {
        discountMinor = promo.percentOff ? Math.round(combinedBase * promo.percentOff / 100) : (promo.amountOffMinor ?? 0);
      }
    }
    const loyaltyMinor    = (dto.loyaltyRedemptionPoints ?? 0) * 10;
    const taxesMinor      = Math.round(combinedBase * 0.05);
    const totalMinor      = Math.max(0, combinedBase - discountMinor - loyaltyMinor + taxesMinor);
    const displayCurrency = dto.displayCurrency || Currency.ETB;
    const displayTotalMinor = displayCurrency !== Currency.ETB
      ? await this.currencyService.convertAmount(totalMinor, Currency.ETB, displayCurrency)
      : totalMinor;

    // Single booking — leg-1 seats at leg=1, leg-2 seats at leg=2
    const booking = await this.prisma.booking.create({
      data: {
        bookingRef: generateRef(),
        passengerId: dto.passengerId,
        scheduleId: dto.scheduleId,
        status: 'PENDING_PAYMENT',
        bookingType: 'TRANSIT',
        totalMinor,
        adultCount,
        childCount,
        displayCurrency,
        displayTotalMinor,
        leg2ScheduleId: dto.leg2ScheduleId,
        leg2OriginStationId: dto.transitStationId,
        leg2DestinationStationId: dto.leg2DestinationStationId,
        leg2SeatClassId,
        seats: {
          create: [
            ...passengersData.map(p => ({
              seat: { connect: { id: p.seatId } },
              leg: 1,
              scheduleId: dto.scheduleId,
              passengerName: p.passengerName,
              dateOfBirth: p.dateOfBirth,
              passengerCategory: p.category,
              idDocumentType: p.idDocumentType,
              passportNumber: p.passportNumber,
              passportCountry: p.passportCountry,
              verifaydaVerified: p.verifaydaVerified,
              verifaydaData: p.verifaydaData,
              fareMinor: p.category === PassengerCategory.ADULT ? leg1Fare.baseFareMinor : (leg1Fare.paidChildrenCount > 0 ? leg1Fare.baseFareMinor : 0),
              displayCurrency,
            })),
            ...passengersData.map(p => ({
              seat: { connect: { id: p.leg2SeatId ?? p.seatId } },
              leg: 2,
              scheduleId: dto.leg2ScheduleId,
              passengerName: p.passengerName,
              dateOfBirth: p.dateOfBirth,
              passengerCategory: p.category,
              idDocumentType: p.idDocumentType,
              passportNumber: p.passportNumber,
              passportCountry: p.passportCountry,
              verifaydaVerified: p.verifaydaVerified,
              verifaydaData: p.verifaydaData,
              fareMinor: p.category === PassengerCategory.ADULT ? leg2Fare.baseFareMinor : (leg2Fare.paidChildrenCount > 0 ? leg2Fare.baseFareMinor : 0),
              displayCurrency,
            })),
          ],
        },
      } as any,
      include: { seats: { include: { seat: true } }, schedule: { include: { originStation: true, destinationStation: true, train: true } } },
    });

    await Promise.all([
      this.seatsService.confirmSeats(passengersData.map(p => p.seatId)),
      this.seatsService.confirmSeats(passengersData.map(p => p.leg2SeatId ?? p.seatId)),
    ]);
    this.eventEmitter.emit('booking.created', { booking });

    return {
      ...booking,
      fareBreakdown: {
        leg1BaseFareMinor: leg1Fare.baseFareMinor,
        leg2BaseFareMinor: leg2Fare.baseFareMinor,
        adultCount, childCount,
        freeChildrenCount: Math.min(childCount, 1),
        paidChildrenCount: leg1Fare.paidChildrenCount,
        combinedBaseFareMinor: combinedBase,
        discountMinor, loyaltyRedemptionMinor: loyaltyMinor,
        taxesFeesMinor: taxesMinor, totalMinor,
        currency: 'ETB', displayCurrency, displayTotalMinor,
      },
    };
  }

  private async createRoundTripTransitBooking(dto: CreateBookingDto) {
    if (!dto.leg2ScheduleId || !dto.leg2HoldId || !dto.transitStationId || !dto.leg2DestinationStationId ||
        !dto.returnScheduleId || !dto.returnHoldId || !dto.returnOriginStationId || !dto.returnDestinationStationId ||
        !dto.returnLeg2ScheduleId || !dto.returnLeg2HoldId || !dto.returnTransitStationId || !dto.returnLeg2DestinationStationId) {
      throw new BadRequestException(
        'ROUND_TRIP_TRANSIT requires outbound transit fields (leg2ScheduleId, leg2HoldId, transitStationId, leg2DestinationStationId) ' +
        'AND return transit fields (returnScheduleId, returnHoldId, returnOriginStationId, returnDestinationStationId, ' +
        'returnLeg2ScheduleId, returnLeg2HoldId, returnTransitStationId, returnLeg2DestinationStationId)',
      );
    }

    // Validate all 4 holds
    const [obL1Hold, obL2Hold, retL1Hold, retL2Hold] = await Promise.all([
      this.prisma.seatHold.findUnique({ where: { id: dto.holdId } }),
      this.prisma.seatHold.findUnique({ where: { id: dto.leg2HoldId } }),
      this.prisma.seatHold.findUnique({ where: { id: dto.returnHoldId } }),
      this.prisma.seatHold.findUnique({ where: { id: dto.returnLeg2HoldId } }),
    ]);
    const now = new Date();
    if (!obL1Hold  || obL1Hold.expiresAt  < now) throw new BadRequestException('Outbound leg-1 seat hold expired');
    if (!obL2Hold  || obL2Hold.expiresAt  < now) throw new BadRequestException('Outbound leg-2 seat hold expired');
    if (!retL1Hold || retL1Hold.expiresAt < now) throw new BadRequestException('Return leg-1 seat hold expired');
    if (!retL2Hold || retL2Hold.expiresAt < now) throw new BadRequestException('Return leg-2 seat hold expired');

    // Load all 4 schedules
    const [obL1Sched, obL2Sched, retL1Sched, retL2Sched] = await Promise.all([
      this.prisma.trainSchedule.findUnique({ where: { id: dto.scheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
      this.prisma.trainSchedule.findUnique({ where: { id: dto.leg2ScheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
      this.prisma.trainSchedule.findUnique({ where: { id: dto.returnScheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
      this.prisma.trainSchedule.findUnique({ where: { id: dto.returnLeg2ScheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
    ]);
    if (!obL1Sched)  throw new NotFoundException('Outbound leg-1 schedule not found');
    if (!obL2Sched)  throw new NotFoundException('Outbound leg-2 schedule not found');
    if (!retL1Sched) throw new NotFoundException('Return leg-1 schedule not found');
    if (!retL2Sched) throw new NotFoundException('Return leg-2 schedule not found');

    const obL1Origin  = obL1Sched.stopTimes.find(s => s.stationId === dto.originStationId);
    const obL1Dest    = obL1Sched.stopTimes.find(s => s.stationId === dto.transitStationId);
    const obL2Origin  = obL2Sched.stopTimes.find(s => s.stationId === dto.transitStationId);
    const obL2Dest    = obL2Sched.stopTimes.find(s => s.stationId === dto.leg2DestinationStationId);
    const retL1Origin = retL1Sched.stopTimes.find(s => s.stationId === dto.returnOriginStationId);
    const retL1Dest   = retL1Sched.stopTimes.find(s => s.stationId === dto.returnTransitStationId);
    const retL2Origin = retL2Sched.stopTimes.find(s => s.stationId === dto.returnTransitStationId);
    const retL2Dest   = retL2Sched.stopTimes.find(s => s.stationId === dto.returnLeg2DestinationStationId);
    if (!obL1Origin || !obL1Dest)   throw new NotFoundException('Outbound leg-1: origin or transit station not found');
    if (!obL2Origin || !obL2Dest)   throw new NotFoundException('Outbound leg-2: transit or destination not found');
    if (!retL1Origin || !retL1Dest) throw new NotFoundException('Return leg-1: origin or transit station not found');
    if (!retL2Origin || !retL2Dest) throw new NotFoundException('Return leg-2: transit or destination not found');

    const passengersData = await this.processRoundTripPassengers(dto.passengers as any[]);
    const { adultCount, childCount } = this.countPassengers(passengersData);
    const nat = passengersData[0]?.nationality;

    const obL2SeatClassId  = dto.leg2SeatClassId        ?? dto.seatClassId;
    const retL1SeatClassId = dto.returnSeatClassId       ?? dto.seatClassId;
    const retL2SeatClassId = dto.returnLeg2SeatClassId   ?? dto.seatClassId;

    const [obL1Fare, obL2Fare, retL1Fare, retL2Fare] = await Promise.all([
      this.calculateFare(dto.scheduleId,          dto.seatClassId,   obL1Origin,  obL1Dest,  nat, adultCount, childCount),
      this.calculateFare(dto.leg2ScheduleId,       obL2SeatClassId,  obL2Origin,  obL2Dest,  nat, adultCount, childCount),
      this.calculateFare(dto.returnScheduleId,     retL1SeatClassId, retL1Origin, retL1Dest, nat, adultCount, childCount),
      this.calculateFare(dto.returnLeg2ScheduleId, retL2SeatClassId, retL2Origin, retL2Dest, nat, adultCount, childCount),
    ]);

    const combinedBase = obL1Fare.totalBaseFareMinor + obL2Fare.totalBaseFareMinor +
                         retL1Fare.totalBaseFareMinor + retL2Fare.totalBaseFareMinor;
    let discountMinor = 0;
    if (dto.promoCode) {
      const promo = await this.prisma.promotion.findUnique({ where: { code: dto.promoCode } });
      if (promo?.active && promo.validUntil > new Date()) {
        discountMinor = promo.percentOff ? Math.round(combinedBase * promo.percentOff / 100) : (promo.amountOffMinor ?? 0);
      }
    }
    const loyaltyMinor    = (dto.loyaltyRedemptionPoints ?? 0) * 10;
    const taxesMinor      = Math.round(combinedBase * 0.05);
    const totalMinor      = Math.max(0, combinedBase - discountMinor - loyaltyMinor + taxesMinor);
    const displayCurrency = dto.displayCurrency || Currency.ETB;
    const displayTotalMinor = displayCurrency !== Currency.ETB
      ? await this.currencyService.convertAmount(totalMinor, Currency.ETB, displayCurrency)
      : totalMinor;

    const makeSeat = (p: any, seatId: string, leg: number, scheduleId: string, fare: Awaited<ReturnType<BookingsService['calculateFare']>>) => ({
      seat: { connect: { id: seatId } },
      leg,
      scheduleId,
      passengerName: p.passengerName,
      dateOfBirth: p.dateOfBirth,
      passengerCategory: p.category,
      idDocumentType: p.idDocumentType,
      passportNumber: p.passportNumber,
      passportCountry: p.passportCountry,
      verifaydaVerified: p.verifaydaVerified,
      verifaydaData: p.verifaydaData,
      fareMinor: p.category === PassengerCategory.ADULT ? fare.baseFareMinor : (fare.paidChildrenCount > 0 ? fare.baseFareMinor : 0),
      displayCurrency,
    });

    const booking = await this.prisma.booking.create({
      data: {
        bookingRef: generateRef(),
        passengerId: dto.passengerId,
        scheduleId: dto.scheduleId,
        status: 'PENDING_PAYMENT',
        bookingType: 'ROUND_TRIP_TRANSIT',
        totalMinor, adultCount, childCount, displayCurrency, displayTotalMinor,
        // Outbound transit leg-2
        leg2ScheduleId: dto.leg2ScheduleId,
        leg2OriginStationId: dto.transitStationId,
        leg2DestinationStationId: dto.leg2DestinationStationId,
        leg2SeatClassId: obL2SeatClassId,
        // Return transit
        returnScheduleId: dto.returnScheduleId,
        returnOriginStationId: dto.returnOriginStationId,
        returnDestinationStationId: dto.returnDestinationStationId,
        returnSeatClassId: retL1SeatClassId,
        returnLeg2ScheduleId: dto.returnLeg2ScheduleId,
        returnLeg2OriginStationId: dto.returnTransitStationId,
        returnLeg2DestStationId: dto.returnLeg2DestinationStationId,
        returnLeg2SeatClassId: retL2SeatClassId,
        returnLegStatus: 'NEITHER_USED',
        seats: {
          create: [
            // Outbound leg-1 (sequence 1)
            ...passengersData.map(p => makeSeat(p, p.outboundSeatId, 1, dto.scheduleId, obL1Fare)),
            // Outbound leg-2 (sequence 2)
            ...passengersData.map(p => makeSeat(p, p.outboundLeg2SeatId ?? p.outboundSeatId, 2, dto.leg2ScheduleId!, obL2Fare)),
            // Return leg-1 (sequence 3)
            ...passengersData.map(p => makeSeat(p, p.returnSeatId, 3, dto.returnScheduleId!, retL1Fare)),
            // Return leg-2 (sequence 4)
            ...passengersData.map(p => makeSeat(p, p.returnLeg2SeatId ?? p.returnSeatId, 4, dto.returnLeg2ScheduleId!, retL2Fare)),
          ],
        },
      } as any,
      include: { seats: { include: { seat: true } }, schedule: { include: { originStation: true, destinationStation: true, train: true } } },
    });

    await Promise.all([
      this.seatsService.confirmSeats(passengersData.map(p => p.outboundSeatId)),
      this.seatsService.confirmSeats(passengersData.map(p => p.outboundLeg2SeatId ?? p.outboundSeatId)),
      this.seatsService.confirmSeats(passengersData.map(p => p.returnSeatId)),
      this.seatsService.confirmSeats(passengersData.map(p => p.returnLeg2SeatId ?? p.returnSeatId)),
    ]);
    this.eventEmitter.emit('booking.created', { booking });

    return {
      ...booking,
      fareBreakdown: {
        outboundLeg1FareMinor: obL1Fare.baseFareMinor,
        outboundLeg2FareMinor: obL2Fare.baseFareMinor,
        returnLeg1FareMinor:   retL1Fare.baseFareMinor,
        returnLeg2FareMinor:   retL2Fare.baseFareMinor,
        adultCount, childCount,
        freeChildrenCount: Math.min(childCount, 1),
        paidChildrenCount: obL1Fare.paidChildrenCount,
        combinedBaseFareMinor: combinedBase,
        discountMinor, loyaltyRedemptionMinor: loyaltyMinor,
        taxesFeesMinor: taxesMinor, totalMinor,
        currency: 'ETB', displayCurrency, displayTotalMinor,
      },
    };
  }

  private async processPassengers(passengers: any[]) {
    const processedPassengers = [];
    for (const passenger of passengers) {
      const dateOfBirth = new Date(passenger.dateOfBirth);
      const age = calculateAge(dateOfBirth);
      const category: PassengerCategory = age < 5 ? PassengerCategory.CHILD : PassengerCategory.ADULT;

      let passengerName = passenger.passengerName;
      let verifaydaVerified = false;
      let verifaydaData: Record<string, any> | undefined;
      let nationality = passenger.nationality;

      if (passenger.idDocumentType === IdDocumentType.NATIONAL_ID && passenger.idDocumentNumber) {
        const verification = await this.verifaydaService.verifyNationalId(passenger.idDocumentNumber);
        if (!verification.verified) throw new BadRequestException(`Verifayda verification failed for ${passenger.passengerName}: ${verification.failureReason}`);
        passengerName = verification.passengerData?.fullName || passengerName;
        verifaydaVerified = true;
        verifaydaData = verification.passengerData?.profileData;
        nationality = nationality || 'Ethiopian';
      } else if (passenger.idDocumentType === IdDocumentType.PASSPORT) {
        if (!passenger.passportNumber || !passenger.passportCountry) throw new BadRequestException(`Passport number and country required for ${passenger.passengerName}`);
        nationality = nationality || (passenger.passportCountry === 'Djibouti' ? 'Djiboutian' : 'Other');
      }

      processedPassengers.push({ ...passenger, passengerName, dateOfBirth, category, verifaydaVerified, verifaydaData, nationality });
    }
    return processedPassengers;
  }

  private async processRoundTripPassengers(passengers: any[]) {
    const processedPassengers = [];
    for (const passenger of passengers) {
      const dateOfBirth = new Date(passenger.dateOfBirth);
      const age = calculateAge(dateOfBirth);
      const category: PassengerCategory = age < 5 ? PassengerCategory.CHILD : PassengerCategory.ADULT;

      let passengerName = passenger.passengerName;
      let verifaydaVerified = false;
      let verifaydaData: Record<string, any> | undefined;
      let nationality = passenger.nationality;

      if (passenger.idDocumentType === IdDocumentType.NATIONAL_ID && passenger.idDocumentNumber) {
        const verification = await this.verifaydaService.verifyNationalId(passenger.idDocumentNumber);
        if (!verification.verified) throw new BadRequestException(`Verifayda verification failed for ${passenger.passengerName}: ${verification.failureReason}`);
        passengerName = verification.passengerData?.fullName || passengerName;
        verifaydaVerified = true;
        verifaydaData = verification.passengerData?.profileData;
        nationality = nationality || 'Ethiopian';
      } else if (passenger.idDocumentType === IdDocumentType.PASSPORT) {
        if (!passenger.passportNumber || !passenger.passportCountry) throw new BadRequestException(`Passport number and country required for ${passenger.passengerName}`);
        nationality = nationality || (passenger.passportCountry === 'Djibouti' ? 'Djiboutian' : 'Other');
      }

      processedPassengers.push({ ...passenger, passengerName, dateOfBirth, category, verifaydaVerified, verifaydaData, nationality });
    }
    return processedPassengers;
  }

  private countPassengers(passengersData: any[]) {
    let adultCount = 0, childCount = 0;
    for (const passenger of passengersData) {
      if (passenger.category === PassengerCategory.ADULT) adultCount++;
      else childCount++;
    }
    return { adultCount, childCount };
  }

  private async calculateFare(
    scheduleId: string,
    seatClassId: string,
    originStop: any,
    destStop: any,
    nationality?: string,
    adultCount = 1,
    childCount = 0,
    promoCode?: string,
    loyaltyRedemptionPoints?: number
  ) {
    const segmentRoute = `${originStop.station.code}-${destStop.station.code}`;
    const baseFareMinor = await this.getBaseFare(scheduleId, seatClassId, segmentRoute, undefined, nationality, originStop.sequence, destStop.sequence);
    
    const adultFareMinor = baseFareMinor * adultCount;
    const paidChildrenCount = Math.max(0, childCount - 1);
    const childFareMinor = baseFareMinor * paidChildrenCount;
    const totalBaseFareMinor = adultFareMinor + childFareMinor;

    let discountMinor = 0;
    if (promoCode) {
      const promo = await this.prisma.promotion.findUnique({ where: { code: promoCode } });
      if (promo?.active && promo.validUntil > new Date()) {
        discountMinor = promo.percentOff ? Math.round(totalBaseFareMinor * promo.percentOff / 100) : (promo.amountOffMinor ?? 0);
      }
    }

    const loyaltyMinor = (loyaltyRedemptionPoints ?? 0) * 10;
    const taxesMinor = Math.round(totalBaseFareMinor * 0.05);
    const totalMinor = Math.max(0, totalBaseFareMinor - discountMinor - loyaltyMinor + taxesMinor);

    return {
      baseFareMinor,
      adultCount,
      adultFareMinor,
      childCount,
      freeChildrenCount: Math.min(childCount, 1),
      paidChildrenCount,
      childFareMinor,
      totalBaseFareMinor,
      discountMinor,
      loyaltyRedemptionMinor: loyaltyMinor,
      taxesFeesMinor: taxesMinor,
      totalMinor
    };
  }

  private async getBaseFare(
    scheduleId: string,
    seatClassId: string,
    segmentRoute?: string,
    fullRoute?: string,
    nationality?: string,
    originStopSeq?: number,
    destStopSeq?: number,
  ): Promise<number> {
    const now = new Date();

    // 1. SegmentFareRule — most specific explicit price
    const schedule = await this.prisma.trainSchedule.findUnique({
      where: { id: scheduleId },
      select: { routeId: true, originStationId: true, destinationStationId: true },
    });

    if (schedule?.routeId && originStopSeq !== undefined && destStopSeq !== undefined) {
      const segmentFare = await this.prisma.segmentFareRule.findFirst({
        where: {
          routeId: schedule.routeId,
          originStopSequence: originStopSeq,
          destinationStopSequence: destStopSeq,
          seatClassId,
          nationality: nationality ?? null,
          validFrom: { lte: now },
          OR: [{ validUntil: null }, { validUntil: { gte: now } }],
        },
      }) ?? (nationality ? await this.prisma.segmentFareRule.findFirst({
        where: {
          routeId: schedule.routeId,
          originStopSequence: originStopSeq,
          destinationStopSequence: destStopSeq,
          seatClassId,
          nationality: null,
          validFrom: { lte: now },
          OR: [{ validUntil: null }, { validUntil: { gte: now } }],
        },
      }) : null);

      if (segmentFare) return segmentFare.baseFareMinor;
    }

    // 2. FareRule table — explicit override rules
    const candidates = await this.prisma.fareRule.findMany({
      where: {
        seatClassId,
        validFrom: { lte: now },
        OR: [{ validUntil: null }, { validUntil: { gte: now } }],
      },
    });
    const bestMatch = this.selectBestFareRule(candidates, scheduleId, segmentRoute, fullRoute, nationality);
    if (bestMatch) return bestMatch.baseFareMinor;

    // 3. FareEngine — distance × rate-per-km from the schedule's route
    if (schedule?.routeId) {
      try {
        const fare = await this.fareEngine.calculate({
          routeId: schedule.routeId,
          originStationId: schedule.originStationId,
          destinationStationId: schedule.destinationStationId,
          seatClassId,
          nationality,
        });
        return fare.baseFarePerPassengerMinor;
      } catch {
        // FareEngine throws if distanceKm is missing; fall through to error
      }
    }

    throw new BadRequestException(
      `No fare configured for this schedule and seat class. Please set up fare rules or route distances.`,
    );
  }

  async getByRef(bookingRef: string) {
    const booking = await this.prisma.booking.findUnique({
      where: { bookingRef },
      include: {
        schedule: { include: { originStation: true, destinationStation: true, train: true } },
        seats: { include: { seat: { include: { coach: { include: { coachType: { include: { seatClasses: true } } } } } } } },
        paymentIntent: true, ticket: true,
      },
    });
    if (!booking) throw new NotFoundException('Booking not found');
    return {
      id: booking.id, bookingRef: booking.bookingRef, status: booking.status,
      totalMinor: booking.totalMinor, currency: 'ETB',
      adultCount: booking.adultCount, childCount: booking.childCount,
      displayCurrency: booking.displayCurrency, displayTotalMinor: booking.displayTotalMinor ?? undefined,
      bookingType: booking.bookingType,
      returnLegStatus: (booking as any).returnLegStatus ?? null,
      outboundBoardedAt: (booking as any).outboundBoardedAt ?? null,
      returnBoardedAt: (booking as any).returnBoardedAt ?? null,
      contactEmail: booking.contactEmail,
      contactPhone: booking.contactPhone,
      createdAt: booking.createdAt,
      schedule: {
        id: booking.schedule.id,
        trainNumber: booking.schedule.train.number,
        trainName: booking.schedule.train.name,
        origin: { id: booking.schedule.originStation.id, name: booking.schedule.originStation.name, code: booking.schedule.originStation.code, city: booking.schedule.originStation.city },
        destination: { id: booking.schedule.destinationStation.id, name: booking.schedule.destinationStation.name, code: booking.schedule.destinationStation.code, city: booking.schedule.destinationStation.city },
        departureAt: booking.schedule.departureAt, arrivalAt: booking.schedule.arrivalAt,
      },
      passengers: booking.seats?.map((bs: any) => ({
        fullName: bs.passengerName,
        category: bs.passengerCategory,
        leg: bs.leg ?? 1,
        fareMinor: bs.fareMinor,
        verifaydaVerified: bs.verifaydaVerified,
        seat: {
          id: bs.seat.id,
          number: bs.seat.seatNumber,
          coach: bs.seat.coach.number,
          coachId: bs.seat.coach.id,
          seatClass: bs.seat.coach.coachType?.seatClasses?.[0]?.name ?? null,
        },
      })),
      payment: booking.paymentIntent ? { method: booking.paymentIntent.method, status: booking.paymentIntent.status } : undefined,
      ticket: booking.ticket ? { id: booking.ticket.id, qrPayload: booking.ticket.qrPayload, barcodePayload: booking.ticket.barcodePayload, status: booking.ticket.status } : undefined,
    };
  }

  async modify(dto: ModifyBookingDto) {
    const booking = await this.prisma.booking.findUnique({ where: { bookingRef: dto.bookingRef }, include: { seats: true, schedule: true } });
    if (!booking) throw new NotFoundException('Booking not found');
    if (booking.status !== 'CONFIRMED') throw new BadRequestException('Only confirmed bookings can be modified');
    if (booking.schedule.departureAt < new Date()) throw new BadRequestException('Cannot modify past bookings');

    const oldSeats = booking.seats.map(s => s.seatId);
    await this.prisma.bookingModification.create({
      data: { bookingId: booking.id, modifiedBy: booking.passengerId, modificationType: 'SEAT_CHANGE', oldData: { scheduleId: booking.scheduleId, seatIds: oldSeats }, newData: { scheduleId: dto.newScheduleId, seatIds: dto.newSeatIds }, fareAdjustment: 0, reason: dto.reason },
    });
    await this.seatsService.releaseSeats(oldSeats);
    await this.seatsService.confirmSeats(dto.newSeatIds);
    return { modified: true, bookingRef: dto.bookingRef };
  }

  async cancel(bookingRef: string, reason?: string) {
    const booking = await this.prisma.booking.findUnique({ where: { bookingRef }, include: { seats: true, paymentIntent: true } });
    if (!booking) throw new NotFoundException('Booking not found');
    if (booking.status === 'CANCELLED') throw new BadRequestException('Booking already cancelled');
    const refundAmount = booking.status === 'CONFIRMED' ? Math.floor(booking.totalMinor * 0.8) : 0;
    await this.prisma.bookingCancellation.create({ data: { bookingId: booking.id, cancelledBy: booking.passengerId, reason, refundAmount, refundMethod: booking.paymentIntent?.method ?? 'ORIGINAL', refundStatus: 'PENDING' } });
    await this.seatsService.releaseSeats(booking.seats.map((s) => s.seatId));
    await this.prisma.booking.update({ where: { bookingRef }, data: { status: 'CANCELLED' } });
    this.eventEmitter.emit('booking.cancelled', { booking, refundAmount });
    return { cancelled: true, refundAmount: refundAmount / 100, currency: 'ETB' };
  }

  async update(id: string, dto: any) {
    const booking = await this.prisma.booking.findUnique({ where: { id } });
    if (!booking) throw new NotFoundException('Booking not found');
    return this.prisma.booking.update({
      where: { id },
      data: {
        status: dto.status || booking.status,
        totalMinor: dto.totalMinor !== undefined ? dto.totalMinor : booking.totalMinor,
        displayCurrency: dto.displayCurrency || booking.displayCurrency,
        displayTotalMinor: dto.displayTotalMinor !== undefined ? dto.displayTotalMinor : booking.displayTotalMinor,
      },
      include: {
        schedule: { include: { originStation: true, destinationStation: true, train: true } },
        paymentIntent: true,
        seats: { include: { seat: true } },
      },
    });
  }

  async delete(id: string) {
    const booking = await this.prisma.booking.findUnique({ where: { id }, include: { seats: true } });
    if (!booking) throw new NotFoundException('Booking not found');
    
    await this.seatsService.releaseSeats(booking.seats.map((s) => s.seatId));
    
    await this.prisma.bookingSeat.deleteMany({ where: { bookingId: id } });
    await this.prisma.booking.delete({ where: { id } });
    
    return { deleted: true, bookingRef: booking.bookingRef };
  }

  async checkBookingUsage(id: string) {
    const booking = await this.prisma.booking.findUnique({ where: { id } });
    if (!booking) throw new NotFoundException('Booking not found');

    const [ticketCount, paymentIntentCount, modificationsCount, cancellationCount] = await Promise.all([
      this.prisma.ticket.count({ where: { bookingId: id } }),
      this.prisma.paymentIntent.count({ where: { bookingId: id } }),
      this.prisma.bookingModification.count({ where: { bookingId: id } }),
      this.prisma.bookingCancellation.count({ where: { bookingId: id } }),
    ]);

    const usage = [];
    if (ticketCount > 0) usage.push('Ticket(s)');
    if (paymentIntentCount > 0) usage.push('Payment record(s)');
    if (modificationsCount > 0) usage.push('Modification history');
    if (cancellationCount > 0) usage.push('Cancellation record(s)');

    return {
      isInUse: usage.length > 0,
      affectedModules: usage,
    };
  }

  @Cron(CronExpression.EVERY_MINUTE)
  async expirePendingBookings() {
    const cutoff = new Date(Date.now() - 20 * 60 * 1000);
    const expired = await this.prisma.booking.findMany({ where: { status: 'PENDING_PAYMENT', createdAt: { lt: cutoff } }, include: { seats: true } });
    for (const b of expired) {
      await this.seatsService.releaseSeats(b.seats.map((s) => s.seatId));
      await this.prisma.booking.update({ where: { id: b.id }, data: { status: 'CANCELLED' } });
    }
  }

  // Mark round-trip bookings where the return train has departed but the return leg
  // was never scanned. Runs every minute; only acts on CONFIRMED bookings whose
  // returnSchedule.departureAt is in the past and returnBoardedAt is still null.
  @Cron(CronExpression.EVERY_MINUTE)
  async markReturnLegNoShows() {
    const now = new Date();
    const graceCutoff = new Date(now.getTime() - 30 * 60 * 1000);

    const candidates = await this.prisma.booking.findMany({
      where: {
        bookingType: 'ROUND_TRIP',
        status: 'CONFIRMED',
        returnLegStatus: 'NEITHER_USED' as any,
        outboundBoardedAt: { not: null },
        returnBoardedAt: null,
        returnScheduleId: { not: null },
      },
      include: { returnSchedule: { select: { departureAt: true } } },
    } as any);

    for (const b of candidates) {
      const returnDep: Date | undefined = (b as any).returnSchedule?.departureAt;
      if (returnDep && returnDep < graceCutoff) {
        await this.prisma.booking.update({
          where: { id: b.id },
          data: { returnLegStatus: 'OUTBOUND_ONLY' } as any,
        });
      }
    }
  }

  private selectBestFareRule(
    candidates: any[],
    scheduleId: string,
    segmentRoute?: string,
    fullRoute?: string,
    nationality?: string,
  ): any | null {
    const priorities = [
      { tripId: scheduleId, route: segmentRoute, nationality },
      { tripId: scheduleId, route: segmentRoute, nationality: null },
      { tripId: scheduleId, route: fullRoute, nationality },
      { tripId: scheduleId, route: fullRoute, nationality: null },
      { tripId: scheduleId, route: null, nationality },
      { tripId: scheduleId, route: null, nationality: null },
      { tripId: null, route: segmentRoute, nationality },
      { tripId: null, route: segmentRoute, nationality: null },
      { tripId: null, route: fullRoute, nationality },
      { tripId: null, route: fullRoute, nationality: null },
      { tripId: null, route: null, nationality },
      { tripId: null, route: null, nationality: null },
    ];

    for (const priority of priorities) {
      const match = candidates.find(
        (c) =>
          c.tripId === priority.tripId &&
          c.route === priority.route &&
          c.nationality === priority.nationality,
      );
      if (match) return match;
    }

    return null;
  }
}
