/**
 * MIXED IMPORT journey M4 + matrix — dead mixed cycle, mixed recovery, and
 * mixed ride-alongs on one train (D+21):
 *
 *   1. cycle 1: one container (20w) and one bulk (1 400 T) reserved, neither
 *      pays → both expire, the window REOPENS (mixed cycle bookkeeping)
 *   2. cycle 2: a fresh container (10w) AND a fresh bulk (730 T → ceil = 11
 *      wagons, the rounding case) book, pay, allocate typed on the recovered
 *      train
 *   3. mixed intercity: a container ride-along AND a bulk ride-along
 *      (KALITY-bound legs are free) join the same import train — accepted,
 *      paid, linked
 *   4. mixed sub-corridor: a bulk NAGAD→MOJO booking rides the through-train
 *      beside the cycle-2 pair
 *
 * Sequential steps of one journey — retries off.
 */

import {
  acceptOperation,
  apiPost,
  bookBulk,
  bookContainers,
  closeBookingWindow,
  completeDocReview,
  createImportSchedule,
  db,
  departureAt,
  eatDayStr,
  endPaymentPhase,
  ensureCorridorRoute,
  expectWagonType,
  forceReservationExpiry,
  forceWindowOpen,
  markPaid,
  opsStaff,
  pollAllocations,
  pollBookingStatus,
  pollDb,
  resetCorridorDay,
  seedImportContract,
  withBooking,
  withSchedule,
  type ScheduleRow,
} from "./import-utils";

const DEPARTURE = departureAt(21);
const BOOKING_DAY = eatDayStr(DEPARTURE);

const stamp = String(Date.now());
const stampedRef = (suffix: string) => `CTR-IMP-${stamp}-${suffix}`;

describe("mixed import: dead mixed cycle, mixed recovery, mixed ride-alongs", { retries: 0 }, () => {
  before(() => {
    cy.task("db:seedFile", "seed-import-corridor.sql");
    seedImportContract({ suffix: "MRC", reference: stampedRef("MRC") });
    seedImportContract({ suffix: "MRB", reference: stampedRef("MRB"), freight: "BULK" });
    seedImportContract({ suffix: "MRC2", reference: stampedRef("MRC2") });
    seedImportContract({ suffix: "MRB2", reference: stampedRef("MRB2"), freight: "BULK" });
    seedImportContract({
      suffix: "MRSUB",
      reference: stampedRef("MRSUB"),
      freight: "BULK",
      originCode: "NAGAD",
      destCode: "MOJO",
    });
    seedImportContract({
      suffix: "MRIC",
      reference: stampedRef("MRIC"),
      direction: "DOMESTIC",
      originCode: "MOJO",
      destCode: "KALITY",
    });
    seedImportContract({
      suffix: "MRIB",
      reference: stampedRef("MRIB"),
      freight: "BULK",
      direction: "DOMESTIC",
      originCode: "MOJO",
      destCode: "KALITY",
    });
  });

  it("operations prepares the corridor train — first window opens (cycle 1)", () => {
    ensureCorridorRoute();
    resetCorridorDay(DEPARTURE);
    createImportSchedule({ departure: DEPARTURE, locoPair: ["LOCO-IMP-9", "LOCO-IMP-10"] });
    withSchedule(DEPARTURE, (s) => forceWindowOpen(s.id, 45));
    withSchedule(DEPARTURE, (s) => expect(s.booking_cycle_no, "cycle 1").to.eq(1));
  });

  it("cycle 1: one container and one bulk reserved — neither pays, both expire, window reopens", () => {
    bookContainers({
      suffix: "MRC",
      runStamp: stamp,
      isoSeed: 12_000,
      twenty: 40,
      scheduledDate: BOOKING_DAY,
    });
    acceptOperation("MRC");
    bookBulk({ suffix: "MRB", tons: 1400, scheduledDate: BOOKING_DAY });
    acceptOperation("MRB");

    withSchedule(DEPARTURE, (s) => {
      closeBookingWindow(s.id);
      completeDocReview(s.id);
    });
    ["MRC", "MRB"].forEach((suffix) =>
      pollBookingStatus(suffix, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
    );
    ["MRC", "MRB"].forEach((suffix) => forceReservationExpiry(suffix));
    withSchedule(DEPARTURE, (s) => {
      endPaymentPhase(s.id);
      // Same-tick guard-loop can chain PRE_WINDOW straight into OPEN when the
      // reopen instant falls inside office hours — assert it left PAYMENT
      // without concluding FULL/DONE, whichever phase it lands on.
      pollDb<ScheduleRow>(
        "window concludes not-full (PRE_WINDOW or fast-forwarded to OPEN)",
        `SELECT window_phase FROM freight.train_schedules WHERE id = $1`,
        [s.id],
        (row) => !!row && row.window_phase !== "PAYMENT" && row.window_phase !== "DONE",
      );
    });
  });

  it("cycle 2: a fresh container (10w) and a fresh bulk (730 T → 11 wagons) pay and allocate typed", () => {
    withSchedule(DEPARTURE, (s) => forceWindowOpen(s.id, 45));
    withSchedule(DEPARTURE, (s) => expect(s.booking_cycle_no, "cycle 2").to.eq(2));

    bookContainers({
      suffix: "MRC2",
      runStamp: stamp,
      isoSeed: 12_200,
      twenty: 20,
      scheduledDate: BOOKING_DAY,
    });
    acceptOperation("MRC2");
    // ceil(730 / 70) = 11 — the bulk rounding case rides beside containers.
    bookBulk({ suffix: "MRB2", tons: 730, scheduledDate: BOOKING_DAY });
    acceptOperation("MRB2");
    // Mixed sub-corridor: bulk boards NAGAD, alights MOJO, same train.
    bookBulk({ suffix: "MRSUB", tons: 700, scheduledDate: BOOKING_DAY });
    acceptOperation("MRSUB");

    withSchedule(DEPARTURE, (s) => {
      closeBookingWindow(s.id);
      completeDocReview(s.id);
    });
    ["MRC2", "MRB2", "MRSUB"].forEach((suffix) =>
      pollBookingStatus(suffix, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
    );
    markPaid("MRC2");
    pollAllocations("MRC2", 10);
    expectWagonType("MRC2", "NW5", 10);
    markPaid("MRB2");
    pollAllocations("MRB2", 11);
    expectWagonType("MRB2", "CW4", 11);
    markPaid("MRSUB");
    pollAllocations("MRSUB", 10);
    expectWagonType("MRSUB", "CW4", 10);

    ["MRC", "MRB"].forEach((suffix) =>
      withBooking(suffix, (b) => expect(b.status, `${suffix} stays expired`).to.eq("EXPIRED")),
    );
    withSchedule(DEPARTURE, (s) => {
      ["MRC2", "MRB2", "MRSUB"].forEach((suffix) =>
        withBooking(suffix, (b) =>
          expect(b.train_schedule_id, `${suffix} rides the recovered train`).to.eq(s.id),
        ),
      );
    });
  });

  it("mixed intercity: a container AND a bulk ride-along join the same import train", () => {
    bookContainers({ suffix: "MRIC", runStamp: stamp, isoSeed: 12_400, twenty: 4 });
    acceptOperation("MRIC");
    bookBulk({ suffix: "MRIB", tons: 140 });
    acceptOperation("MRIB");

    withSchedule(DEPARTURE, (s) => {
      withBooking("MRIC", (bc) => {
        withBooking("MRIB", (bb) => {
          apiPost(opsStaff, `/api/train-scheduling/schedules/${s.id}/intercity/accept`, {
            bookingIds: [bc.id, bb.id],
          })
            .its("status")
            .should("be.oneOf", [200, 201]);
        });
      });
    });
    ["MRIC", "MRIB"].forEach((suffix) =>
      pollBookingStatus(suffix, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
    );
    markPaid("MRIC");
    markPaid("MRIB");
    withSchedule(DEPARTURE, (s) => {
      ["MRIC", "MRIB"].forEach((suffix) =>
        withBooking(suffix, (b) => {
          expect(b.train_schedule_id, `${suffix} linked`).to.eq(s.id);
          // The link row is written by the async allocate step — poll it.
          pollDb<{ n: string }>(
            `${suffix} link row`,
            `SELECT count(*) AS n FROM freight.train_schedule_bookings
             WHERE booking_id = $1 AND train_schedule_id = $2 AND deleted_at IS NULL`,
            [b.id, s.id],
            (row) => Number(row?.n ?? 0) === 1,
            15,
          );
        }),
      );
    });
  });
});

export {};
