/**
 * GENERAL mixed reopen (D+26) — the same two GENERAL contracts (one
 * container, one bulk) book AGAIN after their first bookings die: cycle 1
 * reserves a container + a bulk drawdown, nobody pays, both expire, the
 * window reopens — and the SAME contracts issue fresh drawdowns in cycle 2
 * that pay and allocate typed. GENERAL contracts survive dead bookings and
 * dead cycles alike.
 *
 * Sequential steps of one journey — retries off.
 */

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

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

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

describe("GENERAL mixed reopen: the same contracts book again after a dead cycle", { retries: 0 }, () => {
  before(() => {
    cy.task("db:seedFile", "seed-import-corridor.sql");
    seedImportContract({ suffix: "GMC", reference: stampedRef("GMC"), kind: "GENERAL" });
    seedImportContract({
      suffix: "GMB",
      reference: stampedRef("GMB"),
      kind: "GENERAL",
      freight: "BULK",
    });
  });

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

  it("cycle 1: a container and a bulk GENERAL drawdown clear per booking and are reserved", () => {
    bookContainers({
      suffix: "GMC",
      runStamp: stamp,
      isoSeed: 19_000,
      twenty: 12, // 6 wagons
      scheduledDate: BOOKING_DAY,
    });
    clearGeneralBooking("GMC", BOOKING_DAY);
    bookBulk({ suffix: "GMB", tons: 700, scheduledDate: BOOKING_DAY }); // 10 wagons
    clearGeneralBooking("GMB", BOOKING_DAY);

    withSchedule(DEPARTURE, (s) => {
      closeBookingWindow(s.id);
      completeDocReview(s.id);
    });
    ["GMC", "GMB"].forEach((suffix) =>
      pollBookingStatus(suffix, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
    );
  });

  it("nobody pays — both drawdowns expire and the window reopens", () => {
    ["GMC", "GMB"].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: the SAME contracts issue fresh drawdowns that 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: "GMC",
      runStamp: stamp,
      isoSeed: 19_200,
      twenty: 12,
      scheduledDate: BOOKING_DAY,
    });
    clearGeneralBooking("GMC", BOOKING_DAY);
    bookBulk({ suffix: "GMB", tons: 700, scheduledDate: BOOKING_DAY });
    clearGeneralBooking("GMB", BOOKING_DAY);

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

    // The cycle-1 corpses stay dead; both contracts remain ACTIVE.
    ["GMC", "GMB"].forEach((suffix) =>
      withBooking(suffix, (b) => expect(b.status, `${suffix} cycle-2 rides`).to.eq("PAID")),
    );
  });
});

export {};
