/**
 * IMPORT journey 1 — six container bookings fill a 54-wagon train on the long
 * corridor DJIB_PORT → NAGAD → DIRE_DAWA → E2E_AWASH → MOJO → KALITY, all in
 * the FIRST booking window, then the full life of the train: payment,
 * allocation, gate pass, T1, dispatch, checkpoint-by-checkpoint movement,
 * arrival, and the post-arrival customs tail to IMPORT_PROCESS_COMPLETED.
 *
 * The six bookings (exact wagon math — Σ = 54, the full consist):
 *   FT1  customs + USD   16×20ft            =  8 wagons
 *   FT2  customs + ETB    6×40ft            =  6 wagons
 *   FT3  self    + ETB   12×20ft            =  6 wagons
 *   FT4  self    + ETB    6×40ft            =  6 wagons
 *   FT5  customs + USD   44×20ft            = 22 wagons  (the ≥22-wagon giant)
 *   FT6  self    + USD    4×40ft + 4×20ft   =  6 wagons
 *
 * Sequential steps of one journey — retries off (steps are not idempotent).
 */

import {
  resetCorridorDay,
  acceptOperation,
  apiPost,
  bookContainers,
  closeBookingWindow,
  completeBookingMilestone,
  completeDocReview,
  createImportSchedule,
  db,
  dbBooking,
  departureAt,
  eatDayStr,
  endPaymentPhase,
  ensureCorridorRoute,
  expectMilestoneDone,
  forceWindowOpen,
  glUpload,
  markPaid,
  opsStaff,
  pollAllocations,
  pollBookingStatus,
  pollDb,
  seedImportContract,
  withBooking,
  withSchedule,
  type ScheduleRow,
  expectClearanceOnBookingInvoice,
} from "./import-utils";

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

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

/** suffix → [customs, currency, twenty, forty, wagons] */
const BOOKINGS: Array<{
  suffix: string;
  customs: boolean;
  currency: "ETB" | "USD";
  twenty: number;
  forty: number;
  wagons: number;
}> = [
  { suffix: "FT1", customs: true, currency: "USD", twenty: 16, forty: 0, wagons: 8 },
  { suffix: "FT2", customs: true, currency: "ETB", twenty: 0, forty: 6, wagons: 6 },
  { suffix: "FT3", customs: false, currency: "ETB", twenty: 12, forty: 0, wagons: 6 },
  { suffix: "FT4", customs: false, currency: "ETB", twenty: 0, forty: 6, wagons: 6 },
  { suffix: "FT5", customs: true, currency: "USD", twenty: 44, forty: 0, wagons: 22 },
  { suffix: "FT6", customs: false, currency: "USD", twenty: 4, forty: 4, wagons: 6 },
];
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) {
  withSchedule(DEPARTURE, (s) => fn(s.id, s));
}

describe("import: six bookings fill the 54-wagon corridor 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,
      });
    }
  });

  it("operations ensures the 6-stop import corridor route exists (direction frozen IMPORT)", () => {
    ensureCorridorRoute();
    resetCorridorDay(DEPARTURE);
  });

  it("operations schedules the 54-wagon import train — first window forced open", () => {
    createImportSchedule({ departure: DEPARTURE, locoPair: ["LOCO-IMP-1", "LOCO-IMP-2"] });
    withScheduleId((id) => forceWindowOpen(id, 45));
    withScheduleId((_, s) => {
      expect(s.booking_cycle_no, "FIRST window cycle").to.eq(1);
    });
  });

  it("customer books all six shipments inside the first window", () => {
    let isoSeed = 0;
    BOOKINGS.forEach((b) => {
      bookContainers({
        suffix: b.suffix,
        runStamp: stamp,
        isoSeed,
        twenty: b.twenty,
        forty: b.forty,
        scheduledDate: BOOKING_DAY,
      });
      isoSeed += b.twenty + b.forty;
      pollBookingStatus(b.suffix, "OPERATION_REQUEST_PENDING", 5);
    });
  });

  it("operations accepts all six — the whole pool is FULLY_EXECUTED (in window)", () => {
    BOOKINGS.forEach((b) => acceptOperation(b.suffix));
  });

  it("window closes, doc review completes — the batch reserves ALL six (they fit exactly)", () => {
    withScheduleId((id) => {
      closeBookingWindow(id);
      completeDocReview(id);
    });
    BOOKINGS.forEach((b) =>
      pollBookingStatus(b.suffix, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
    );
    // Reservation = pay deadline + a payable invoice in the CONTRACT currency.
    BOOKINGS.forEach((b) => {
      withBooking(b.suffix, (row) => {
        expect(row.payment_deadline, `${b.suffix} pay deadline`).to.be.a("string");
        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 — allocated onto the train, 54/54 wagons, window FULL and schedule finalized", () => {
    BOOKINGS.forEach((b) => {
      // Customs bookings carry their clearance service fee ON the booking
      // invoice (no prepaid clearance invoice) — assert before settling.
      if (b.customs) expectClearanceOnBookingInvoice(b.suffix);
      markPaid(b.suffix);
      pollAllocations(b.suffix, b.wagons);
    });
    withScheduleId((id) => {
      endPaymentPhase(id);
      // Full train → conclude marks FULL + DONE and auto-finalizes (DRAFT→SCHEDULED).
      pollDb<ScheduleRow>(
        "schedule FULL + DONE + finalized",
        `SELECT window_phase, booking_window_status, status
         FROM freight.train_schedules WHERE id = $1`,
        [id],
        (s) =>
          s?.booking_window_status === "FULL" &&
          s?.window_phase === "DONE" &&
          s?.status === "SCHEDULED",
      );
      db<{ n: string }>(
        `SELECT count(*) AS n FROM freight.train_schedule_bookings
         WHERE train_schedule_id = $1 AND deleted_at IS NULL`,
        [id],
      ).then(({ rows }) => expect(Number(rows[0].n), "6 bookings linked").to.eq(6));
      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));
    });
  });

  it("backoffice sees the full train on the schedule detail", () => {
    cy.loginBackoffice(opsStaff);
    withScheduleId((id) => cy.visit(`/dashboard/operations/train-scheduling-v2/${id}`));
    cy.contains(/54/, { timeout: 30000 }).should("exist");
  });

  it("GL Djibouti: gate pass granted, T1 documents uploaded for the customs bookings", () => {
    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}/t1-documents`);
      });
    });
  });

  it("the train dispatches — every booking boards at the origin (IN_TRANSIT)", () => {
    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,
      );
    });
    BOOKINGS.forEach((b) => pollBookingStatus(b.suffix, "IN_TRANSIT", 10));
    BOOKINGS.forEach((b) =>
      withBooking(b.suffix, (row) => {
        db<{ loaded_at: string | null }>(
          `SELECT loaded_at FROM freight.bookings WHERE id = $1`,
          [row.id],
        ).then(({ rows }) => expect(rows[0].loaded_at, `${b.suffix} loaded`).to.be.a("string"));
      }),
    );
  });

  it("the train runs the corridor checkpoint by checkpoint and arrives at the terminal", () => {
    withScheduleId((id) => {
      recordAll(id);
      pollDb<ScheduleRow>(
        "schedule ARRIVED",
        `SELECT status FROM freight.train_schedules WHERE id = $1`,
        [id],
        (s) => s?.status === "ARRIVED",
        20,
      );
    });
    // Final-yard auto-arrive settles every booking + its wagons at KALITY.
    BOOKINGS.forEach((b) => pollBookingStatus(b.suffix, "ARRIVED", 20));
    withScheduleId((id) => {
      db<{ n: string }>(
        `SELECT count(*) AS n FROM freight.wagon_movements
         WHERE train_schedule_id = $1`,
        [id],
      ).then(({ rows }) =>
        expect(Number(rows[0].n), "wagon movement ledger rows").to.be.at.least(54),
      );
    });

    function recordAll(id: string) {
      // seq 0 = origin DEPARTED is stamped by dispatch; walk the rest.
      [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]);
    }
  });

  it("GL Ethiopia runs the customs tail on every customs booking (T1 close → risk → second duty → release → final invoice)", () => {
    CUSTOMS.forEach((suffix) => {
      withBooking(suffix, (b) => {
        apiPost("superadmin@tria.com", `/api/contracts/bookings/${b.id}/t1-close`)
          .its("status")
          .should("be.oneOf", [200, 201]);
        apiPost("superadmin@tria.com", `/api/contracts/bookings/${b.id}/risk`, {
          riskLevel: "GREEN",
        })
          .its("status")
          .should("be.oneOf", [200, 201]);
        glUpload(
          `/api/contracts/bookings/${b.id}/second-duty`,
          { dutyRequired: "false" },
          "attachment",
        );
      });
      completeBookingMilestone(suffix, "IMPORT_RELEASE_GRANTED");
      expectMilestoneDone(suffix, "T1_CLOSED");
      expectMilestoneDone(suffix, "RISK_ASSIGNED");
      expectMilestoneDone(suffix, "IMPORT_RELEASE_GRANTED");
    });
  });

  it("GL Djibouti raises the final invoice; GL confirms the slip — import process completed", () => {
    CUSTOMS.forEach((suffix) => {
      withBooking(suffix, (b) => {
        glUpload(
          `/api/contracts/bookings/${b.id}/final-invoice`,
          { amount: "1000", description: "e2e final invoice" },
          "file",
        );
        // The customer attaches the payment slip; only then can GL confirm.
        glUpload(`/api/contracts/bookings/${b.id}/final-invoice-slip`, {}, "file");
        apiPost(
          "superadmin@tria.com",
          `/api/contracts/bookings/${b.id}/final-invoice/confirm`,
        )
          .its("status")
          .should("be.oneOf", [200, 201]);
      });
      completeBookingMilestone(suffix, "IMPORT_PROCESS_COMPLETED");
      expectMilestoneDone(suffix, "IMPORT_PROCESS_COMPLETED");
    });
  });

  it("the self-clearance bookings arrived clean — no customs tail required", () => {
    SELF_CLEAR.forEach((suffix) => {
      withBooking(suffix, (b) => {
        expect(b.status, `${suffix} final status`).to.eq("ARRIVED");
      });
      dbBooking(suffix).then(({ rows }) => {
        db<{ n: string }>(
          `SELECT count(*) AS n FROM freight.clearance_milestones
           WHERE booking_id = $1 AND milestone_code = 'T1_CLOSED'
             AND status = 'COMPLETED' AND deleted_at IS NULL`,
          [rows[0].id],
        ).then(({ rows: ms }) =>
          expect(Number(ms[0].n), `${suffix} has no T1 tail`).to.eq(0),
        );
      });
    });
  });
});

export {};
