/**
 * MIXED EXPORT journeys XM4 + XM5 — mixed window close, and a FULL mixed
 * train carrying a DOUBLE intercity ride-along:
 *
 *   XM4 (D+17): five mixed reservations (2 container + 3 bulk = 54w), only
 *   one container + one bulk pay. The window CLOSE passes → phase DONE, the
 *   three unpaid (both kinds) expire in ONE sweep, export never reopens.
 *
 *   XM5 (D+18): through container 40w + sub-corridor bulk (DIRE_DAWA → port)
 *   14w commit the border edges → directional FULL. Then a container AND a
 *   bulk intercity ride-along (KALITY → MOJO, dateless) are accepted onto the
 *   FULL train's free home leg in ONE staff action — pay windows clamped to
 *   the EXPORT close, both pay, both linked; the export window stays FULL.
 *
 * Sequential steps — retries off.
 */

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

const CLOSE_DEPARTURE = departureAt(17);
const CLOSE_DAY = eatDayStr(CLOSE_DEPARTURE);
const FULL_DEPARTURE = departureAt(18);
const FULL_DAY = eatDayStr(FULL_DEPARTURE);

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

/** 2 container + 3 bulk = 54 wagons; only QMC1 + QMB1 pay. */
const CLOSERS: Array<{
  suffix: string;
  freight: "CONTAINER" | "BULK";
  twenty?: number;
  tons?: number;
  wagons: number;
  pays: boolean;
}> = [
  { suffix: "QMC1", freight: "CONTAINER", twenty: 24, wagons: 12, pays: true },
  { suffix: "QMB1", freight: "BULK", tons: 840, wagons: 12, pays: true },
  { suffix: "QMC2", freight: "CONTAINER", twenty: 24, wagons: 12, pays: false },
  { suffix: "QMB2", freight: "BULK", tons: 840, wagons: 12, pays: false },
  { suffix: "QMB3", freight: "BULK", tons: 420, wagons: 6, pays: false },
];

describe("mixed export: mixed close sweep + FULL train with double intercity", { retries: 0 }, () => {
  before(() => {
    cy.task("db:seedFile", "seed-import-corridor.sql");
    CLOSERS.forEach((c) =>
      seedImportContract({
        suffix: c.suffix,
        reference: stampedRef(c.suffix),
        freight: c.freight,
        direction: "EXPORT",
        originCode: EXP_ORIGIN,
        destCode: EXP_DEST,
      }),
    );
    seedImportContract({
      suffix: "FMTH",
      reference: stampedRef("FMTH"),
      direction: "EXPORT",
      originCode: EXP_ORIGIN,
      destCode: EXP_DEST,
    });
    seedImportContract({
      suffix: "FMSB",
      reference: stampedRef("FMSB"),
      freight: "BULK",
      direction: "EXPORT",
      originCode: "DIRE_DAWA",
      destCode: EXP_DEST,
    });
    seedImportContract({
      suffix: "MEIC",
      reference: stampedRef("MEIC"),
      direction: "DOMESTIC",
      originCode: EXP_ORIGIN,
      destCode: "MOJO",
    });
    seedImportContract({
      suffix: "MEIB",
      reference: stampedRef("MEIB"),
      freight: "BULK",
      direction: "DOMESTIC",
      originCode: EXP_ORIGIN,
      destCode: "MOJO",
    });
  });

  it("mixed close day: five mixed reservations, one container + one bulk pay", () => {
    ensureExportRoute();
    resetCorridorDay(CLOSE_DEPARTURE, EXP_DEST, EXP_ORIGIN);
    resetCorridorDay(FULL_DEPARTURE, EXP_DEST, EXP_ORIGIN);
    createImportSchedule({
      departure: CLOSE_DEPARTURE,
      locoPair: ["LOCO-EXP-11", "LOCO-EXP-12"],
      originCode: EXP_ORIGIN,
      destCode: EXP_DEST,
    });
    withExportSchedule(CLOSE_DEPARTURE, (s) => forceWindowOpen(s.id, 60));

    let isoSeed = 15_000;
    CLOSERS.forEach((c) => {
      if (c.freight === "CONTAINER") {
        bookContainers({
          suffix: c.suffix,
          runStamp: stamp,
          isoSeed,
          twenty: c.twenty,
          scheduledDate: CLOSE_DAY,
        });
        isoSeed += c.twenty ?? 0;
      } else {
        bookBulk({ suffix: c.suffix, tons: c.tons!, scheduledDate: CLOSE_DAY });
      }
      acceptExport(c.suffix);
    });
    CLOSERS.filter((c) => c.pays).forEach((c) => {
      markPaid(c.suffix);
      pollAllocations(c.suffix, c.wagons);
      expectWagonType(c.suffix, c.freight === "CONTAINER" ? "NW5" : "CW4", c.wagons);
    });
  });

  it("the window CLOSES — the three unpaid of BOTH kinds expire in one sweep, no reopen", () => {
    withExportSchedule(CLOSE_DEPARTURE, (s) => {
      db(
        `UPDATE freight.train_schedules
         SET window_closes_at = now() - interval '1 second'
         WHERE id = $1 AND window_phase = 'OPEN'`,
        [s.id],
      );
      pollDb<ScheduleRow>(
        "export window DONE (no reopen)",
        `SELECT window_phase, booking_cycle_no FROM freight.train_schedules WHERE id = $1`,
        [s.id],
        (row) => row?.window_phase === "DONE" && Number(row?.booking_cycle_no) === 1,
      );
    });
    // Forced close ⇒ force the matching deadline clamp on the unpaid trio.
    CLOSERS.filter((c) => !c.pays).forEach((c) => forceReservationExpiry(c.suffix));
    CLOSERS.filter((c) => !c.pays).forEach((c) => pollBookingStatus(c.suffix, "EXPIRED"));
    CLOSERS.filter((c) => c.pays).forEach((c) =>
      withBooking(c.suffix, (b) => expect(b.status, `${c.suffix} rides`).to.eq("PAID")),
    );
  });

  it("FULL-train day: through container 40w + sub-corridor bulk 14w flip the window FULL", () => {
    createImportSchedule({
      departure: FULL_DEPARTURE,
      locoPair: ["LOCO-EXP-1", "LOCO-EXP-2"],
      originCode: EXP_ORIGIN,
      destCode: EXP_DEST,
    });
    withExportSchedule(FULL_DEPARTURE, (s) => forceWindowOpen(s.id, 90));

    bookContainers({
      suffix: "FMTH",
      runStamp: stamp,
      isoSeed: 15_500,
      twenty: 80, // 40 wagons, full corridor
      scheduledDate: FULL_DAY,
    });
    acceptExport("FMTH");
    bookBulk({ suffix: "FMSB", tons: 980, scheduledDate: FULL_DAY });
    acceptExport("FMSB");

    markPaid("FMTH");
    pollAllocations("FMTH", 40);
    expectWagonType("FMTH", "NW5", 40);
    markPaid("FMSB");
    pollAllocations("FMSB", 14);
    expectWagonType("FMSB", "CW4", 14);

    withExportSchedule(FULL_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("a container AND a bulk intercity ride-along join the FULL train in ONE accept — clamped, paid, linked", () => {
    bookContainers({ suffix: "MEIC", runStamp: stamp, isoSeed: 15_700, twenty: 4 });
    acceptOperation("MEIC");
    bookBulk({ suffix: "MEIB", tons: 140 });
    acceptOperation("MEIB");

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

export {};
