/**
 * BULK IMPORT journey 3 — split offer, exact-remainder rebooking, pay-window
 * expiry and priority-ordered waiting-list promotion on one 54-wagon CW4
 * train (bulk splits are FULL-WAGONS-ONLY at the base 70 T cap):
 *
 *   reserved (priority order): BSA 1 400 T = 20w, BSB 980 T = 14w,
 *   BSD 840 T = 12w → 46w. BSC 1 680 T = 24w does NOT fit whole → PARTIAL
 *   offer of the remaining 8 wagons = 560 T. BSC settles via the real payment
 *   pipeline → split applies (is_split + snapshot); the outstanding 1 120 T
 *   must later be rebooked EXACTLY (a wrong tonnage is rejected).
 *   BSD never pays → EXPIRES; the freed 12 wagons promote BS1 (420 T = 6w)
 *   and BS2 (420 T = 6w); BS3 (1 400 T = 20w) never fits and expires.
 *
 *   Final consist: 20 + 14 + 8 + 6 + 6 = 54/54.
 *
 * Sequential steps of one journey — retries off.
 */

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

const DEPARTURE = departureAt(12);
const BOOKING_DAY = eatDayStr(DEPARTURE);
const REMAINDER_DEPARTURE = departureAt(14);
const REMAINDER_DAY = eatDayStr(REMAINDER_DEPARTURE);

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

const ORDER = ["BSA", "BSB", "BSD", "BSC", "BS1", "BS2", "BS3"] as const;
const TONS: Record<string, number> = {
  BSA: 1400,
  BSB: 980,
  BSD: 840,
  BSC: 1680,
  BS1: 420,
  BS2: 420,
  BS3: 1400,
};

describe("bulk import: split offer, remainder rebooking, expiry + promotion", { retries: 0 }, () => {
  before(() => {
    cy.task("db:seedFile", "seed-import-corridor.sql");
    ORDER.forEach((suffix) =>
      seedImportContract({ suffix, reference: stampedRef(suffix), freight: "BULK" }),
    );
  });

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

  it("seven customers book wheat in the first window; operations accepts them in priority order", () => {
    ORDER.forEach((suffix) => {
      bookBulk({ suffix, tons: TONS[suffix], scheduledDate: BOOKING_DAY });
      acceptOperation(suffix);
    });
    ORDER.forEach((suffix, i) => setPriority(suffix, i + 1));
  });

  it("the batch reserves BSA/BSB/BSD whole and offers BSC a PARTIAL for the last 8 wagons (560 T)", () => {
    withSchedule(DEPARTURE, (s) => {
      closeBookingWindow(s.id);
      completeDocReview(s.id);
    });
    ["BSA", "BSB", "BSD", "BSC"].forEach((suffix) =>
      pollBookingStatus(suffix, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
    );
    withBooking("BSC", (b) => {
      pollDb<{ status: string }>(
        "BSC 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,
      );
    });
    ["BS1", "BS2", "BS3"].forEach((suffix) =>
      withBooking(suffix, (b) => {
        expect(b.status, `${suffix} waiting`).to.eq("FULLY_EXECUTED");
      }),
    );
  });

  it("BSA and BSB pay; BSC settles via the gateway — the split applies (full wagons only)", () => {
    markPaid("BSA");
    pollAllocations("BSA", 20);
    markPaid("BSB");
    pollAllocations("BSB", 14);

    settleViaGateway("BSC");
    pollAllocations("BSC", 8);
    withBooking("BSC", (b) => {
      expect(b.is_split, "BSC is split").to.eq(true);
      db<{ pre_split_quantities: unknown }>(
        `SELECT pre_split_quantities FROM freight.bookings WHERE id = $1`,
        [b.id],
      ).then(({ rows }) => {
        expect(rows[0].pre_split_quantities, "pre-split snapshot").to.not.be.null;
      });
    });
  });

  it("BSD misses its pay window — EXPIRED, and the freed wagons promote BS1 + BS2 (payment sent)", () => {
    forceReservationExpiry("BSD");
    ["BS1", "BS2"].forEach((suffix) =>
      pollBookingStatus(suffix, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
    );
    ["BS1", "BS2"].forEach((suffix) =>
      withBooking(suffix, (b) => {
        expect(b.payment_deadline, `${suffix} got a pay window`).to.be.a("string");
      }),
    );
    withBooking("BS3", (b) => {
      expect(b.status, "BS3 still has no seat").to.eq("FULLY_EXECUTED");
    });
  });

  it("BS1 and BS2 pay — the train is FULL at 54; BS3 expires with the day", () => {
    markPaid("BS1");
    pollAllocations("BS1", 6);
    markPaid("BS2");
    pollAllocations("BS2", 6);

    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("BS3", "EXPIRED");
  });

  it("the split customer must rebook EXACTLY the whole 1 120 T remainder — wrong tonnage rejected, exact accepted", () => {
    createImportSchedule({
      departure: REMAINDER_DEPARTURE,
      locoPair: ["LOCO-IMP-21", "LOCO-IMP-22"],
      kind: "bulk",
    });
    withSchedule(REMAINDER_DEPARTURE, (s) => forceWindowOpen(s.id, 45));

    // 1 680 booked − 560 shipped-by-split = 1 120 T outstanding. 560 ≠ 1 120.
    bookBulk({
      suffix: "BSC",
      tons: 560,
      scheduledDate: REMAINDER_DAY,
      expectFailure: "must take the whole",
    });

    bookBulk({ suffix: "BSC", tons: 1120, scheduledDate: REMAINDER_DAY });
    pollBookingStatus("BSC", "OPERATION_REQUEST_PENDING", 5);
  });
});

export {};
