import {
  Injectable,
  Logger,
  NotFoundException,
  BadRequestException,
} from "@nestjs/common";
import { PrismaService } from "../../common/prisma.service";
import { SeatsService } from "../seats/seats.service";
import { TicketsService } from "../tickets/tickets.service";
import { EventEmitter2 } from "@nestjs/event-emitter";
import {
  Prisma,
  PaymentIntentStatus,
  PaymentMethodType,
  PaymentRegion,
} from "@prisma/client";
import {
  InitiatePaymentDto,
  RefundDto,
  AddPaymentMethodDto,
  InitiateResponseDto,
  IntentStatusDto,
  PaymentRegionEnum,
} from "./payments.dto";
import { PaymentEventDto, MarkPaidResponseDto } from "./internal-payments.dto";
import { PaymentClientService } from "./payment-client.service";
import {
  PaymentService as PaymentServiceEnum,
  PaymentReferenceType,
  PaymentIntentSnapshot,
  ProviderMethod,
  ClientAction,
  ProviderPaymentStatus,
} from "@edr/types";

const NON_TERMINAL_STATUSES: PaymentIntentStatus[] = [
  PaymentIntentStatus.REQUIRES_ACTION,
  PaymentIntentStatus.PROCESSING,
  PaymentIntentStatus.SUCCEEDED,
];

@Injectable()
export class PaymentsService {
  private readonly logger = new Logger(PaymentsService.name);
  private readonly walletDemoAutoSucceed = true;

  private readonly waafiDemoTrustReturn = true;

  constructor(
    private prisma: PrismaService,
    private seatsService: SeatsService,
    private ticketsService: TicketsService,
    private eventEmitter: EventEmitter2,
    private paymentClient: PaymentClientService,
  ) {}

  async getAll(filters: {
    search?: string;
    status?: string;
    method?: string;
    page?: number;
    pageSize?: number;
  }) {
    const { search, status, method, page = 1, pageSize = 10 } = filters;
    const skip = (page - 1) * pageSize;

    const where: any = {};
    if (search) {
      where.OR = [
        { id: { contains: search, mode: "insensitive" } },
        { booking: { bookingRef: { contains: search, mode: "insensitive" } } },
      ];
    }
    if (status) {
      where.status = status;
    }
    if (method) {
      where.method = method;
    }

    const [items, total] = await Promise.all([
      this.prisma.paymentIntent.findMany({
        where,
        include: { booking: true },
        skip,
        take: pageSize,
        orderBy: { createdAt: "desc" },
      }),
      this.prisma.paymentIntent.count({ where }),
    ]);

    return {
      items: items.map((item) => ({
        id: item.id,
        reference: item.id.substring(0, 8),
        bookingId: item.bookingId,
        booking: { bookingRef: item.booking?.bookingRef },
        amountMinor: item.amountMinor,
        currency: item.currency,
        method: item.method,
        status: item.status,
        createdAt: item.createdAt,
        paidAt: item.paidAt,
      })),
      total,
      page,
      pageSize,
    };
  }

  async initiatePayment(dto: InitiatePaymentDto): Promise<InitiateResponseDto> {
    const booking = await this.prisma.booking.findUnique({
      where: { id: dto.bookingId },
      include: { seats: true },
    });
    if (!booking) throw new NotFoundException("Booking not found");
    if (booking.status !== "PENDING_PAYMENT") {
      throw new BadRequestException("Booking not payable");
    }

    const method = dto.method as PaymentMethodType;

    // WALLET is an internal balance debit — it never leaves this app.
    if (method === PaymentMethodType.WALLET) {
      const existing = await this.prisma.paymentIntent.findUnique({
        where: { bookingId: dto.bookingId },
      });
      if (existing && NON_TERMINAL_STATUSES.includes(existing.status)) {
        return this.formatIntentResponse(existing);
      }
      return this.initiateWalletPayment(booking);
    }

    const { returnUrl, failureUrl } = this.resolveReturnUrls(method);
    const snapshot = await this.paymentClient.initiate({
      service: PaymentServiceEnum.PASSENGER,
      referenceType: PaymentReferenceType.BOOKING,
      referenceId: booking.id,
      orderRef: booking.bookingRef,
      // Send the REAL (major) price, not minor units. The payment API no longer divides by 100
      // (freight already passes the real price), so the providers charge this value as-is.
      amountMinor: booking.totalMinor / 100,
      currency: booking.currency,
      provider: method as unknown as ProviderMethod,
      platform: dto.platform,
      returnUrl,
      failureUrl,
    });

    let intent = await this.syncIntentProjection(booking.id, snapshot);
    if (snapshot.status === ProviderPaymentStatus.SUCCEEDED) {
      // Already-paid order re-initiated: converge the booking now (idempotent).
      await this.finalizePaymentSuccess({
        intentId: intent.id,
        providerTxnId: snapshot.providerTxnId,
        paidAt: snapshot.paidAt ? new Date(snapshot.paidAt) : undefined,
      });
      intent = await this.prisma.paymentIntent.findUniqueOrThrow({
        where: { id: intent.id },
      });
    }
    return this.formatIntentResponse(intent);
  }
  private resolveReturnUrls(method: PaymentMethodType): {
    returnUrl?: string;
    failureUrl?: string;
  } {
    const perMethod: Partial<
      Record<PaymentMethodType, { returnUrl?: string; failureUrl?: string }>
    > = {
      [PaymentMethodType.TELEBIRR]: {
        returnUrl: process.env.TELEBIRR_RETURN_URL,
      },
      [PaymentMethodType.WAAFI]: {
        returnUrl: process.env.WAAFI_SUCCESS_REDIRECT,
        failureUrl: process.env.WAAFI_FAIL_REDIRECT,
      },
      [PaymentMethodType.DMONEY]: {
        returnUrl: process.env.DMONEY_RETURN_URL,
      },
      [PaymentMethodType.CBE_BIRR]: {
        returnUrl: process.env.CBE_RETURN_URL,
      },
      [PaymentMethodType.EBIRR]: {
        returnUrl: process.env.EBIRR_RETURN_URL,
      },
      [PaymentMethodType.CARD]: {
        returnUrl: process.env.CARD_RETURN_URL,
      },
    };

    const m = perMethod[method] ?? {};
    const returnUrl = m.returnUrl || process.env.PAYMENT_RETURN_URL || undefined;
    const failureUrl =
      m.failureUrl || process.env.PAYMENT_FAILURE_URL || returnUrl;
    return { returnUrl, failureUrl };
  }

  async confirmWaafiReturnDemo(params: {
    referenceId?: string;
    state?: string;
    transactionId?: string;
  }): Promise<{ confirmed: boolean; bookingId?: string; reason?: string }> {
    if (!this.waafiDemoTrustReturn) {
      return { confirmed: false, reason: "demo-disabled" };
    }
    if ((params.state ?? "").toUpperCase() !== "APPROVED") {
      return { confirmed: false, reason: `not-approved (${params.state})` };
    }
    if (!params.referenceId) {
      return { confirmed: false, reason: "missing-referenceId" };
    }

    const intent = await this.prisma.paymentIntent.findFirst({
      where: { merchantOrderId: params.referenceId },
    });
    if (!intent) {
      this.logger.warn(
        `waafi demo return: no local intent for referenceId ${params.referenceId}`,
      );
      return { confirmed: false, reason: "intent-not-found" };
    }

    this.logger.warn(
      `WAAFI_DEMO_TRUST_RETURN enabled — confirming booking ${intent.bookingId} from browser return (INSECURE, demo only)`,
    );
    await this.finalizePaymentSuccess({
      intentId: intent.id,
      providerTxnId: params.transactionId,
    });
    return { confirmed: true, bookingId: intent.bookingId };
  }

  private async syncIntentProjection(
    bookingId: string,
    snapshot: PaymentIntentSnapshot,
  ) {
    const status =
      snapshot.status === ProviderPaymentStatus.SUCCEEDED
        ? PaymentIntentStatus.PROCESSING
        : (snapshot.status as unknown as PaymentIntentStatus);
    const data = {
      status,
      method: snapshot.provider as unknown as PaymentMethodType,
      merchantOrderId: snapshot.merchantOrderId,
      clientAction: snapshot.clientAction
        ? (snapshot.clientAction as unknown as Prisma.InputJsonValue)
        : Prisma.DbNull,
      providerTxnId: snapshot.providerTxnId ?? null,
      expiresAt: snapshot.expiresAt ? new Date(snapshot.expiresAt) : null,
      failureCode: snapshot.failureCode ?? null,
      failureMessage: snapshot.failureMessage ?? null,
    };
    return this.prisma.paymentIntent.upsert({
      where: { bookingId },
      update: data,
      create: {
        bookingId,
        amountMinor: snapshot.amountMinor,
        currency: snapshot.currency,
        ...data,
      },
    });
  }

  private async initiateWalletPayment(
    booking: Prisma.BookingGetPayload<{ include: { seats: true } }>,
  ): Promise<InitiateResponseDto> {
    // DEMO ONLY (WALLET_DEMO_AUTO_SUCCEED): pretend the payment succeeded — no balance check,
    // no debit — and run the exact same finalize path a real successful payment uses
    // (booking → CONFIRMED, seats confirmed, ticket issued). Remove once a real provider works.
    if (this.walletDemoAutoSucceed) {
      this.logger.warn(
        `WALLET_DEMO_AUTO_SUCCEED enabled — faking a successful WALLET payment for booking ${booking.bookingRef} (${booking.id})`,
      );
      const demoIntent = await this.prisma.paymentIntent.upsert({
        where: { bookingId: booking.id },
        update: {
          status: PaymentIntentStatus.PROCESSING,
          failureCode: null,
          method: PaymentMethodType.WALLET,
        },
        create: {
          bookingId: booking.id,
          amountMinor: booking.totalMinor,
          method: PaymentMethodType.WALLET,
          status: PaymentIntentStatus.PROCESSING,
          providerRef: `WALLET-DEMO-${Date.now()}`,
        },
      });
      await this.finalizePaymentSuccess({ intentId: demoIntent.id });
      const settled = await this.prisma.paymentIntent.findUniqueOrThrow({
        where: { id: demoIntent.id },
      });
      return this.formatIntentResponse(settled);
    }

    const debitResult = await this.prisma.$transaction(async (tx) => {
      const wallet = await tx.walletAccount.findUnique({
        where: { passengerId: booking.passengerId },
      });
      if (!wallet || wallet.balanceMinor < booking.totalMinor) {
        return { success: false };
      }
      const newBalance = wallet.balanceMinor - booking.totalMinor;
      await tx.walletAccount.update({
        where: { passengerId: booking.passengerId },
        data: { balanceMinor: newBalance },
      });
      await tx.walletLedgerEntry.create({
        data: {
          walletId: wallet.id,
          type: "DEBIT",
          amountMinor: booking.totalMinor,
          balanceAfterMinor: newBalance,
          description: `Train Ticket - ${booking.bookingRef}`,
          relatedBookingId: booking.id,
        },
      });
      return { success: true };
    });

    if (!debitResult.success) {
      const failed = await this.prisma.paymentIntent.upsert({
        where: { bookingId: booking.id },
        update: {
          status: PaymentIntentStatus.FAILED,
          failureCode: "INSUFFICIENT_BALANCE",
        },
        create: {
          bookingId: booking.id,
          amountMinor: booking.totalMinor,
          method: PaymentMethodType.WALLET,
          status: PaymentIntentStatus.FAILED,
          failureCode: "INSUFFICIENT_BALANCE",
        },
      });
      return this.formatIntentResponse(failed);
    }

    const intent = await this.prisma.paymentIntent.upsert({
      where: { bookingId: booking.id },
      update: { status: PaymentIntentStatus.PROCESSING },
      create: {
        bookingId: booking.id,
        amountMinor: booking.totalMinor,
        method: PaymentMethodType.WALLET,
        status: PaymentIntentStatus.PROCESSING,
        providerRef: `WALLET-${Date.now()}`,
      },
    });
    await this.finalizePaymentSuccess({ intentId: intent.id });
    const refreshed = await this.prisma.paymentIntent.findUniqueOrThrow({
      where: { id: intent.id },
    });
    return this.formatIntentResponse(refreshed);
  }

  private formatIntentResponse(
    intent: Prisma.PaymentIntentGetPayload<Record<string, never>>,
  ): InitiateResponseDto {
    const clientAction =
      intent.clientAction && typeof intent.clientAction === "object"
        ? (intent.clientAction as unknown as ClientAction)
        : undefined;
    return {
      intentId: intent.id,
      status: intent.status,
      clientAction,
      merchantOrderId: intent.merchantOrderId ?? undefined,
    };
  }

  async getIntentByBookingId(bookingId: string): Promise<IntentStatusDto> {
    const local = await this.prisma.paymentIntent.findUnique({
      where: { bookingId },
    });

    // WALLET payments never leave this app — no remote intent exists for them.
    if (local?.method === PaymentMethodType.WALLET) {
      return this.formatIntentStatus(local);
    }

    // Pull/reconcile through the payment microservice (it refreshes stale intents from the
    // provider itself). Falls back to the legacy local path when the service is unreachable
    // or only a pre-cutover local intent exists.
    let snapshot: PaymentIntentSnapshot | null = null;
    try {
      snapshot = await this.paymentClient.getIntentByReference(
        PaymentReferenceType.BOOKING,
        bookingId,
      );
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      this.logger.warn(
        `payment service lookup failed for booking ${bookingId}: ${message}; using local intent`,
      );
    }

    if (!snapshot) {
      // Pre-cutover/local-only intent (or service briefly unreachable): serve the cached
      // status. The payment service owns provider refresh for everything initiated after
      // the cutover; webhooks/mark-paid converge the rest.
      if (!local) throw new NotFoundException("PaymentIntent not found");
      return this.formatIntentStatus(local);
    }

    let intent = await this.syncIntentProjection(bookingId, snapshot);
    if (snapshot.status === ProviderPaymentStatus.SUCCEEDED) {
      // Poll observed success before (or instead of) the mark-paid event — converge now.
      await this.finalizePaymentSuccess({
        intentId: intent.id,
        providerTxnId: snapshot.providerTxnId,
        paidAt: snapshot.paidAt ? new Date(snapshot.paidAt) : undefined,
      });
      intent = await this.prisma.paymentIntent.findUniqueOrThrow({
        where: { id: intent.id },
      });
    }
    return this.formatIntentStatus(intent);
  }

  private formatIntentStatus(
    intent: Prisma.PaymentIntentGetPayload<Record<string, never>>,
  ): IntentStatusDto {
    const base = this.formatIntentResponse(intent);
    return {
      ...base,
      paidAt: intent.paidAt?.toISOString(),
      failureCode: intent.failureCode ?? undefined,
      failureMessage: intent.failureMessage ?? undefined,
    };
  }

  async refund(dto: RefundDto) {
    const intent = await this.prisma.paymentIntent.findUnique({
      where: { bookingId: dto.bookingId },
    });
    if (!intent || intent.status !== "SUCCEEDED")
      throw new BadRequestException("No successful payment to refund");
    await this.prisma.paymentIntent.update({
      where: { bookingId: dto.bookingId },
      data: { status: "CANCELLED" },
    });
    const booking = await this.prisma.booking.findUnique({
      where: { id: dto.bookingId },
      include: { seats: true },
    });
    if (booking) {
      await this.seatsService.releaseSeats(booking.seats.map((s) => s.seatId));
      await this.prisma.booking.update({
        where: { id: dto.bookingId },
        data: { status: "CANCELLED" },
      });
    }
    return { refunded: true, bookingRef: booking?.bookingRef };
  }

  addPaymentMethod(dto: AddPaymentMethodDto) {
    const data = {
      type: dto.type as unknown as PaymentMethodType,
      displayName: dto.displayName,
      region: dto.region as unknown as PaymentRegion,
      currency: dto.currency ?? "ETB",
      providerId: dto.providerId,
      enabled: dto.enabled ?? true,
      sortOrder: dto.sortOrder ?? 0,
    };
    return this.prisma.paymentMethod.upsert({
      where: { type: data.type },
      update: data,
      create: data,
    });
  }

  getSupportedPaymentMethods(region?: PaymentRegionEnum) {
    return this.prisma.paymentMethod.findMany({
      where: {
        enabled: true,
        ...(region
          ? {
              region: {
                in: [
                  region,
                  PaymentRegionEnum.GLOBAL,
                ] as unknown as PaymentRegion[],
              },
            }
          : {}),
      },
      orderBy: [{ sortOrder: "asc" }, { displayName: "asc" }],
    });
  }

  /**
   * Guard against an implausible paidAt from a provider event (e.g. a Telebirr epoch parsed as
   * ms×1000 → year 58429), which Prisma/Postgres rejects and would otherwise dead-letter the
   * whole confirmation. Falls back to "now" for missing/invalid/far-future/ancient values so the
   * booking still confirms.
   */
  private sanitizePaidAt(value?: Date): Date {
    const now = new Date();
    if (!value) return now;
    const t = value.getTime();
    const oneDayMs = 86_400_000;
    if (
      Number.isNaN(t) ||
      t > now.getTime() + oneDayMs ||
      t < Date.UTC(2000, 0, 1)
    ) {
      this.logger.warn(
        `finalizePaymentSuccess: implausible paidAt (epoch=${t}); using current time instead`,
      );
      return now;
    }
    return value;
  }

  async finalizePaymentSuccess(input: {
    intentId: string;
    providerTxnId?: string;
    paidAt?: Date;
  }): Promise<{ alreadyFinalized: boolean }> {
    const intent = await this.prisma.paymentIntent.findUnique({
      where: { id: input.intentId },
    });
    if (!intent) throw new NotFoundException("PaymentIntent not found");
    if (intent.status === PaymentIntentStatus.SUCCEEDED) {
      return { alreadyFinalized: true };
    }
    if (intent.status === PaymentIntentStatus.CANCELLED) {
      throw new BadRequestException(
        "PaymentIntent is cancelled; cannot finalize",
      );
    }

    const booking = await this.prisma.booking.findUnique({
      where: { id: intent.bookingId },
      include: { seats: true },
    });
    if (!booking) throw new NotFoundException("Booking not found");

    const paidAt = this.sanitizePaidAt(input.paidAt);
    await this.prisma.$transaction(async (tx) => {
      await tx.paymentIntent.update({
        where: { id: intent.id },
        data: {
          status: PaymentIntentStatus.SUCCEEDED,
          providerTxnId:
            input.providerTxnId ?? intent.providerTxnId ?? undefined,
          paidAt,
        },
      });
      await tx.booking.update({
        where: { id: booking.id },
        data: { status: "CONFIRMED" },
      });
    });

    try {
      await this.seatsService.confirmSeats(booking.seats.map((s) => s.seatId));
    } catch (err) {
      this.logger.error(
        `Error confirming seats: ${err instanceof Error ? err.message : String(err)}`,
      );
    }

    try {
      await this.createJourneySegments(booking);
    } catch (err) {
      this.logger.error(
        `Error creating journey segments: ${err instanceof Error ? err.message : String(err)}`,
      );
    }

    try {
      await this.ticketsService.generate(booking.id);
    } catch (err) {
      this.logger.error(
        `Error generating ticket: ${err instanceof Error ? err.message : String(err)}`,
      );
      throw err;
    }

    try {
      await this.awardLoyaltyPoints(
        booking.passengerId,
        booking.totalMinor,
        booking.id,
      );
    } catch (err) {
      this.logger.warn(
        `Error awarding loyalty points: ${err instanceof Error ? err.message : String(err)}`,
      );
    }

    this.eventEmitter.emit("payment.succeeded", { booking });
    return { alreadyFinalized: false };
  }

  async handlePaymentEvent(
    event: PaymentEventDto,
  ): Promise<MarkPaidResponseDto> {
    if (
      event.service !== PaymentServiceEnum.PASSENGER ||
      event.referenceType !== PaymentReferenceType.BOOKING
    ) {
      this.logger.warn(
        `mark-paid: ignoring foreign reference ${event.service}/${event.referenceType}/${event.referenceId}`,
      );
      return { processed: false, reason: "foreign-reference" };
    }

    if (event.eventType === "payment.failed") {
      const intent = await this.prisma.paymentIntent.findUnique({
        where: { bookingId: event.referenceId },
      });
      if (intent) {
        await this.markPaymentFailed({
          intentId: intent.id,
          failureCode: event.failureCode,
          failureMessage: event.failureMessage,
        });
      }
      const failedBooking = await this.prisma.booking.findUnique({
        where: { id: event.referenceId },
      });
      if (failedBooking) {
        this.eventEmitter.emit("payment.failed", { booking: failedBooking });
      }
      return { processed: true };
    }

    const booking = await this.prisma.booking.findUnique({
      where: { id: event.referenceId },
    });
    if (!booking) {
      // Ack (200) — a missing booking will not appear on redelivery; needs investigation.
      this.logger.error(
        `mark-paid: no booking for reference ${event.referenceId}`,
      );
      return { processed: false, reason: "booking-not-found" };
    }

    // The event carries the REAL (major) price the provider charged (passenger now sends
    // booking.totalMinor/100 on initiate), so convert it back to minor units before comparing
    // with booking.totalMinor (which is in minor units).
    const eventAmountMinor = Math.round(event.amountMinor * 100);
    if (booking.totalMinor !== eventAmountMinor) {
      // Refuse to confirm: a 4xx makes the relay retry and eventually flag the row FAILED,
      // which is the alertable signal for an asserted-vs-paid amount divergence.
      this.logger.error(
        `mark-paid: amount mismatch for booking ${booking.id}: booking=${booking.totalMinor} event=${event.amountMinor} (=${eventAmountMinor} minor)`,
      );
      throw new BadRequestException(
        "Event amount does not match booking total",
      );
    }

    // Local intent row is a projection during the strangler migration: reuse it when the
    // legacy initiate path created one, otherwise materialize it from the event.
    let intent = await this.prisma.paymentIntent.findUnique({
      where: { bookingId: event.referenceId },
    });
    if (!intent) {
      intent = await this.prisma.paymentIntent.create({
        data: {
          bookingId: event.referenceId,
          amountMinor: event.amountMinor,
          currency: event.currency,
          method: event.provider as unknown as PaymentMethodType,
          status: PaymentIntentStatus.PROCESSING,
          merchantOrderId: event.merchantOrderId,
          providerTxnId: event.providerTxnId,
        },
      });
    }

    const { alreadyFinalized } = await this.finalizePaymentSuccess({
      intentId: intent.id,
      providerTxnId: event.providerTxnId,
      paidAt: event.paidAt ? new Date(event.paidAt) : undefined,
    });
    return { processed: true, alreadyFinalized };
  }

  async markPaymentFailed(input: {
    intentId: string;
    failureCode?: string;
    failureMessage?: string;
  }): Promise<void> {
    const intent = await this.prisma.paymentIntent.findUnique({
      where: { id: input.intentId },
    });
    if (!intent) throw new NotFoundException("PaymentIntent not found");
    if (
      intent.status === PaymentIntentStatus.SUCCEEDED ||
      intent.status === PaymentIntentStatus.CANCELLED
    ) {
      return;
    }
    await this.prisma.paymentIntent.update({
      where: { id: intent.id },
      data: {
        status: PaymentIntentStatus.FAILED,
        failureCode: input.failureCode,
        failureMessage: input.failureMessage,
      },
    });
  }

  private async awardLoyaltyPoints(
    passengerId: string,
    amountMinor: number,
    bookingId: string,
  ) {
    const points = Math.floor(amountMinor / 100);
    const account = await this.prisma.loyaltyAccount.findUnique({
      where: { passengerId },
    });
    if (!account) return;
    const newBalance = account.pointsBalance + points;
    const tier =
      newBalance >= 10000
        ? "PLATINUM"
        : newBalance >= 5000
          ? "GOLD"
          : newBalance >= 2000
            ? "SILVER"
            : "BRONZE";
    await this.prisma.loyaltyAccount.update({
      where: { passengerId },
      data: { pointsBalance: { increment: points }, tier: tier as any },
    });
    await this.prisma.loyaltyLedgerEntry.create({
      data: {
        accountId: account.id,
        delta: points,
        reason: "TRIP_COMPLETED",
        bookingId,
        balanceAfter: newBalance,
      },
    });
  }

  private async createJourneySegments(
    booking: Prisma.BookingGetPayload<{ include: { seats: true } }>,
  ) {
    const schedule = await this.prisma.trainSchedule.findUnique({
      where: { id: booking.scheduleId },
      include: {
        stopTimes: { include: { station: true }, orderBy: { sequence: "asc" } },
      },
    });
    if (!schedule) return;

    const stopTimes = schedule.stopTimes;
    if (stopTimes.length < 2) return;

    const originSequence = stopTimes.findIndex(
      (st) => st.stationId === schedule.originStationId,
    );
    const destSequence = stopTimes.findIndex(
      (st) => st.stationId === schedule.destinationStationId,
    );

    if (
      originSequence < 0 ||
      destSequence < 0 ||
      originSequence >= destSequence
    )
      return;

    const journey = await this.prisma.journey.create({
      data: {
        passengerId: booking.passengerId,
        status: "CONFIRMED",
        totalMinor: booking.totalMinor,
        currency: booking.currency,
      },
    });

    const journeySegments = [];
    for (const bookingSeat of booking.seats) {
      for (let i = originSequence; i < destSequence; i++) {
        journeySegments.push({
          journeyId: journey.id,
          scheduleId: booking.scheduleId,
          segmentOrder: i,
          seatId: bookingSeat.seatId,
          departureStationId: stopTimes[i].stationId,
          arrivalStationId: stopTimes[i + 1].stationId,
        });
      }
    }

    if (journeySegments.length > 0) {
      await this.prisma.journeySegment.createMany({ data: journeySegments });
    }
  }
}
