/**
 * GENERAL rush hour — 20 "users" (20 GENERAL contracts) book one 54-wagon
 * import train in the SAME first window (D+23). Every GENERAL booking clears
 * PER BOOKING (upload → GL approve → finalize → proceed → ops accept) before
 * it may enter the pool. Then:
 *
 *   – a 21st booking whose operation request is never accepted is EXPIRED by
 *     the batch at doc-review end (it can no longer make the train)
 *   – the batch reserves the top 9 (9 × 6w = 54/54); 11 wait
 *   – the payment race: 5 pay, 4 miss their deadline → the freed 24 wagons
 *     promote the next 4 waiters live (payment phase extends); they pay →
 *     the train still departs FULL
 *   – the 7 left-over waiters expire in the day-end sweep; every contract
 *     stays CONTRACT_ACTIVE (GENERAL survives its bookings)
 *
 * Sequential steps of one journey — retries off.
 */

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

const DEPARTURE = departureAt(23);
const BOOKING_DAY = eatDayStr(DEPARTURE);

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

/** 20 virtual users — one GENERAL contract each, 12×20ft = 6 wagons per booking. */
const USERS = Array.from({ length: 20 }, (_, i) =>
  `GR${String(i + 1).padStart(2, "0")}`,
);
const SELECTED = USERS.slice(0, 9); // 9 × 6w = 54
const PAYERS = SELECTED.slice(0, 5);
const DEFAULTERS = SELECTED.slice(5, 9);
const PROMOTED = USERS.slice(9, 13); // take the defaulters' 24 wagons
const LEFTOVER = USERS.slice(13); // 7 expire with the day

describe("GENERAL rush hour: 20 users, one train, one window", { retries: 0 }, () => {
  before(() => {
    cy.task("db:seedFile", "seed-import-corridor.sql");
    [...USERS, "GRNA"].forEach((suffix) =>
      seedImportContract({ suffix, reference: stampedRef(suffix), kind: "GENERAL" }),
    );
  });

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

  it("20 users book at once — each GENERAL booking clears PER BOOKING into the pool", () => {
    USERS.forEach((suffix, i) => {
      bookContainers({
        suffix,
        runStamp: stamp,
        isoSeed: 16_000 + i * 15,
        twenty: 12,
        scheduledDate: BOOKING_DAY,
      });
      clearGeneralBooking(suffix, BOOKING_DAY);
    });
    USERS.forEach((suffix, i) => setPriority(suffix, i + 1));
  });

  it("a 21st booking never accepted by operations is expired when doc review ends", () => {
    bookContainers({
      suffix: "GRNA",
      runStamp: stamp,
      isoSeed: 16_500,
      twenty: 12,
      scheduledDate: BOOKING_DAY,
    });
    // Clearance done, operation requested — but ops never accept it.
    withBooking("GRNA", (b) => {
      cy.log(`GRNA ${b.reference} stays OPERATION_REQUEST_PENDING`);
    });
    db(
      `UPDATE freight.bookings b SET status = 'OPERATION_REQUEST_PENDING'
       FROM freight.contracts ct
       WHERE ct.id = b.contract_id AND ct.reference LIKE 'CTR-IMP-%-GRNA'
         AND b.status = 'AWAITING_DOCUMENTS'`,
      [],
    );

    withSchedule(DEPARTURE, (s) => {
      closeBookingWindow(s.id);
      completeDocReview(s.id);
    });
    // Never-accepted bookings are swept BEFORE the batch runs.
    pollBookingStatus("GRNA", "EXPIRED");
  });

  it("the batch reserves the top 9 (54/54); 11 wait with no pay window", () => {
    SELECTED.forEach((suffix) =>
      pollBookingStatus(suffix, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
    );
    [...PROMOTED, ...LEFTOVER].forEach((suffix) =>
      withBooking(suffix, (b) => {
        expect(b.status, `${suffix} waiting`).to.eq("FULLY_EXECUTED");
        expect(b.payment_deadline, `${suffix} no pay window yet`).to.be.null;
      }),
    );
  });

  it("payment race: 5 pay, 4 default — the freed 24 wagons promote the next 4 waiters live", () => {
    PAYERS.forEach((suffix) => {
      markPaid(suffix);
      pollAllocations(suffix, 6);
    });
    DEFAULTERS.forEach((suffix) => forceReservationExpiry(suffix));
    PROMOTED.forEach((suffix) =>
      pollBookingStatus(suffix, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
    );
  });

  it("the promoted 4 pay — the train departs FULL at 54/54, typed", () => {
    PROMOTED.forEach((suffix) => {
      markPaid(suffix);
      pollAllocations(suffix, 6);
      expectWagonType(suffix, "NW5", 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));
    });
  });

  it("the 7 leftover waiters expire in ONE day-end sweep; every contract stays ACTIVE", () => {
    LEFTOVER.forEach((suffix) => pollBookingStatus(suffix, "EXPIRED"));
    db<{ n: string }>(
      `SELECT count(*) AS n FROM freight.contracts
       WHERE reference LIKE 'CTR-IMP-%-GR%' AND status = 'CONTRACT_ACTIVE'
         AND deleted_at IS NULL AND created_at > now() - interval '1 hour'`,
      [],
    ).then(({ rows }) =>
      expect(Number(rows[0].n), "GENERAL contracts survive their bookings").to.be.at.least(21),
    );
  });
});

export {};
