/**
 * BULK EXPORT critical matrix (reversed corridor, D+13):
 *
 *   1. gate: a wheat booking on a day with no open window is rejected
 *   2. sub-corridor bulk export (DIRE_DAWA → DJIB_PORT, 980 T) boards
 *      mid-route and shares the train with a KALITY 2 800 T through-booking
 *   3. directional FULL: through 40w + sub 14w commit the border edges → the
 *      export window flips FULL while the home leg still has 14 free wagons
 *   4. bulk intercity ride-along on the FULL export train's free home leg
 *      (KALITY → MOJO, DOMESTIC, 140 T, dateless) — accepted, pay window
 *      clamped to the EXPORT close, paid + linked; the window stays FULL
 *   5. same-day sibling export train keeps its OWN window (no group rule)
 *
 * Sequential steps — retries off.
 */

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

const DEPARTURE = departureAt(13);
const BOOKING_DAY = eatDayStr(DEPARTURE);
const NO_WINDOW_DAY = eatDayStr(departureAt(20)); // no schedule exists there

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

describe("bulk export matrix: sub-corridor, directional FULL, bulk intercity, own windows", { retries: 0 }, () => {
  before(() => {
    cy.task("db:seedFile", "seed-import-corridor.sql");
    seedImportContract({
      suffix: "YM1",
      reference: stampedRef("YM1"),
      freight: "BULK",
      direction: "EXPORT",
      originCode: EXP_ORIGIN,
      destCode: EXP_DEST,
    });
    seedImportContract({
      suffix: "YMSUB",
      reference: stampedRef("YMSUB"),
      freight: "BULK",
      direction: "EXPORT",
      originCode: "DIRE_DAWA",
      destCode: EXP_DEST,
    });
    seedImportContract({
      suffix: "YMIC",
      reference: stampedRef("YMIC"),
      freight: "BULK",
      direction: "DOMESTIC",
      originCode: EXP_ORIGIN,
      destCode: "MOJO",
    });
  });

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

  it("gate: a wheat booking on a day with no open window is rejected", () => {
    bookBulk({
      suffix: "YM1",
      tons: 140,
      scheduledDate: NO_WINDOW_DAY,
      expectFailure: "booking window",
    });
  });

  it("a KALITY through-booking (2 800 T) and a DIRE_DAWA sub-corridor booking (980 T) share the train", () => {
    bookBulk({ suffix: "YM1", tons: 2800, scheduledDate: BOOKING_DAY });
    acceptExport("YM1");
    bookBulk({ suffix: "YMSUB", tons: 980, scheduledDate: BOOKING_DAY });
    acceptExport("YMSUB");

    markPaid("YM1");
    pollAllocations("YM1", 40);
    markPaid("YMSUB");
    pollAllocations("YMSUB", 14);

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

  it("the border edges are committed — the export window flips FULL (home leg still free)", () => {
    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("bulk intercity ride-along boards the FULL train's free home leg — clamped, paid, linked", () => {
    bookBulk({ suffix: "YMIC", tons: 140 }); // 2 wagons KALITY → MOJO, dateless
    withBooking("YMIC", (b) => {
      apiPost(opsStaff, `/api/bookings/${b.id}/operation/review`, { decision: "ACCEPT" })
        .its("status")
        .should("be.oneOf", [200, 201]);
    });
    pollBookingStatus("YMIC", "FULLY_EXECUTED", 10);

    withExportSchedule(DEPARTURE, (s) => {
      withBooking("YMIC", (b) => {
        apiPost(opsStaff, `/api/train-scheduling/schedules/${s.id}/intercity/accept`, {
          bookingIds: [b.id],
        })
          .its("status")
          .should("be.oneOf", [200, 201]);
      });
    });
    pollBookingStatus("YMIC", ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]);
    withExportSchedule(DEPARTURE, (s) => {
      withBooking("YMIC", (b) => {
        expect(new Date(b.payment_deadline!).getTime()).to.be.at.most(
          new Date(s.window_closes_at!).getTime(),
        );
      });
    });
    markPaid("YMIC");
    withExportSchedule(DEPARTURE, (s) => {
      withBooking("YMIC", (b) => {
        expect(b.train_schedule_id, "linked to the export train").to.eq(s.id);
      });
      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-3", "LOCO-EXP-4"],
      kind: "bulk",
      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);
        expect(rows[0].window_phase, "own fresh window").to.eq("PRE_WINDOW");
        expect(
          new Date(rows[0].window_closes_at).getTime(),
          "own close, anchored to its own departure",
        ).to.not.eq(new Date(anchor.window_closes_at!).getTime());
      });
    });
  });
});

export {};
