/**
 * BATCH — government preemption:
 *
 *   A government booking that fits nowhere on an already-committed train
 *   displaces the LOWEST-priority commercial reservation first (never the
 *   higher-priority one), then allocates directly — no pay window, since
 *   government rides unpaid (see booking-batch.service.ts `preemptForGovernment`
 *   + `allocate(..., "gov")`).
 *
 *   Setup: a 3-wagon import train. CGA (priority 1, higher) and CGB (priority
 *   2, lower) each book one 40ft container and reserve via the normal batch —
 *   2/3 wagons used, 1 free. A standalone government booking (2×40ft = 2
 *   wagons) then needs more than the 1 free slot: it doesn't fit, so the
 *   engine preempts CGB (the lower-priority reservation) to free the second
 *   wagon, then allocates the government booking. CGA is untouched throughout.
 *
 *   Government bookings don't go through the customer contract wizard —
 *   they're created directly via `POST /bookings` (isGovernment: true, billed
 *   to a real government company/profile — see seed-government.sql) and
 *   promoted with `POST /bookings/:id/government-expedite`.
 *
 * Sequential steps of one scenario — retries off.
 */

import {
  acceptOperation,
  apiPost,
  bookContainers,
  closeBookingWindow,
  completeDocReview,
  createImportSchedule,
  db,
  departureAt,
  DEST,
  eatDayStr,
  ensureCorridorRoute,
  forceWindowOpen,
  ORIGIN,
  pollBookingStatus,
  pollDb,
  resetCorridorDay,
  seedImportContract,
  setPriority,
  superAdmin,
  withBooking,
  withSchedule,
  type ScheduleRow,
} from "./import-utils";

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

const GOV_COMPANY_ID = "0a1b0001-0000-4000-8000-000000000001";
const GOV_PROFILE_ID = "0b1c0001-0000-4000-8000-000000000001";

function withScheduleId(fn: (id: string, s: ScheduleRow) => void) {
  withSchedule(DEPARTURE, (s) => fn(s.id, s));
}

interface GovBookingRow {
  id: string;
  status: string;
  scheduling_status: string;
  is_government: boolean;
  train_schedule_id: string | null;
}

/** This run's government booking — always re-queried, never trusted from a
 * create-response shape (only one exists per run: newest for this gov company). */
function withGovBooking(fn: (b: GovBookingRow) => void) {
  db<GovBookingRow>(
    `SELECT id, status, scheduling_status, is_government, train_schedule_id
     FROM freight.bookings
     WHERE company_id = $1 AND is_government = true AND deleted_at IS NULL
     ORDER BY created_at DESC LIMIT 1`,
    [GOV_COMPANY_ID],
  ).then(({ rows }) => {
    expect(rows, "this run's government booking").to.have.length(1);
    fn(rows[0]);
  });
}

describe("batch: government booking preempts the lowest-priority commercial reservation", { retries: 0 }, () => {
  before(() => {
    cy.task("db:seedFile", "seed-import-corridor.sql");
    cy.task("db:seedFile", "seed-government.sql");
    ["CGA", "CGB"].forEach((suffix) =>
      seedImportContract({ suffix, reference: stampedRef(suffix) }),
    );
  });

  it("operations schedules the dedicated 3-wagon built train (TRN-GOV-1) — window forced open", () => {
    ensureCorridorRoute();
    resetCorridorDay(DEPARTURE);
    // A loco-pair schedule's max_wagons is NOT a real cap — it gets recomputed
    // from the locomotive's length every fill pass (54 for a standard loco),
    // ignoring maxWagonsPerTrain. A BUILT train's coupled count IS the cap.
    createImportSchedule({
      departure: DEPARTURE,
      trainCode: "TRN-GOV-1",
      maxWagons: 3,
    });
    withScheduleId((id) => forceWindowOpen(id, 45));
  });

  it("CGA and CGB each book one 40ft and reserve — 2/3 wagons used, 1 free", () => {
    bookContainers({
      suffix: "CGA",
      runStamp: stamp,
      isoSeed: 0,
      forty: 1,
      scheduledDate: BOOKING_DAY,
    });
    acceptOperation("CGA");
    bookContainers({
      suffix: "CGB",
      runStamp: stamp,
      isoSeed: 10,
      forty: 1,
      scheduledDate: BOOKING_DAY,
    });
    acceptOperation("CGB");
    setPriority("CGA", 1); // higher priority — must survive
    setPriority("CGB", 2); // lower priority — the preemption target

    withScheduleId((id) => {
      closeBookingWindow(id);
      completeDocReview(id);
    });

    withBooking("CGA", (b) =>
      expect(b.status, "CGA reserved").to.be.oneOf(["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
    );
    withBooking("CGB", (b) =>
      expect(b.status, "CGB reserved").to.be.oneOf(["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
    );
  });

  it("a government booking (2×40ft) is created and expedited to PAID, then pinned to the train", () => {
    db<{ id: string }>(
      `SELECT id FROM freight.yards WHERE code = $1`,
      [ORIGIN],
    ).then(({ rows: o }) => {
      db<{ id: string }>(`SELECT id FROM freight.yards WHERE code = $1`, [DEST]).then(
        ({ rows: d }) => {
          db<{ id: string }>(
            `SELECT id FROM freight.service_types ORDER BY created_at LIMIT 1`,
          ).then(({ rows: st }) => {
            db<{ id: string }>(
              `SELECT id FROM freight.container_types WHERE size_ft = 40 AND is_active LIMIT 1`,
            ).then(({ rows: ct }) => {
              apiPost(superAdmin, "/api/bookings", {
                isGovernment: true,
                companyId: GOV_COMPANY_ID,
                companyProfileId: GOV_PROFILE_ID,
                contractType: "NEW",
                serviceTypeId: st[0].id,
                equipmentReturn: "WITHOUT_RETURN",
                originYardId: o[0].id,
                destinationYardId: d[0].id,
                tradeDirection: "IMPORT",
                freightType: "CONTAINER",
                containers: [{ containerTypeId: ct[0].id, quantity: 2, vgmPerUnitTons: 10 }],
                cargoTotalWeightVgm: 20,
                paymentCurrency: "USD",
              }).then((res) => {
                expect(res.status, "government booking created").to.be.oneOf([200, 201]);

                withGovBooking((created) => {
                  apiPost(
                    superAdmin,
                    `/api/bookings/${created.id}/government-expedite`,
                  ).then((expRes) => {
                    expect(expRes.status, "expedited").to.be.oneOf([200, 201]);
                  });
                });
                withGovBooking((b) => {
                  expect(b.status, "PAID after expedite").to.eq("PAID");
                  expect(b.is_government, "flagged government").to.eq(true);

                  // Staff pin onto this schedule (the customer-facing OPEN-window
                  // pin can't be used here — the window already closed when
                  // completeDocReview ran). Mirrors a staff manual assign.
                  withScheduleId((scheduleId) => {
                    db(
                      `UPDATE freight.bookings SET train_schedule_id = $1 WHERE id = $2`,
                      [scheduleId, b.id],
                    );
                  });
                });
              });
            });
          });
        },
      );
    });
  });

  it("run-batch: the government booking preempts CGB (lower priority), CGA is untouched", () => {
    withScheduleId((scheduleId) => {
      apiPost(superAdmin, `/api/train-scheduling/schedules/${scheduleId}/run-batch`).then(
        (res) => expect(res.status, "run-batch").to.be.oneOf([200, 201]),
      );
    });

    // CGB displaced: EXPIRED, back to ELIGIBLE, no pay window, invoice expired.
    pollBookingStatus("CGB", "EXPIRED");
    withBooking("CGB", (b) => {
      expect(b.scheduling_status, "CGB back to ELIGIBLE").to.eq("ELIGIBLE");
      expect(b.payment_deadline, "CGB pay window cleared").to.be.null;
      db<{ n: string }>(
        `SELECT count(*) AS n FROM freight.invoices
          WHERE source = 'booking' AND source_id = $1
            AND status NOT IN ('EXPIRED','CANCELLED','PAID') AND deleted_at IS NULL`,
        [b.id],
      ).then(({ rows }) => expect(Number(rows[0].n), "CGB invoice expired").to.eq(0));
    });

    // CGA survives untouched — still reserved on the same train.
    withScheduleId((scheduleId) => {
      withBooking("CGA", (b) => {
        expect(b.status, "CGA still reserved").to.be.oneOf([
          "SELECTED_FOR_BATCH",
          "AWAITING_PAYMENT",
        ]);
        expect(b.train_schedule_id, "CGA still on this train").to.eq(scheduleId);
      });
    });

    // Government booking allocated: SCHEDULED + linked via the schedule-bookings
    // table (allocate() never sets the train_schedule_id column for gov — only
    // the link row — so the pin from the previous step is what carries it).
    withGovBooking((b) => {
      expect(b.scheduling_status, "gov SCHEDULED").to.eq("SCHEDULED");
      withScheduleId((scheduleId) => {
        db<{ n: string }>(
          `SELECT count(*) AS n FROM freight.train_schedule_bookings
            WHERE booking_id = $1 AND train_schedule_id = $2 AND deleted_at IS NULL`,
          [b.id, scheduleId],
        ).then(({ rows }) => expect(Number(rows[0].n), "gov linked to schedule").to.eq(1));
      });
      pollDb<{ n: string }>(
        "gov booking allocated 2 wagons",
        `SELECT count(*) AS n FROM freight.wagon_booking_allocations WHERE booking_id = $1`,
        [b.id],
        (row) => Number(row?.n ?? 0) >= 2,
      );
    });
  });
});

export {};
