/**
 * EXPORT critical-scenario matrix (reversed corridor, D+7):
 *
 *   1. sub-corridor export: a DIRE_DAWA → DJIB_PORT booking boards mid-route
 *      and shares the train with a KALITY through-booking
 *   2. directional FULL: through 40w + sub-corridor 14w commit every wagon on
 *      the border edges → the export window flips FULL while the KALITY→MOJO
 *      home leg still has 14 free wagons
 *   3. intercity ride-along on the FULL export train's free home leg
 *      (KALITY → MOJO, DOMESTIC, dateless) — accepted, pay deadline clamped
 *      to the EXPORT window close, paid + linked; the window stays FULL
 *   4. same-day sibling export train keeps its OWN window — export is
 *      excluded from the import route-day group rule
 *   5. duplicate ISO container number across two bookings of the same
 *      route-day is rejected
 *
 * Sequential steps — retries off.
 */

import {
  acceptExport,
  apiPost,
  bookContainers,
  createImportSchedule,
  db,
  departureAt,
  eatDayStr,
  ensureExportRoute,
  EXP_DEST,
  EXP_ORIGIN,
  forceWindowOpen,
  isoNumber,
  markPaid,
  opsStaff,
  pollAllocations,
  pollBookingStatus,
  pollDb,
  resetCorridorDay,
  seedImportContract,
  withBooking,
  withExportSchedule,
  type ScheduleRow,
} from "./import-utils";

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

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

describe("export matrix: sub-corridor, directional FULL, intercity on FULL train, own windows", { retries: 0 }, () => {
  before(() => {
    cy.task("db:seedFile", "seed-import-corridor.sql");
    seedImportContract({
      suffix: "EM1",
      reference: stampedRef("EM1"),
      direction: "EXPORT",
      originCode: EXP_ORIGIN,
      destCode: EXP_DEST,
    });
    seedImportContract({
      suffix: "EMSUB",
      reference: stampedRef("EMSUB"),
      direction: "EXPORT",
      originCode: "DIRE_DAWA",
      destCode: EXP_DEST,
    });
    seedImportContract({
      suffix: "EMIC",
      reference: stampedRef("EMIC"),
      direction: "DOMESTIC",
      originCode: EXP_ORIGIN,
      destCode: "MOJO",
    });
    seedImportContract({
      suffix: "EMDUP",
      reference: stampedRef("EMDUP"),
      direction: "EXPORT",
      originCode: EXP_ORIGIN,
      destCode: EXP_DEST,
    });
  });

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

  it("a KALITY through-booking (40w) and a DIRE_DAWA sub-corridor booking (14w) share the train", () => {
    bookContainers({
      suffix: "EM1",
      runStamp: stamp,
      isoSeed: 8600,
      twenty: 80, // 40 wagons, full corridor
      scheduledDate: BOOKING_DAY,
    });
    acceptExport("EM1");

    // While the window is still OPEN (14 wagons free): a booking reusing one
    // of EM1's ISO container numbers on the same route-day is rejected.
    db<{ id: string }>(
      `SELECT id FROM freight.contracts
       WHERE reference LIKE 'CTR-IMP-%-' || $1
       ORDER BY created_at DESC LIMIT 1`,
      ["EMDUP"],
    ).then(({ rows }) => {
      apiPost(
        "user@gmail.com",
        `/api/contracts/${rows[0].id}/bookings`,
        {
          scheduledDate: BOOKING_DAY,
          containers: [
            {
              containerSize: "20ft",
              quantity: 2,
              units: [
                { containerNumber: isoNumber(stamp, 8600), vgmTons: 10 },
                { containerNumber: isoNumber(stamp, 9990), vgmTons: 10 },
              ],
            },
          ],
        },
        false,
      ).then((res) => {
        expect(res.status, "cross-booking ISO clash rejected").to.be.within(400, 422);
        expect(JSON.stringify(res.body).toLowerCase()).to.include("container");
      });
    });

    bookContainers({
      suffix: "EMSUB",
      runStamp: stamp,
      isoSeed: 8700,
      twenty: 28, // 14 wagons, boards mid-route at DIRE_DAWA
      scheduledDate: BOOKING_DAY,
    });
    acceptExport("EMSUB");

    markPaid("EM1");
    pollAllocations("EM1", 40);
    markPaid("EMSUB");
    pollAllocations("EMSUB", 14);

    withExportSchedule(DEPARTURE, (s) => {
      withBooking("EM1", (b) => expect(b.train_schedule_id).to.eq(s.id));
      withBooking("EMSUB", (b) => expect(b.train_schedule_id).to.eq(s.id));
    });
  });

  it("the border edges are committed — the export window flips FULL (home leg still empty)", () => {
    withExportSchedule(DEPARTURE, (s) => {
      pollDb<ScheduleRow>(
        "directional FULL",
        `SELECT booking_window_status FROM freight.train_schedules WHERE id = $1`,
        [s.id],
        (row) => row?.booking_window_status === "FULL",
      );
    });
  });

  it("intercity ride-along boards the FULL train's free home leg — deadline clamped to the export close", () => {
    bookContainers({
      suffix: "EMIC",
      runStamp: stamp,
      isoSeed: 8800,
      twenty: 4, // 2 wagons KALITY → MOJO, dateless
    });
    withBooking("EMIC", (b) => {
      apiPost(opsStaff, `/api/bookings/${b.id}/operation/review`, { decision: "ACCEPT" })
        .its("status")
        .should("be.oneOf", [200, 201]);
    });
    pollBookingStatus("EMIC", "FULLY_EXECUTED", 10);

    withExportSchedule(DEPARTURE, (s) => {
      withBooking("EMIC", (b) => {
        apiPost(opsStaff, `/api/train-scheduling/schedules/${s.id}/intercity/accept`, {
          bookingIds: [b.id],
        })
          .its("status")
          .should("be.oneOf", [200, 201]);
      });
    });
    pollBookingStatus("EMIC", ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]);
    withExportSchedule(DEPARTURE, (s) => {
      withBooking("EMIC", (b) => {
        // Export parity: the ride-along's pay window never outlives the close.
        expect(new Date(b.payment_deadline!).getTime()).to.be.at.most(
          new Date(s.window_closes_at!).getTime(),
        );
      });
    });
    markPaid("EMIC");
    withExportSchedule(DEPARTURE, (s) => {
      withBooking("EMIC", (b) => {
        expect(b.train_schedule_id, "linked to the export train").to.eq(s.id);
      });
      // The ride-along never reopens the export window.
      expect(s.booking_window_status, "window stays FULL").to.eq("FULL");
    });
  });

  it("a same-day sibling export train keeps its OWN window — no route-day group for export", () => {
    const sibling = new Date(DEPARTURE.getTime() + 90 * 60_000);
    createImportSchedule({
      departure: sibling,
      locoPair: ["LOCO-EXP-1", "LOCO-EXP-2"],
      originCode: EXP_ORIGIN,
      destCode: EXP_DEST,
    });
    withExportSchedule(DEPARTURE, (anchor) => {
      db<{ id: string; window_phase: string; window_closes_at: string }>(
        `SELECT ts.id, ts.window_phase, ts.window_closes_at
         FROM freight.train_schedules ts
         JOIN freight.yards o ON o.id = ts.origin_station_id AND o.code = $1
         JOIN freight.yards d ON d.id = ts.destination_station_id AND d.code = $2
         WHERE ts.deleted_at IS NULL AND ts.id <> $3
           AND abs(extract(epoch FROM (ts.scheduled_departure_date - $4::timestamptz))) < 7200
         ORDER BY ts.created_at DESC LIMIT 1`,
        [EXP_ORIGIN, EXP_DEST, anchor.id, DEPARTURE.toISOString()],
      ).then(({ rows }) => {
        expect(rows, "sibling export schedule").to.have.length(1);
        // The anchor's window was forced/consumed (FULL); a joiner under the
        // import group rule would copy its live state. Export siblings don't:
        // this one starts its own PRE_WINDOW timeline anchored to its OWN
        // departure.
        expect(rows[0].window_phase, "own fresh window").to.eq("PRE_WINDOW");
        expect(
          new Date(rows[0].window_closes_at).getTime(),
          "own close, clamped to its own departure",
        ).to.not.eq(new Date(anchor.window_closes_at!).getTime());
      });
    });
  });

});

export {};
