/**
 * EXPORT journeys E2 + E5 — FCFS capacity truth on the reversed corridor:
 *
 *   E2 (D+3): three bookings (20+20+14 wagons) accept first and hold ALL 54
 *   wagons before anyone pays. Three late exporters then try to book the same
 *   day → each is REJECTED AT SUBMISSION by the whole-train space gate
 *   ("exports ride whole or not at all" — no waiting list, no split). The
 *   three reserved pay → FULL.
 *
 *   E5 (D+4): the whole-or-nothing giant. A 58-wagon booking (116×20ft) is
 *   rejected against the empty 54-wagon train; rebooked at exactly 54 wagons
 *   it reserves the ENTIRE train alone, pays, allocates 54/54 → FULL — and a
 *   1-wagon afterthought bounces off the FULL train.
 *
 * Sequential steps — retries off.
 */

import {
  acceptExport,
  bookContainers,
  createImportSchedule,
  db,
  departureAt,
  eatDayStr,
  ensureExportRoute,
  EXP_DEST,
  EXP_ORIGIN,
  forceWindowOpen,
  markPaid,
  pollAllocations,
  pollDb,
  resetCorridorDay,
  seedImportContract,
  withBooking,
  withExportSchedule,
  type ScheduleRow,
} from "./import-utils";

const DEPARTURE = departureAt(3);
const BOOKING_DAY = eatDayStr(DEPARTURE);
const GIANT_DEPARTURE = departureAt(4);
const GIANT_DAY = eatDayStr(GIANT_DEPARTURE);

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

const FIRST = [
  { suffix: "XA", twenty: 40, forty: 0, wagons: 20 },
  { suffix: "XB", twenty: 0, forty: 20, wagons: 20 },
  { suffix: "XC", twenty: 28, forty: 0, wagons: 14 },
];
const LATE = ["XL1", "XL2", "XL3"];

function seedExport(suffix: string) {
  seedImportContract({
    suffix,
    reference: stampedRef(suffix),
    direction: "EXPORT",
    originCode: EXP_ORIGIN,
    destCode: EXP_DEST,
  });
}

describe("export FCFS: reservations hold capacity, whole-or-nothing space gate", { retries: 0 }, () => {
  before(() => {
    cy.task("db:seedFile", "seed-import-corridor.sql");
    [...FIRST.map((b) => b.suffix), ...LATE, "XG", "XS"].forEach(seedExport);
  });

  it("operations prepares the export corridor and the D+3 train with an open window", () => {
    ensureExportRoute();
    resetCorridorDay(DEPARTURE, EXP_DEST, EXP_ORIGIN);
    resetCorridorDay(GIANT_DEPARTURE, EXP_DEST, EXP_ORIGIN);
    createImportSchedule({
      departure: DEPARTURE,
      locoPair: ["LOCO-EXP-3", "LOCO-EXP-4"],
      originCode: EXP_ORIGIN,
      destCode: EXP_DEST,
    });
    withExportSchedule(DEPARTURE, (s) => forceWindowOpen(s.id, 60));
  });

  it("three exporters book and are accepted — 54 wagons reserved BEFORE any payment", () => {
    let isoSeed = 6500;
    FIRST.forEach((b) => {
      bookContainers({
        suffix: b.suffix,
        runStamp: stamp,
        isoSeed,
        twenty: b.twenty,
        forty: b.forty,
        scheduledDate: BOOKING_DAY,
      });
      isoSeed += b.twenty + b.forty;
      acceptExport(b.suffix);
    });
  });

  it("three late exporters are rejected at submission — the space gate reports no room", () => {
    LATE.forEach((suffix, i) => {
      bookContainers({
        suffix,
        runStamp: stamp,
        isoSeed: 6700 + i * 30,
        twenty: 20, // 10 wagons — but reservations already hold all 54
        scheduledDate: BOOKING_DAY,
        expectFailure: /space|window/i,
      });
    });
  });

  it("the three reserved pay — 54/54 allocated and the export window flips FULL", () => {
    FIRST.forEach((b) => {
      markPaid(b.suffix);
      pollAllocations(b.suffix, b.wagons);
    });
    withExportSchedule(DEPARTURE, (s) => {
      pollDb<ScheduleRow>(
        "export window FULL",
        `SELECT booking_window_status FROM freight.train_schedules WHERE id = $1`,
        [s.id],
        (row) => row?.booking_window_status === "FULL",
      );
      db<{ n: string }>(
        `SELECT count(DISTINCT wba.train_set_wagon_id) AS n
         FROM freight.wagon_booking_allocations wba
         JOIN freight.train_schedule_bookings tsb
           ON tsb.booking_id = wba.booking_id AND tsb.train_schedule_id = $1
         WHERE wba.deleted_at IS NULL AND tsb.deleted_at IS NULL`,
        [s.id],
      ).then(({ rows }) => expect(Number(rows[0].n), "54 wagons allocated").to.eq(54));
    });
  });

  it("whole-or-nothing: a 58-wagon giant is rejected against the empty D+4 train", () => {
    createImportSchedule({
      departure: GIANT_DEPARTURE,
      locoPair: ["LOCO-EXP-5", "LOCO-EXP-6"],
      originCode: EXP_ORIGIN,
      destCode: EXP_DEST,
    });
    withExportSchedule(GIANT_DEPARTURE, (s) => forceWindowOpen(s.id, 60));

    bookContainers({
      suffix: "XG",
      runStamp: stamp,
      isoSeed: 7000,
      twenty: 116, // 58 wagons > 54 — export never splits
      scheduledDate: GIANT_DAY,
      expectFailure: /space|window/i,
    });
  });

  it("rebooked at exactly 54 wagons the giant reserves the whole train alone, pays, fills it", () => {
    bookContainers({
      suffix: "XG",
      runStamp: stamp,
      isoSeed: 7200,
      twenty: 108, // 54 wagons — the whole consist
      scheduledDate: GIANT_DAY,
    });
    acceptExport("XG");
    markPaid("XG");
    pollAllocations("XG", 54);
    withExportSchedule(GIANT_DEPARTURE, (s) => {
      pollDb<ScheduleRow>(
        "giant train FULL from one booking",
        `SELECT booking_window_status FROM freight.train_schedules WHERE id = $1`,
        [s.id],
        (row) => row?.booking_window_status === "FULL",
      );
      withBooking("XG", (b) => {
        expect(b.train_schedule_id, "giant rides its train").to.eq(s.id);
      });
    });
  });

  it("a 1-wagon afterthought bounces off the FULL train", () => {
    bookContainers({
      suffix: "XS",
      runStamp: stamp,
      isoSeed: 7400,
      twenty: 2,
      scheduledDate: GIANT_DAY,
      expectFailure: /space|window/i,
    });
  });
});

export {};
