/**
 * MIXED EXPORT journey XM1 — containers AND wheat share ONE 54-wagon export
 * train (KALITY → … → DJIB_PORT) inside ONE FCFS window: every accept
 * reserves instantly with a close-clamped pay deadline; allocation is typed
 * (NW5 under boxes, CW4 under wheat); then the full journey to Djibouti Port
 * and both export customs tails.
 *
 * The six bookings (Σ = 54 wagons):
 *   XMC1 container customs + USD  16×20ft =  8 × NW5
 *   XMB1 bulk      customs + USD    560 T =  8 × CW4
 *   XMC2 container self    + ETB   6×40ft =  6 × NW5
 *   XMB2 bulk      self    + ETB    980 T = 14 × CW4
 *   XMC3 container customs + ETB  16×20ft =  8 × NW5
 *   XMB3 bulk      self    + USD    700 T = 10 × CW4
 *
 * Sequential steps of one journey — retries off.
 */

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

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

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

const BOOKINGS: Array<{
  suffix: string;
  freight: "CONTAINER" | "BULK";
  customs: boolean;
  currency: "ETB" | "USD";
  twenty?: number;
  forty?: number;
  tons?: number;
  wagons: number;
}> = [
  { suffix: "XMC1", freight: "CONTAINER", customs: true, currency: "USD", twenty: 16, wagons: 8 },
  { suffix: "XMB1", freight: "BULK", customs: true, currency: "USD", tons: 560, wagons: 8 },
  { suffix: "XMC2", freight: "CONTAINER", customs: false, currency: "ETB", forty: 6, wagons: 6 },
  { suffix: "XMB2", freight: "BULK", customs: false, currency: "ETB", tons: 980, wagons: 14 },
  { suffix: "XMC3", freight: "CONTAINER", customs: true, currency: "ETB", twenty: 16, wagons: 8 },
  { suffix: "XMB3", freight: "BULK", customs: false, currency: "USD", tons: 700, wagons: 10 },
];
const CUSTOMS = BOOKINGS.filter((b) => b.customs).map((b) => b.suffix);
const SELF_CLEAR = BOOKINGS.filter((b) => !b.customs).map((b) => b.suffix);

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

describe("mixed export: containers and wheat share one 54-wagon FCFS train", { retries: 0 }, () => {
  before(() => {
    cy.task("db:seedFile", "seed-import-corridor.sql");
    for (const b of BOOKINGS) {
      seedImportContract({
        suffix: b.suffix,
        reference: stampedRef(b.suffix),
        currency: b.currency,
        customs: b.customs,
        freight: b.freight,
        direction: "EXPORT",
        originCode: EXP_ORIGIN,
        destCode: EXP_DEST,
      });
    }
  });

  it("operations prepares the export corridor and one 54-wagon train — window forced open", () => {
    ensureExportRoute();
    resetCorridorDay(DEPARTURE, EXP_DEST, EXP_ORIGIN);
    createImportSchedule({
      departure: DEPARTURE,
      locoPair: ["LOCO-EXP-5", "LOCO-EXP-6"],
      originCode: EXP_ORIGIN,
      destCode: EXP_DEST,
    });
    withScheduleId((id) => forceWindowOpen(id, 60));
  });

  it("three container and three bulk exporters book inside the one window", () => {
    let isoSeed = 13_000;
    BOOKINGS.forEach((b) => {
      if (b.freight === "CONTAINER") {
        bookContainers({
          suffix: b.suffix,
          runStamp: stamp,
          isoSeed,
          twenty: b.twenty,
          forty: b.forty,
          scheduledDate: BOOKING_DAY,
        });
        isoSeed += (b.twenty ?? 0) + (b.forty ?? 0);
      } else {
        bookBulk({ suffix: b.suffix, tons: b.tons!, scheduledDate: BOOKING_DAY });
      }
      pollBookingStatus(b.suffix, "OPERATION_REQUEST_PENDING", 5);
    });
  });

  it("each accept reserves FCFS instantly — both kinds share one clamped ledger", () => {
    BOOKINGS.forEach((b) => acceptExport(b.suffix));
    withScheduleId((_, s) => {
      BOOKINGS.forEach((b) => {
        withBooking(b.suffix, (row) => {
          expect(
            new Date(row.payment_deadline!).getTime(),
            `${b.suffix} deadline never outlives the window close`,
          ).to.be.at.most(new Date(s.window_closes_at!).getTime());
        });
      });
    });
    BOOKINGS.forEach((b) => {
      withBooking(b.suffix, (row) => {
        pollDb<{ currency: string }>(
          `${b.suffix} invoice`,
          `SELECT currency FROM freight.invoices
           WHERE source_id = $1 AND deleted_at IS NULL
           ORDER BY created_at DESC LIMIT 1`,
          [row.id],
          (inv) => inv?.currency === b.currency,
          10,
        );
      });
    });
  });

  it("all six pay — 54/54 STRICTLY typed (boxes on NW5, wheat on CW4), FULL, finalized", () => {
    BOOKINGS.forEach((b) => {
      markPaid(b.suffix);
      pollAllocations(b.suffix, b.wagons);
    });
    BOOKINGS.forEach((b) =>
      expectWagonType(b.suffix, b.freight === "CONTAINER" ? "NW5" : "CW4", b.wagons),
    );
    withScheduleId((id) => {
      pollDb<ScheduleRow>(
        "export window FULL",
        `SELECT booking_window_status FROM freight.train_schedules WHERE id = $1`,
        [id],
        (s) => s?.booking_window_status === "FULL",
      );
      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`,
        [id],
      ).then(({ rows }) => expect(Number(rows[0].n), "54 wagons allocated").to.eq(54));
      apiPost(opsStaff, `/api/train-scheduling/schedules/${id}/finalize`)
        .its("status")
        .should("be.oneOf", [200, 201]);
      pollDb<ScheduleRow>(
        "schedule SCHEDULED",
        `SELECT status FROM freight.train_schedules WHERE id = $1`,
        [id],
        (s) => s?.status === "SCHEDULED",
        10,
      );
    });
  });

  it("transport documents uploaded for customs bookings of BOTH kinds; the train runs to Djibouti", () => {
    withScheduleId((id) => {
      apiPost(opsStaff, `/api/train-scheduling/schedules/${id}/import-djibouti/gatepass-granted`)
        .its("status")
        .should("be.oneOf", [200, 201]);
    });
    CUSTOMS.forEach((suffix) => {
      withBooking(suffix, (b) => {
        glUpload(`/api/contracts/bookings/${b.id}/transport-document`);
      });
    });

    withScheduleId((id) => {
      apiPost(opsStaff, `/api/train-scheduling/schedules/${id}/dispatch`)
        .its("status")
        .should("be.oneOf", [200, 201]);
      pollDb<ScheduleRow>(
        "schedule DISPATCHED",
        `SELECT status FROM freight.train_schedules WHERE id = $1`,
        [id],
        (s) => s?.status === "DISPATCHED",
        10,
      );
      [1, 2, 3, 4].forEach((seq) => {
        apiPost(opsStaff, `/api/train-scheduling/schedules/${id}/checkpoints`, {
          sequenceNo: seq,
          kind: "PASSED",
        })
          .its("status")
          .should("be.oneOf", [200, 201]);
      });
      apiPost(opsStaff, `/api/train-scheduling/schedules/${id}/checkpoints`, {
        sequenceNo: 5,
        kind: "ARRIVED",
      })
        .its("status")
        .should("be.oneOf", [200, 201]);
      pollDb<ScheduleRow>(
        "schedule ARRIVED",
        `SELECT status FROM freight.train_schedules WHERE id = $1`,
        [id],
        (s) => s?.status === "ARRIVED",
        20,
      );
    });
    BOOKINGS.forEach((b) => pollBookingStatus(b.suffix, "ARRIVED", 20));
  });

  it("container AND bulk export tails close side by side (T1 close → offloaded)", () => {
    CUSTOMS.forEach((suffix) => {
      withBooking(suffix, (b) => {
        apiPost("superadmin@tria.com", `/api/contracts/bookings/${b.id}/t1-close`)
          .its("status")
          .should("be.oneOf", [200, 201]);
      });
      completeBookingMilestone(suffix, "OFFLOADED");
      expectMilestoneDone(suffix, "T1_CLOSED");
      expectMilestoneDone(suffix, "OFFLOADED");
    });
    SELF_CLEAR.forEach((suffix) => {
      withBooking(suffix, (b) => {
        expect(b.status, `${suffix} arrived clean`).to.eq("ARRIVED");
      });
    });
  });
});

export {};
