/**
 * MIXED IMPORT journey M3 — a container split and a CROSS-TYPE promotion:
 * container-freed wagons hand the train to the waiting bulk queue.
 *
 * Queue reality: containers order by staff priority, bulk is rule-recomputed
 * and orders by acceptance. Fill (54 slots):
 *   MSC container 40×20ft = 20w (prio 1) — pays
 *   MSD container 40×20ft = 20w (prio 2) — reserved, never pays
 *   MSX container 48×20ft = 24w (prio 3) — only 14 left → PARTIAL offer 14w;
 *       settles via the real payment pipeline → split applies (28 boxes ride,
 *       20×20ft outstanding)
 *   bulk queue (acceptance order): MSB 840 T = 12w, MSW2 560 T = 8w,
 *       MSW3 700 T = 10w — no room at fill, ALL wait.
 *   MSD expires → its 20 container wagons promote the BULK queue: MSB (12w)
 *   and MSW2 (8w) get pay windows; both pay → CW4 gondolas under the wheat →
 *   54/54 = 20 NW5 + 14 NW5 + 12 CW4 + 8 CW4. MSW3 expires with the day, and
 *   the split customer must later rebook EXACTLY the 20×20ft remainder.
 *
 * Sequential steps of one journey — retries off.
 */

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

const DEPARTURE = departureAt(20);
const BOOKING_DAY = eatDayStr(DEPARTURE);
const REMAINDER_DEPARTURE = departureAt(22);
const REMAINDER_DAY = eatDayStr(REMAINDER_DEPARTURE);

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

const CONTAINERS = [
  { suffix: "MSC", twenty: 40, wagons: 20 },
  { suffix: "MSD", twenty: 40, wagons: 20 },
  { suffix: "MSX", twenty: 48, wagons: 24 },
];
const BULKS = [
  { suffix: "MSB", tons: 840, wagons: 12 },
  { suffix: "MSW2", tons: 560, wagons: 8 },
  { suffix: "MSW3", tons: 700, wagons: 10 },
];

describe("mixed import: container split; container-freed wagons promote the bulk queue", { retries: 0 }, () => {
  before(() => {
    cy.task("db:seedFile", "seed-import-corridor.sql");
    CONTAINERS.forEach((b) =>
      seedImportContract({ suffix: b.suffix, reference: stampedRef(b.suffix) }),
    );
    BULKS.forEach((b) =>
      seedImportContract({ suffix: b.suffix, reference: stampedRef(b.suffix), freight: "BULK" }),
    );
  });

  it("operations prepares the corridor train with an open first window", () => {
    ensureCorridorRoute();
    resetCorridorDay(DEPARTURE);
    resetCorridorDay(REMAINDER_DEPARTURE);
    createImportSchedule({ departure: DEPARTURE, locoPair: ["LOCO-IMP-5", "LOCO-IMP-6"] });
    withSchedule(DEPARTURE, (s) => forceWindowOpen(s.id, 45));
  });

  it("six mixed shipments book in the first window; accepted in queue order", () => {
    let isoSeed = 10_000;
    CONTAINERS.forEach((b) => {
      bookContainers({
        suffix: b.suffix,
        runStamp: stamp,
        isoSeed,
        twenty: b.twenty,
        scheduledDate: BOOKING_DAY,
      });
      isoSeed += b.twenty;
      acceptOperation(b.suffix);
    });
    BULKS.forEach((b) => {
      bookBulk({ suffix: b.suffix, tons: b.tons, scheduledDate: BOOKING_DAY });
      acceptOperation(b.suffix);
    });
    CONTAINERS.forEach((b, i) => setPriority(b.suffix, i + 1));
  });

  it("the batch reserves the containers (MSX gets a 14w PARTIAL); the bulk queue waits whole", () => {
    withSchedule(DEPARTURE, (s) => {
      closeBookingWindow(s.id);
      completeDocReview(s.id);
    });
    CONTAINERS.forEach((b) =>
      pollBookingStatus(b.suffix, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
    );
    withBooking("MSX", (b) => {
      pollDb<{ status: string }>(
        "MSX open partial offer",
        `SELECT status FROM freight.booking_batch_offers
         WHERE booking_id = $1 AND deleted_at IS NULL
         ORDER BY created_at DESC LIMIT 1`,
        [b.id],
        (row) => row?.status === "OFFERED",
        10,
      );
    });
    BULKS.forEach((b) =>
      withBooking(b.suffix, (row) => {
        expect(row.status, `${b.suffix} waiting`).to.eq("FULLY_EXECUTED");
      }),
    );
  });

  it("MSC pays; MSX settles via the gateway — the split applies (14 × NW5, 28 boxes ride)", () => {
    markPaid("MSC");
    pollAllocations("MSC", 20);
    expectWagonType("MSC", "NW5", 20);

    settleViaGateway("MSX");
    pollAllocations("MSX", 14);
    expectWagonType("MSX", "NW5", 14);
    withBooking("MSX", (b) => {
      expect(b.is_split, "MSX is split").to.eq(true);
      db<{ q: string }>(
        `SELECT sum(quantity) AS q FROM freight.booking_container
         WHERE booking_id = $1 AND deleted_at IS NULL`,
        [b.id],
      ).then(({ rows }) => expect(Number(rows[0].q), "MSX shrank to 28 boxes").to.eq(28));
    });
  });

  it("the unpaid container expires — its wagons promote the BULK queue (MSB + MSW2 get pay windows)", () => {
    forceReservationExpiry("MSD");
    ["MSB", "MSW2"].forEach((suffix) =>
      pollBookingStatus(suffix, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
    );
    withBooking("MSW3", (b) => {
      expect(b.status, "the 10w bulk still has no seat").to.eq("FULLY_EXECUTED");
    });
  });

  it("both promoted bulks pay — 54/54 typed (NW5 under boxes, CW4 under wheat); MSW3 expires", () => {
    markPaid("MSB");
    pollAllocations("MSB", 12);
    expectWagonType("MSB", "CW4", 12);
    markPaid("MSW2");
    pollAllocations("MSW2", 8);
    expectWagonType("MSW2", "CW4", 8);

    withSchedule(DEPARTURE, (s) => {
      endPaymentPhase(s.id);
      pollDb<ScheduleRow>(
        "window FULL + DONE",
        `SELECT window_phase, booking_window_status FROM freight.train_schedules WHERE id = $1`,
        [s.id],
        (row) => row?.booking_window_status === "FULL" && row?.window_phase === "DONE",
      );
      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));
    });
    pollBookingStatus("MSW3", "EXPIRED");
  });

  it("the split container customer must rebook EXACTLY the 20×20ft remainder", () => {
    createImportSchedule({
      departure: REMAINDER_DEPARTURE,
      locoPair: ["LOCO-IMP-7", "LOCO-IMP-8"],
    });
    withSchedule(REMAINDER_DEPARTURE, (s) => forceWindowOpen(s.id, 45));

    bookContainers({
      suffix: "MSX",
      runStamp: stamp,
      isoSeed: 11_000,
      twenty: 8,
      scheduledDate: REMAINDER_DAY,
      expectFailure: "must take the whole remainder",
    });
    bookContainers({
      suffix: "MSX",
      runStamp: stamp,
      isoSeed: 11_100,
      twenty: 20,
      scheduledDate: REMAINDER_DAY,
    });
    pollBookingStatus("MSX", "OPERATION_REQUEST_PENDING", 5);
  });
});

export {};
