import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
import { CurrencyService } from '../currency/currency.service';
import { CreatePackageDto, BookPackageDto, UpdatePriceTierDto, CreatePriceTierDto, CreateInquiryDto } from './packages.dto';
import { Currency } from '@prisma/client';
import { BookingsService } from '../bookings/bookings.service';
import { GuestBookingService } from '../bookings/guest-booking.service';
import { AuditService } from '../../common/audit.service';
import { computePaymentDeadline, CUTOFF_MINUTES } from '../../common/utils/payment-deadline.utils';

/** Package-specific fare rules */
const PKG_MAX_ADULTS = 5;
const PKG_CHILDREN_PER_ADULT = 5; // max 5 children per adult

/**
 * First child per adult travels FREE (no seat).
 * Additional children beyond one per adult pay the full adult fare.
 */
function calculatePackageFareBreakdown(
  priceMinor: number,
  isRoundTrip: boolean,
  adultCount: number,
  childCount: number,
) {
  const multiplier = isRoundTrip ? 2 : 1;
  const adultFareMinor = priceMinor * multiplier;
  const freeChildren = Math.min(childCount, adultCount);
  const paidChildren = Math.max(0, childCount - adultCount);
  const totalMinor = adultCount * adultFareMinor + paidChildren * adultFareMinor;
  return { adultFareMinor, freeChildren, paidChildren, totalMinor, multiplier };
}

function deriveAge(dateOfBirth: string | Date): number {
  const today = new Date();
  const dob = new Date(dateOfBirth);
  let age = today.getFullYear() - dob.getFullYear();
  if (today < new Date(today.getFullYear(), dob.getMonth(), dob.getDate())) age--;
  return age;
}

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

@Injectable()
export class PackagesService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly currencyService: CurrencyService,
    private readonly bookingsService: BookingsService,
    private readonly guestBookingService: GuestBookingService,
    private readonly auditService: AuditService,
  ) {}

  async getBookingContext(packageId: string, tierId: string, adultCount: number, childCount = 0) {
    const pkg = await this.prisma.travelPackage.findUnique({
      where: { id: packageId },
      include: {
        priceTiers: true,
        outboundSchedule: {
          include: {
            originStation: true,
            destinationStation: true,
            coachAssignments: { include: { coach: { include: { coachType: { include: { seatClasses: true } } } } } },
          },
        },
        returnSchedule: { include: { originStation: true, destinationStation: true } },
      },
    });
    if (!pkg || pkg.status !== 'ACTIVE') throw new NotFoundException('Package not available');
    const tier = pkg.priceTiers.find((t) => t.id === tierId);
    if (!tier) throw new NotFoundException('Price tier not found');

    if (adultCount < 1) throw new BadRequestException('At least one adult passenger required');
    if (adultCount > PKG_MAX_ADULTS) throw new BadRequestException(`Maximum ${PKG_MAX_ADULTS} adults allowed per package booking`);
    const maxChildren = adultCount * PKG_CHILDREN_PER_ADULT;
    if (childCount > maxChildren) throw new BadRequestException(`Maximum ${PKG_CHILDREN_PER_ADULT} children per adult (${maxChildren} for ${adultCount} adult${adultCount !== 1 ? 's' : ''}) allowed per package booking`);

    const isRoundTrip = !!pkg.returnScheduleId;
    const { adultFareMinor, freeChildren, paidChildren, totalMinor } = calculatePackageFareBreakdown(
      tier.priceMinor, isRoundTrip, adultCount, childCount,
    );

    // Only adults and paid children need seats; free children travel without a seat
    const seatsNeeded = adultCount + paidChildren;
    const passengerCount = adultCount + childCount;
    // Re-fetch tier from DB to get accurate live counts
    const liveTier = await this.prisma.packagePriceTier.findUnique({ where: { id: tierId } });
    if (!liveTier) throw new NotFoundException('Price tier not found');
    const remaining = liveTier.availableSeats - liveTier.bookedSeats;
    if (seatsNeeded > remaining)
      throw new BadRequestException(`Only ${remaining} seat(s) remaining in the ${tier.label} tier`);

    // Resolve the seatClassId and coachTypeId that matches this tier's seatType from the outbound schedule coaches
    let seatClassId: string | null = tier.seatClassId ?? null;
    let seatClassName: string | null = null;
    let coachTypeId: string | null = null;
    for (const a of pkg.outboundSchedule.coachAssignments) {
      const sc = seatClassId
        ? a.coach.coachType?.seatClasses?.find((s: any) => s.id === seatClassId)
        : a.coach.coachType?.seatClasses?.find(
            (s: any) => s.name.toLowerCase().includes(tier.seatType.toLowerCase()) ||
                        tier.seatType.toLowerCase().includes(s.name.toLowerCase()),
          );
      if (sc) { seatClassId = sc.id; seatClassName = sc.name; coachTypeId = a.coach.coachTypeId ?? a.coach.coachType?.id ?? null; break; }
    }
    if (!coachTypeId && pkg.outboundSchedule.coachAssignments.length > 0) {
      const first = pkg.outboundSchedule.coachAssignments[0];
      coachTypeId = first.coach.coachTypeId ?? first.coach.coachType?.id ?? null;
    }

    return {
      packageId: pkg.id,
      packageName: pkg.name,
      priceTierId: tier.id,
      tierLabel: tier.label,
      seatType: tier.seatType,
      seatClassId,
      seatClassName,
      coachTypeId,
      adultCount,
      childCount,
      passengerCount,
      isRoundTrip,
      pricePerAdultMinor: adultFareMinor,
      pricePerChildMinor: adultFareMinor, // paid children pay full adult fare
      freeChildrenCount: freeChildren,
      paidChildrenCount: paidChildren,
      childFareNote: `First child per adult travels free (no seat); additional children pay full adult fare`,
      maxAdults: PKG_MAX_ADULTS,
      maxChildren: adultCount * PKG_CHILDREN_PER_ADULT,
      totalMinor,
      currency: tier.currency,
      remainingSeats: remaining,
      outboundSchedule: {
        scheduleId: pkg.outboundScheduleId,
        originStationId: pkg.outboundSchedule.originStationId,
        destinationStationId: pkg.outboundSchedule.destinationStationId,
        departureAt: pkg.outboundSchedule.departureAt,
        arrivalAt: pkg.outboundSchedule.arrivalAt,
        originStation: pkg.outboundSchedule.originStation,
        destinationStation: pkg.outboundSchedule.destinationStation,
      },
      returnSchedule: pkg.returnSchedule ? {
        scheduleId: pkg.returnScheduleId,
        originStationId: pkg.returnSchedule.originStationId,
        destinationStationId: pkg.returnSchedule.destinationStationId,
        departureAt: pkg.returnSchedule.departureAt,
        arrivalAt: pkg.returnSchedule.arrivalAt,
        originStation: pkg.returnSchedule.originStation,
        destinationStation: pkg.returnSchedule.destinationStation,
      } : null,
      includedServices: pkg.includedServices,
      busTransferIncluded: pkg.busTransferIncluded,
      busTransferRoute: pkg.busTransferRoute,
    };
  }

  async createInquiry(dto: CreateInquiryDto) {
    return this.prisma.packageInquiry.create({
      data: {
        packageId: dto.packageId,
        priceTierId: dto.priceTierId ?? null,
        travelerCount: dto.travelerCount,
        contactName: dto.contactName,
        contactEmail: dto.contactEmail ?? null,
        contactPhone: dto.contactPhone ?? null,
        notes: dto.notes ?? null,
        enquiredAt: new Date(),
      },
      include: { package: { select: { id: true, name: true, code: true } }, priceTier: { select: { id: true, label: true } } },
    });
  }

  async listInquiries({ packageId, status, page = 1, pageSize = 20 }: { packageId?: string; status?: string; page?: number; pageSize?: number }) {
    const where: any = {};
    if (packageId) where.packageId = packageId;
    if (status) where.status = status;
    const skip = (page - 1) * pageSize;
    const [items, total] = await Promise.all([
      this.prisma.packageInquiry.findMany({
        where,
        include: { package: { select: { id: true, name: true, code: true } }, priceTier: { select: { id: true, label: true, priceMinor: true } } },
        orderBy: { enquiredAt: 'desc' },
        skip,
        take: pageSize,
      }),
      this.prisma.packageInquiry.count({ where }),
    ]);
    return { items, total, page, pageSize };
  }

  async updateInquiryStatus(id: string, status: string) {
    const inquiry = await this.prisma.packageInquiry.findUnique({ where: { id } });
    if (!inquiry) throw new NotFoundException('Inquiry not found');
    return this.prisma.packageInquiry.update({ where: { id }, data: { status } });
  }

  async deleteInquiry(id: string) {
    const inquiry = await this.prisma.packageInquiry.findUnique({ where: { id } });
    if (!inquiry) throw new NotFoundException('Inquiry not found');
    await this.prisma.packageInquiry.delete({ where: { id } });
    return { deleted: true };
  }

  listActive() {
    const now = new Date();
    return this.prisma.travelPackage.findMany({
      where: { status: 'ACTIVE', validUntil: { gte: now } },
      include: {
        priceTiers: true,
        outboundSchedule: { include: { originStation: true, destinationStation: true } },
        returnSchedule: { include: { originStation: true, destinationStation: true } },
      },
      orderBy: { validFrom: 'asc' },
    }).then(pkgs => pkgs.map(p => ({ ...p, journeyType: p.returnScheduleId ? 'ROUND_TRIP' : 'ONE_WAY' })));
  }

  async getById(id: string) {
    const pkg = await this.prisma.travelPackage.findUnique({
      where: { id },
      include: {
        priceTiers: { include: { seatClass: { include: { coachType: true } } } },
        outboundSchedule: {
          include: {
            originStation: true,
            destinationStation: true,
            train: true,
            stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } },
          },
        },
        returnSchedule: { include: { originStation: true, destinationStation: true, train: true } },
      },
    });
    if (!pkg) throw new NotFoundException('Package not found');

    // Build departure station list from live route stops when a routeId exists,
    // falling back to the schedule's own stopTimes (already included in the query).
    let routeStops: { sequence: number; station: any }[] = [];
    if (pkg.outboundSchedule.routeId) {
      const stops = await this.prisma.routeStop.findMany({
        where: { routeId: pkg.outboundSchedule.routeId },
        orderBy: { sequence: 'asc' },
      });
      if (stops.length > 0) {
        const stationIds = stops.map((s) => s.stationId);
        const stations = await this.prisma.station.findMany({ where: { id: { in: stationIds } } });
        const stationMap = Object.fromEntries(stations.map((s) => [s.id, s]));
        routeStops = stops.map((s) => ({ sequence: s.sequence, station: stationMap[s.stationId] }));
      }
    }
    // Fall back to the schedule's own TripStopTimes when RouteStop table has no rows
    // for this route (e.g. route exists but stops were never seeded).
    if (routeStops.length === 0) {
      routeStops = (pkg.outboundSchedule.stopTimes ?? [])
        .filter((st: any) => st.station)
        .map((st: any) => ({ sequence: st.sequence, station: st.station }));
    }

    return {
      ...pkg,
      journeyType: pkg.returnScheduleId ? 'ROUND_TRIP' : 'ONE_WAY',
      outboundSchedule: { ...pkg.outboundSchedule, routeStops },
    };
  }

  async create(dto: CreatePackageDto) {
    const pkg = await this.prisma.travelPackage.create({
      data: {
        code: dto.code,
        name: dto.name,
        description: dto.description,
        outboundScheduleId: dto.outboundScheduleId,
        returnScheduleId: dto.returnScheduleId,
        originStationId: dto.originStationId,
        destinationStationId: dto.destinationStationId,
        boardingTime: new Date(dto.boardingTime),
        departureTime: new Date(dto.departureTime),
        arrivalTime: new Date(dto.arrivalTime),
        totalCapacity: dto.totalCapacity,
        coachConfiguration: dto.coachConfiguration,
        includedServices: dto.includedServices,
        busTransferIncluded: dto.busTransferIncluded ?? false,
        busTransferRoute: dto.busTransferRoute,
        validFrom: new Date(dto.validFrom),
        validUntil: new Date(dto.validUntil),
        status: 'DRAFT',
        priceTiers: { create: dto.priceTiers },
      },
      include: { priceTiers: true },
    });
    await this.auditService.log({ action: 'CREATE', entityType: 'Package', entityId: pkg.id, newData: { code: pkg.code, name: pkg.name } });
    return pkg;
  }

  async update(id: string, dto: Partial<CreatePackageDto>) {
    const pkg = await this.prisma.travelPackage.findUnique({ where: { id } });
    if (!pkg) throw new NotFoundException('Package not found');
    const updated = await this.prisma.travelPackage.update({
      where: { id },
      data: {
        ...(dto.code && { code: dto.code }),
        ...(dto.name && { name: dto.name }),
        ...(dto.description !== undefined && { description: dto.description }),
        ...(dto.outboundScheduleId && { outboundScheduleId: dto.outboundScheduleId }),
        ...(dto.returnScheduleId && { returnScheduleId: dto.returnScheduleId }),
        ...(dto.originStationId && { originStationId: dto.originStationId }),
        ...(dto.destinationStationId && { destinationStationId: dto.destinationStationId }),
        ...(dto.boardingTime && { boardingTime: new Date(dto.boardingTime) }),
        ...(dto.departureTime && { departureTime: new Date(dto.departureTime) }),
        ...(dto.arrivalTime && { arrivalTime: new Date(dto.arrivalTime) }),
        ...(dto.totalCapacity && { totalCapacity: dto.totalCapacity }),
        ...(dto.coachConfiguration !== undefined && { coachConfiguration: dto.coachConfiguration }),
        ...(dto.includedServices && { includedServices: dto.includedServices }),
        ...(dto.busTransferIncluded !== undefined && { busTransferIncluded: dto.busTransferIncluded }),
        ...(dto.busTransferRoute !== undefined && { busTransferRoute: dto.busTransferRoute }),
        ...(dto.validFrom && { validFrom: new Date(dto.validFrom) }),
        ...(dto.validUntil && { validUntil: new Date(dto.validUntil) }),
      },
      include: { priceTiers: true },
    });
    await this.auditService.log({ action: 'UPDATE', entityType: 'Package', entityId: id, newData: { code: dto.code, name: dto.name } });
    return updated;
  }

  async addTier(packageId: string, dto: CreatePriceTierDto) {
    const pkg = await this.prisma.travelPackage.findUnique({ where: { id: packageId } });
    if (!pkg) throw new NotFoundException('Package not found');
    return this.prisma.packagePriceTier.create({ data: { ...dto, packageId } });
  }

  async updateTier(tierId: string, dto: UpdatePriceTierDto) {
    const tier = await this.prisma.packagePriceTier.findUnique({ where: { id: tierId } });
    if (!tier) throw new NotFoundException('Price tier not found');
    return this.prisma.packagePriceTier.update({ where: { id: tierId }, data: dto });
  }

  async deleteTier(tierId: string) {
    const tier = await this.prisma.packagePriceTier.findUnique({ where: { id: tierId } });
    if (!tier) throw new NotFoundException('Price tier not found');
    if (tier.bookedSeats > 0) throw new BadRequestException('Cannot delete a tier that has bookings');
    return this.prisma.packagePriceTier.delete({ where: { id: tierId } });
  }

  async remove(id: string, cascade = false) {
    const pkg = await this.prisma.travelPackage.findUnique({ where: { id } });
    if (!pkg) throw new NotFoundException('Package not found');

    const packageBookings = await this.prisma.packageBooking.findMany({
      where: { packageId: id },
      select: { id: true, status: true },
    });

    if (!cascade) {
      const hasActive = packageBookings.some(b => b.status === 'PENDING_PAYMENT' || b.status === 'CONFIRMED');
      if (hasActive) throw new BadRequestException('Cannot delete a package with active bookings. Use cascade=true to force delete.');
    }

    const pbIds = packageBookings.map(b => b.id);
    if (pbIds.length > 0) {
      await this.prisma.packageBookingPassenger.deleteMany({ where: { bookingId: { in: pbIds } } });
      await this.prisma.packagePaymentIntent.deleteMany({ where: { packageBookingId: { in: pbIds } } });
      await this.prisma.packageBooking.deleteMany({ where: { id: { in: pbIds } } });
    }

    // PackageInquiry references packageId and priceTierId
    await this.prisma.packageInquiry.deleteMany({ where: { packageId: id } });
    await this.prisma.packagePriceTier.deleteMany({ where: { packageId: id } });
    await this.prisma.travelPackage.delete({ where: { id } });
    await this.auditService.log({ action: 'DELETE', entityType: 'Package', entityId: id });
    return { deleted: true };
  }

  async activate(id: string) {
    const pkg = await this.prisma.travelPackage.findUnique({ where: { id } });
    if (!pkg) throw new NotFoundException('Package not found');
    const activated = await this.prisma.travelPackage.update({ where: { id }, data: { status: 'ACTIVE' } });
    await this.auditService.log({ action: 'UPDATE', entityType: 'Package', entityId: id, newData: { status: 'ACTIVE' } });
    return activated;
  }

  async deactivate(id: string) {
    const pkg = await this.prisma.travelPackage.findUnique({ where: { id } });
    if (!pkg) throw new NotFoundException('Package not found');
    const deactivated = await this.prisma.travelPackage.update({ where: { id }, data: { status: 'DRAFT' } });
    await this.auditService.log({ action: 'UPDATE', entityType: 'Package', entityId: id, newData: { status: 'DRAFT' } });
    return deactivated;
  }

  async book(dto: BookPackageDto, passengerId?: string) {
    const pkg = await this.prisma.travelPackage.findUnique({
      where: { id: dto.packageId },
      include: {
        priceTiers: true,
        outboundSchedule: { select: { departureAt: true, route: { select: { checkinMinutesBefore: true } } } },
      },
    });
    if (!pkg) throw new NotFoundException('Package not found');
    if (pkg.status !== 'ACTIVE') throw new BadRequestException('Package is not available for booking');

    const tier = pkg.priceTiers.find((t) => t.id === dto.priceTierId);
    if (!tier) throw new NotFoundException('Price tier not found');

    // Derive adult/child counts from the passengers array (dateOfBirth-based)
    let adultCount = 0, childCount = 0;
    for (const p of dto.passengers) {
      if (p.dateOfBirth && deriveAge(p.dateOfBirth) < 5) childCount++;
      else adultCount++;
    }
    // Allow explicit override from mobile app (e.g. when dateOfBirth is not provided per passenger)
    if (dto.adultCount !== undefined) adultCount = dto.adultCount;
    if (dto.childCount !== undefined) childCount = dto.childCount;

    if (adultCount < 1) throw new BadRequestException('At least one adult passenger required');
    if (adultCount > PKG_MAX_ADULTS) throw new BadRequestException(`Maximum ${PKG_MAX_ADULTS} adults allowed per package booking`);
    const maxChildrenBook = adultCount * PKG_CHILDREN_PER_ADULT;
    if (childCount > maxChildrenBook) throw new BadRequestException(`Maximum ${PKG_CHILDREN_PER_ADULT} children per adult (${maxChildrenBook} for ${adultCount} adult${adultCount !== 1 ? 's' : ''}) allowed per package booking`);

    const passengerCount = adultCount + childCount;
    const isRoundTrip = !!pkg.returnScheduleId;
    const { adultFareMinor, freeChildren, paidChildren, totalMinor } = calculatePackageFareBreakdown(
      tier.priceMinor, isRoundTrip, adultCount, childCount,
    );
    // Only adults and paid children need seats; free children travel without a seat
    const seatsNeeded = adultCount + paidChildren;
    const displayCurrency = (dto.displayCurrency as Currency) ?? Currency.ETB;
    const displayTotalMinor =
      displayCurrency !== Currency.ETB
        ? await this.currencyService.convertAmount(totalMinor, Currency.ETB, displayCurrency)
        : totalMinor;

    const [booking] = await this.prisma.$transaction(async (tx) => {
      // Re-fetch tier inside transaction for race-condition-safe availability check
      const freshTier = await tx.packagePriceTier.findUnique({ where: { id: dto.priceTierId } });
      if (!freshTier) throw new NotFoundException('Price tier not found');
      const remaining = freshTier.availableSeats - freshTier.bookedSeats;
      if (seatsNeeded > remaining) {
        throw new BadRequestException(`Only ${remaining} seats remaining in the ${tier.label} tier`);
      }

      return Promise.all([
        tx.packageBooking.create({
        data: {
          bookingRef: generateRef(),
          packageId: dto.packageId,
          priceTierId: dto.priceTierId,
          passengerId: passengerId ?? null,
          contactEmail: dto.contactEmail,
          contactPhone: dto.contactPhone,
          promoCode: dto.promoCode,
          passengerCount,
          adultCount,
          childCount,
          totalMinor: displayTotalMinor,
          currency: displayCurrency,
          displayCurrency,
          displayTotalMinor,
          status: 'PENDING_PAYMENT',
          passengers: {
            create: dto.passengers.map((p) => ({
              passengerName: p.passengerName,
              dateOfBirth: p.dateOfBirth ? new Date(p.dateOfBirth) : undefined,
              idDocumentType: p.idDocumentType as any,
              idDocumentNumber: p.idDocumentNumber,
              passportNumber: p.passportNumber,
              passportCountry: p.passportCountry,
            })),
          },
        },
        include: {
          passengers: true,
          priceTier: true,
          package: {
            include: {
              outboundSchedule: { include: { originStation: true, destinationStation: true } },
              returnSchedule: { include: { originStation: true, destinationStation: true } },
            },
          },
        },
        }),
        tx.packagePriceTier.update({
          where: { id: dto.priceTierId },
          data: {
            bookedSeats: { increment: seatsNeeded },
            availableSeats: { decrement: seatsNeeded },
          },
        }),
      ]);
    });

    // Extend the seatmap SeatHold (if one was passed) to the payment deadline so the
    // specific seat remains visually reserved on the seatmap during the full payment
    // window — matching the behaviour of normal bookings (which call confirmSeats).
    if (dto.holdId) {
      const dep = (pkg as any).outboundSchedule?.departureAt as Date | undefined;
      if (dep) {
        const checkinMinutes = (pkg as any).outboundSchedule?.route?.checkinMinutesBefore ?? CUTOFF_MINUTES;
        const paymentDeadline = computePaymentDeadline(booking.createdAt as Date, dep, checkinMinutes);
        const hold = await this.prisma.seatHold.findUnique({
          where: { id: dto.holdId },
          select: { expiresAt: true },
        });
        if (hold && paymentDeadline > hold.expiresAt) {
          await this.prisma.seatHold.update({
            where: { id: dto.holdId },
            data: { expiresAt: paymentDeadline },
          });
        }
      }
    }

    return {
      ...booking,
      fareBreakdown: {
        isRoundTrip,
        adultCount,
        adultFareMinor,
        childCount,
        freeChildrenCount: freeChildren,
        paidChildrenCount: paidChildren,
        paidChildFareMinor: adultFareMinor,
        childFareNote: `First child per adult travels free (no seat); additional children pay full adult fare`,
        totalMinor,
        currency: booking.displayCurrency,
        displayCurrency,
        displayTotalMinor,
      },
    };
  }

  getMyBookings(passengerId: string) {
    return this.prisma.packageBooking.findMany({
      where: { passengerId },
      include: { package: true, priceTier: true, passengers: true, paymentIntent: true },
      orderBy: { createdAt: 'desc' },
    });
  }

  async getBookingByRef(bookingRef: string) {
    const booking = await this.prisma.packageBooking.findUnique({
      where: { bookingRef },
      include: {
        package: {
          include: {
            outboundSchedule: { include: { originStation: true, destinationStation: true } },
            returnSchedule: { include: { originStation: true, destinationStation: true } },
          },
        },
        priceTier: true,
        passengers: true,
        paymentIntent: true,
      },
    });
    if (!booking) throw new NotFoundException('Package booking not found');
    return booking;
  }

  async listBookings({ packageId, status, page = 1, pageSize = 20 }: { packageId?: string; status?: string; page?: number; pageSize?: number }) {
    const where: any = {};
    if (packageId) where.packageId = packageId;
    if (status) where.status = status;
    const skip = (page - 1) * pageSize;
    const [items, total] = await Promise.all([
      this.prisma.packageBooking.findMany({
        where,
        include: {
          package: { select: { id: true, name: true, code: true } },
          priceTier: { select: { id: true, label: true, seatType: true } },
          passengers: true,
          paymentIntent: true,
        },
        orderBy: { createdAt: 'desc' },
        skip,
        take: pageSize,
      }),
      this.prisma.packageBooking.count({ where }),
    ]);
    return { items, total, page, pageSize, totalPages: Math.ceil(total / pageSize) };
  }

  async listAll(page = 1, pageSize = 20) {
    const skip = (page - 1) * pageSize;
    const [items, total] = await Promise.all([
      this.prisma.travelPackage.findMany({
        skip,
        take: pageSize,
        include: {
          priceTiers: true,
          outboundSchedule: { include: { originStation: true, destinationStation: true } },
          returnSchedule: { include: { originStation: true, destinationStation: true } },
        },
        orderBy: { createdAt: 'desc' },
      }),
      this.prisma.travelPackage.count(),
    ]);
    return { items, total, page, pageSize };
  }
}
