/**
 * BULK EXPORT — FCFS capacity truth (mirror of export_fcfs_space):
 *
 *   D+9:  three wheat bookings (1 400 + 1 400 + 980 T = 54 wagons) accept
 *   first and hold the train before any payment; three late 700 T exporters
 *   are REJECTED AT SUBMISSION by the whole-train space gate. The three pay
 *   → FULL.
 *
 *   D+10: the whole-or-nothing giant — 4 060 T (58 wagons) rejected against
 *   the empty train; rebooked at exactly 3 780 T (54 wagons) it reserves the
 *   whole consist alone, pays, allocates 54/54 → FULL; a 70 T afterthought
 *   bounces.
 *
 * Sequential steps — retries off.
 */

import {
  acceptExport,
  bookBulk,
  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(9);
const BOOKING_DAY = eatDayStr(DEPARTURE);
const GIANT_DEPARTURE = departureAt(10);
const GIANT_DAY = eatDayStr(GIANT_DEPARTURE);

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

const FIRST = [
  { suffix: "YA", tons: 1400, wagons: 20 },
  { suffix: "YB", tons: 1400, wagons: 20 },
  { suffix: "YC", tons: 980, wagons: 14 },
];
const LATE = ["YL1", "YL2", "YL3"];

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

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

  it("operations prepares the export corridor and the D+9 CW4 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"],
      kind: "bulk",
      originCode: EXP_ORIGIN,
      destCode: EXP_DEST,
    });
    withExportSchedule(DEPARTURE, (s) => forceWindowOpen(s.id, 60));
  });

  it("three exporters book wheat and are accepted — 54 wagons reserved BEFORE any payment", () => {
    FIRST.forEach((b) => {
      bookBulk({ suffix: b.suffix, tons: b.tons, scheduledDate: BOOKING_DAY });
      acceptExport(b.suffix);
    });
  });

  it("three late exporters are rejected at submission — the space gate reports no room", () => {
    LATE.forEach((suffix) => {
      bookBulk({
        suffix,
        tons: 700,
        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",
      );
    });
  });

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

    bookBulk({
      suffix: "YG",
      tons: 4060, // 58 wagons > 54 — export never splits
      scheduledDate: GIANT_DAY,
      expectFailure: /space|window/i,
    });
  });

  it("rebooked at exactly 3 780 T the giant reserves the whole train alone, pays, fills it", () => {
    bookBulk({ suffix: "YG", tons: 3780, scheduledDate: GIANT_DAY });
    acceptExport("YG");
    markPaid("YG");
    pollAllocations("YG", 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("YG", (b) => {
        expect(b.train_schedule_id, "giant rides its train").to.eq(s.id);
      });
      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("a 70 T afterthought bounces off the FULL train", () => {
    bookBulk({
      suffix: "YS",
      tons: 70,
      scheduledDate: GIANT_DAY,
      expectFailure: /space|window/i,
    });
  });
});

export {};
