/**
 * MIXED EXPORT journeys XM2 + XM3 — one FCFS ledger for two cargo kinds:
 *
 *   XM2 (D+15): container 20w, bulk 14w, container 20w accept in order — 54
 *   wagons held before any payment. A late bulk (700 T) AND a late container
 *   (10w) both bounce off the SAME space gate. The three reserved pay →
 *   typed allocation, FULL. One shared reservation account, no per-type quota.
 *
 *   XM3 (D+16): cross-type pay-or-lose. Container 20w pays, bulk 14w pays,
 *   a second container 20w never pays → expires at its clamped deadline →
 *   a BULK 1 400 T booking takes the container's freed slots (slots are
 *   type-blind, wagons stay typed: the wheat lands on CW4). The train is
 *   FULL again — a 1-wagon container afterthought bounces.
 *
 * Sequential steps — retries off.
 */

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

const DEPARTURE = departureAt(15);
const BOOKING_DAY = eatDayStr(DEPARTURE);
const PAYLOSE_DEPARTURE = departureAt(16);
const PAYLOSE_DAY = eatDayStr(PAYLOSE_DEPARTURE);

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

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

describe("mixed export FCFS: one shared ledger, cross-type pay-or-lose", { retries: 0 }, () => {
  before(() => {
    cy.task("db:seedFile", "seed-import-corridor.sql");
    seedMixedExport("FMC1", "CONTAINER");
    seedMixedExport("FMB1", "BULK");
    seedMixedExport("FMC2", "CONTAINER");
    seedMixedExport("FML1", "BULK"); // late bulk
    seedMixedExport("FML2", "CONTAINER"); // late container
    seedMixedExport("PLC1", "CONTAINER");
    seedMixedExport("PLB1", "BULK");
    seedMixedExport("PLC2", "CONTAINER");
    seedMixedExport("PLB2", "BULK"); // takes PLC2's freed slots
    seedMixedExport("PLS", "CONTAINER"); // afterthought
  });

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

  it("container, bulk, container accept in order — 54 wagons held before any payment", () => {
    bookContainers({
      suffix: "FMC1",
      runStamp: stamp,
      isoSeed: 14_000,
      twenty: 40,
      scheduledDate: BOOKING_DAY,
    });
    acceptExport("FMC1");
    bookBulk({ suffix: "FMB1", tons: 980, scheduledDate: BOOKING_DAY });
    acceptExport("FMB1");
    bookContainers({
      suffix: "FMC2",
      runStamp: stamp,
      isoSeed: 14_100,
      twenty: 40,
      scheduledDate: BOOKING_DAY,
    });
    acceptExport("FMC2");
  });

  it("a late bulk AND a late container bounce off the same space gate", () => {
    bookBulk({
      suffix: "FML1",
      tons: 700,
      scheduledDate: BOOKING_DAY,
      expectFailure: /space|window/i,
    });
    bookContainers({
      suffix: "FML2",
      runStamp: stamp,
      isoSeed: 14_200,
      twenty: 20,
      scheduledDate: BOOKING_DAY,
      expectFailure: /space|window/i,
    });
  });

  it("the three reserved pay — typed allocation 54/54, window FULL", () => {
    markPaid("FMC1");
    pollAllocations("FMC1", 20);
    expectWagonType("FMC1", "NW5", 20);
    markPaid("FMB1");
    pollAllocations("FMB1", 14);
    expectWagonType("FMB1", "CW4", 14);
    markPaid("FMC2");
    pollAllocations("FMC2", 20);
    expectWagonType("FMC2", "NW5", 20);

    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("pay-or-lose day: container pays, bulk pays, the second container reserves unpaid", () => {
    createImportSchedule({
      departure: PAYLOSE_DEPARTURE,
      locoPair: ["LOCO-EXP-9", "LOCO-EXP-10"],
      originCode: EXP_ORIGIN,
      destCode: EXP_DEST,
    });
    withExportSchedule(PAYLOSE_DEPARTURE, (s) => forceWindowOpen(s.id, 60));

    bookContainers({
      suffix: "PLC1",
      runStamp: stamp,
      isoSeed: 14_400,
      twenty: 40,
      scheduledDate: PAYLOSE_DAY,
    });
    acceptExport("PLC1");
    markPaid("PLC1");
    pollAllocations("PLC1", 20);

    bookBulk({ suffix: "PLB1", tons: 980, scheduledDate: PAYLOSE_DAY });
    acceptExport("PLB1");
    markPaid("PLB1");
    pollAllocations("PLB1", 14);

    bookContainers({
      suffix: "PLC2",
      runStamp: stamp,
      isoSeed: 14_500,
      twenty: 40,
      scheduledDate: PAYLOSE_DAY,
    });
    acceptExport("PLC2");
  });

  it("the unpaid container expires — a BULK booking takes its freed slots (typed wagons)", () => {
    forceReservationExpiry("PLC2");
    bookBulk({ suffix: "PLB2", tons: 1400, scheduledDate: PAYLOSE_DAY });
    acceptExport("PLB2");
    markPaid("PLB2");
    pollAllocations("PLB2", 20);
    // Slots are type-blind; the physical wagons are not — wheat rides CW4.
    expectWagonType("PLB2", "CW4", 20);
  });

  it("the train is FULL again — a 1-wagon container afterthought bounces", () => {
    bookContainers({
      suffix: "PLS",
      runStamp: stamp,
      isoSeed: 14_600,
      twenty: 2,
      scheduledDate: PAYLOSE_DAY,
      expectFailure: /space|window/i,
    });
  });
});

export {};
